001package co.codewizards.cloudstore.core.util;
002
003import static co.codewizards.cloudstore.core.util.Util.*;
004import static java.util.Objects.*;
005
006import java.lang.ref.Reference;
007import java.lang.ref.WeakReference;
008import java.lang.reflect.Constructor;
009import java.lang.reflect.Field;
010import java.lang.reflect.InvocationTargetException;
011import java.lang.reflect.Method;
012import java.lang.reflect.ParameterizedType;
013import java.lang.reflect.Type;
014import java.lang.reflect.TypeVariable;
015import java.util.ArrayList;
016import java.util.Collection;
017import java.util.Collections;
018import java.util.HashMap;
019import java.util.LinkedHashSet;
020import java.util.LinkedList;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.WeakHashMap;
025
026import org.slf4j.Logger;
027import org.slf4j.LoggerFactory;
028
029public class ReflectionUtil {
030
031        private static final Logger logger = LoggerFactory.getLogger(ReflectionUtil.class);
032
033        private ReflectionUtil() { }
034
035        public static <T> T invokeConstructor(final Class<T> clazz, final Object ... args) {
036                requireNonNull(clazz, "clazz");
037
038                final Class<?>[] argTypes = getArgumentTypes(args);
039
040                final List<Constructor<?>> compatibleConstructors = new ArrayList<>();
041                for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
042                        if (isConstructorCompatible(constructor, argTypes))
043                                compatibleConstructors.add(constructor);
044                }
045
046                if (compatibleConstructors.isEmpty()) {
047                        final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(clazz.getSimpleName(), argTypes);
048                        throw new IllegalArgumentException(new NoSuchMethodException(String.format("None of the constructors of %s matches %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes)));
049                }
050
051                if (compatibleConstructors.size() > 1 && logger.isDebugEnabled()) {
052                        // TODO find + invoke the *most* *suitable* one - instead of logging this warning (and simply invoking the first).
053                        final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(clazz.getSimpleName(), argTypes);
054                        final String msg = String.format("%s declares multiple constructors matching %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes);
055//                      throw new IllegalArgumentException(new NoSuchMethodException(msg));
056                        logger.warn("invokeConstructor: {}", msg);
057                }
058
059                return cast(invoke(compatibleConstructors.get(0), args));
060        }
061
062        public static <T> T invokeConstructor(final Class<T> clazz, Class<?>[] parameterTypes, final Object ... args) {
063                final Constructor<T> constructor = getDeclaredConstructorOrFail(clazz, parameterTypes);
064                return invoke(constructor, args);
065        }
066
067        public static <T> T invokeStatic(final Class<?> clazz, final String methodName, final Object ... args) {
068                requireNonNull(clazz, "clazz");
069                requireNonNull(methodName, "methodName");
070                return invoke(clazz, (Object)null, methodName, args);
071        }
072
073        public static <T> T invokeStatic(final Class<?> clazz, final String methodName, Class<?>[] parameterTypes, final Object ... args) {
074                final Method method = getDeclaredMethodOrFail(clazz, methodName, parameterTypes);
075                return invoke((Object)null, method, args);
076        }
077
078        public static <T> T invoke(final Object object, final String methodName, final Object ... args) {
079                requireNonNull(object, "object");
080                requireNonNull(methodName, "methodName");
081                return invoke(object.getClass(), object, methodName, args);
082        }
083
084        private static <T> T invoke(final Class<?> clazz, final Object object, final String methodName, final Object ... args) {
085                requireNonNull(clazz, "clazz");
086                // object may be null
087                requireNonNull(methodName, "methodName");
088                // args may be null
089
090                final Class<?>[] argTypes = getArgumentTypes(args);
091
092                // TODO cache methods - don't search for them again and again
093                final List<Method> methods = getDeclaredMethods(clazz, methodName);
094                final List<Method> compatibleMethods = new ArrayList<>(Math.min(5, methods.size()));
095                for (final Method method : methods) {
096                        if (isMethodCompatible(method, argTypes))
097                                compatibleMethods.add(method);
098                }
099
100                if (compatibleMethods.isEmpty()) {
101                        final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(methodName, argTypes);
102                        throw new IllegalArgumentException(new NoSuchMethodException(String.format("Neither %s nor one of its super-classes declares the method %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes)));
103                }
104
105                if (compatibleMethods.size() > 1 && logger.isDebugEnabled()) {
106                        // TODO find + invoke the *most* *suitable* one - instead of logging this warning (and simply invoking the first).
107                        final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(methodName, argTypes);
108                        final String msg = String.format("%s and its super-classes declare multiple methods matching %s (or an equivalent using super-types of these parameter-types)!", clazz.getName(), methodNameWithParameterTypes);
109//                      final Exception x = new NoSuchMethodException(msg);
110                        logger.warn("invoke: {}", msg);
111                }
112
113                return invoke(object, compatibleMethods.get(0), args);
114        }
115
116        private static Class<?>[] getArgumentTypes(final Object... args) {
117                final Class<?>[] argTypes = args == null ? new Class<?>[0] : new Class<?>[args.length];
118                for (int i = 0; i < argTypes.length; i++)
119                        argTypes[i] = args[i] == null ? null : args[i].getClass();
120                return argTypes;
121        }
122
123        public static <T> T invoke(final Object object, final String methodName, Class<?>[] parameterTypes, final Object ... args) {
124                requireNonNull(object, "object");
125                requireNonNull(methodName, "methodName");
126
127                if (parameterTypes == null)
128                        return invoke(object, methodName, args);
129
130                final Method method = getDeclaredMethodOrFail(object.getClass(), methodName, parameterTypes);
131                return invoke(object, method, args);
132        }
133
134        private static <T> T invoke(final Constructor<T> constructor, final Object ... args) {
135                try {
136                        constructor.setAccessible(true);
137
138                        final Object result = constructor.newInstance(args);
139                        return cast(result);
140                } catch (IllegalArgumentException e) {
141                        throw e;
142                } catch (IllegalAccessException | InstantiationException e) {
143                        throw new RuntimeException(e);
144                } catch (InvocationTargetException e) {
145                        throw new RuntimeException(e.getCause());
146                }
147        }
148
149        private static <T> T invoke(final Object object, Method method, final Object ... args) {
150                try {
151                        method.setAccessible(true);
152
153                        final Object result = method.invoke(object, args);
154                        return cast(result);
155                } catch (final IllegalArgumentException e) {
156                        throw e;
157                } catch (final IllegalAccessException e) {
158                        throw new RuntimeException(e);
159                } catch (final InvocationTargetException e) {
160                        final Throwable cause = e.getCause();
161                        if (cause instanceof RuntimeException)
162                                throw (RuntimeException) cause;
163                        else if (cause instanceof Error)
164                                throw (Error) cause;
165                        else
166                                throw new RuntimeException(cause);
167                }
168        }
169
170        private static boolean isConstructorCompatible(final Constructor constructor, final Class<?>[] argTypes) {
171                final Class<?>[] parameterTypes = constructor.getParameterTypes();
172                return areMethodParametersCompatible(parameterTypes, argTypes);
173        }
174
175        private static boolean isMethodCompatible(final Method method, final Class<?>[] argTypes) {
176                final Class<?>[] parameterTypes = method.getParameterTypes();
177                return areMethodParametersCompatible(parameterTypes, argTypes);
178        }
179
180        private static boolean areMethodParametersCompatible(final Class<?>[] parameterTypes, final Class<?>[] argTypes) {
181                if (argTypes.length != parameterTypes.length)
182                        return false;
183
184                for (int i = 0; i < parameterTypes.length; i++) {
185                        if (argTypes[i] != null
186                                        && ! parameterTypes[i].isAssignableFrom(argTypes[i])
187                                        && ! isSimpleTypeAssignmentPossible(parameterTypes[i], argTypes[i]))
188                                return false;
189                }
190
191                return true;
192        }
193
194        private static boolean isSimpleTypeAssignmentPossible(Class<?> parameterType, Class<?> argType) {
195                if (parameterType == boolean.class && argType == Boolean.class)
196                        return true;
197
198                if (parameterType == byte.class && argType == Byte.class)
199                        return true;
200
201                if (parameterType == char.class && argType == Character.class)
202                        return true;
203
204                if (parameterType == double.class && argType == Double.class)
205                        return true;
206
207                if (parameterType == float.class && argType == Float.class)
208                        return true;
209
210                if (parameterType == int.class && argType == Integer.class)
211                        return true;
212
213                if (parameterType == long.class && argType == Long.class)
214                        return true;
215
216                if (parameterType == short.class && argType == Short.class)
217                        return true;
218
219                return false;
220        }
221
222        public static List<Method> getDeclaredMethods(final Class<?> clazz, final String name) {
223                final List<Method> result = new ArrayList<>();
224
225                Class<?> c = clazz;
226                while (c != null) {
227                        final Method[] methods = c.getDeclaredMethods();
228                        for (final Method method : methods) {
229                                if (name.equals(method.getName()))
230                                        result.add(method);
231                        }
232                        c = c.getSuperclass();
233                }
234
235                return result;
236        }
237
238        public static <T> Constructor<T> getDeclaredConstructorOrFail(final Class<T> clazz, final Class<?>[] parameterTypes) {
239                final Constructor<T> constructor;
240                try {
241                        constructor = clazz.getDeclaredConstructor(parameterTypes);
242                } catch (NoSuchMethodException | SecurityException e) {
243                        final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(clazz.getName(), parameterTypes);
244                        throw new IllegalArgumentException(new NoSuchMethodException(String.format("%s does not declare the method %s!", clazz.getName(), methodNameWithParameterTypes)).initCause(e));
245                }
246                return constructor;
247        }
248
249        public static Method getDeclaredMethodOrFail(final Class<?> clazz, final String name, final Class<?>[] parameterTypes) {
250                final Method method = getDeclaredMethod(clazz, name, parameterTypes);
251                if (method == null) {
252                        final String methodNameWithParameterTypes = createMethodNameWithParameterTypes(name, parameterTypes);
253                        throw new IllegalArgumentException(new NoSuchMethodException(String.format("Neither %s nor one of its super-classes declares the method %s!", clazz.getName(), methodNameWithParameterTypes)));
254                }
255                return method;
256        }
257
258        private static String createMethodNameWithParameterTypes(final String name, final Class<?>[] parameterTypes) {
259                final StringBuilder sb = new StringBuilder();
260                if (parameterTypes == null)
261                        return name + "(...)";
262
263                for (Class<?> parameterType : parameterTypes) {
264                        if (sb.length() > 0)
265                                sb.append(", ");
266
267                        sb.append(parameterType.getName());
268                }
269                return name + '(' + sb.toString() + ')';
270        }
271
272        public static Method getDeclaredMethod(final Class<?> clazz, final String name, final Class<?>[] parameterTypes) {
273                Class<?> c = clazz;
274                while (c != null) {
275                        try {
276                                final Method declaredMethod = c.getDeclaredMethod(name, parameterTypes);
277                                return declaredMethod;
278                        } catch (NoSuchMethodException x) {
279                                doNothing(); // expected in many cases ;-)
280                        }
281                        c = c.getSuperclass();
282                }
283                return null;
284        }
285
286        private static final Map<Class<?>, Reference<List<Field>>> class2AllDeclaredFields = Collections.synchronizedMap(
287                        new WeakHashMap<Class<?>, Reference<List<Field>>>());
288
289        /**
290         * Gets all fields declared by the given {@code clazz} including its super-classes; starting from the most concrete sub-class
291         * (i.e. the given {@code clazz}).
292         * <p>
293         * Please note that the order of the fields declared by one single class is unspecified according to the Java specification.
294         * The only guaranteed order is that between fields declared by one class and fields declared by its super-classes: The result
295         * of this method begins with fields of the most concrete class, followed by its super-class's fields, followed by the
296         * fields of the super-class' super-class and so on.
297         * <p>
298         * For example, consider the following classes: {@code Roadster} <i>extends</i> {@code Car} <i>extends</i> {@code Vehicle}. This
299         * method would thus return all fields declared by all three classes, starting with the fields of {@code Roadster},
300         * followed by the fields of {@code Car} and finally followed by the fields of {@code Vehicle}.
301         *
302         * @param clazz the class whose fields to obtain.
303         * @return the list of fields declared by the given class and all its super-classes. Never <code>null</code>, but maybe
304         * empty.
305         */
306        public static List<Field> getAllDeclaredFields(final Class<?> clazz) {
307                requireNonNull(clazz, "clazz");
308                synchronized(clazz) {
309                        final Reference<List<Field>> resultRef = class2AllDeclaredFields.get(clazz);
310                        List<Field> result = resultRef == null ? null : resultRef.get();
311                        if (result == null) {
312                                result = new ArrayList<>();
313                                Class<?> c = clazz;
314                                while (c != null) {
315                                        final Field[] declaredFields = c.getDeclaredFields();
316                                        for (Field field : declaredFields)
317                                                result.add(field);
318
319                                        c = c.getSuperclass();
320                                }
321                                ((ArrayList<?>)result).trimToSize();
322                                class2AllDeclaredFields.put(clazz, new WeakReference<List<Field>>(result));
323                        }
324                        return result;
325                }
326        }
327
328        public static Map<Field, Object> getAllDeclaredFieldValues(final Object object) {
329                requireNonNull(object, "object");
330
331                final List<Field> allDeclaredFields = getAllDeclaredFields(object.getClass());
332                final Map<Field, Object> result = new HashMap<>(allDeclaredFields.size());
333                for (Field field : allDeclaredFields) {
334                        field.setAccessible(true);
335                        try {
336                                final Object fieldValue = field.get(object);
337                                result.put(field, fieldValue);
338                        } catch (IllegalAccessException e) {
339                                throw new RuntimeException(e);
340                        }
341                }
342                return result;
343        }
344
345        public static <V> V getFieldValue(final Object object, final String fieldName) {
346                requireNonNull(object, "object");
347                requireNonNull(fieldName, "fieldName");
348
349                // TODO pretty inefficient implementation - make better!
350
351                String className = null;
352                String simpleFieldName = fieldName;
353
354                final int lastDotIndex = fieldName.lastIndexOf('.');
355                if (lastDotIndex >= 0) {
356                        className = fieldName.substring(0, lastDotIndex);
357                        simpleFieldName = fieldName.substring(lastDotIndex + 1);
358                }
359
360                final List<Field> declaredFields = getAllDeclaredFields(object.getClass());
361                for (final Field field : declaredFields) {
362                        if (className != null && !className.equals(field.getDeclaringClass().getName()))
363                                continue;
364
365                        if (!simpleFieldName.equals(field.getName()))
366                                continue;
367
368                        field.setAccessible(true);
369                        try {
370                                @SuppressWarnings("unchecked")
371                                final V value = (V) field.get(object);
372                                return value;
373                        } catch (IllegalAccessException e) {
374                                throw new RuntimeException(e);
375                        }
376                }
377
378                throw new IllegalArgumentException(String.format("object's class %s does not have this field: %s", object.getClass(), fieldName));
379        }
380
381        /**
382         * Sets the {@code object}'s field &#8211; identified by {@code fieldName} &#8211; to the given {@code value}.
383         * <p>
384         * The {@code fieldName} can be simple (e.g. "firstName") or fully qualified (e.g. "co.codewizards.bla.Person.firstName").
385         * If it is simple, the most concrete sub-class' matching field is used.
386         *
387         * @param object the object whose field to manipulate. Must not be <code>null</code>.
388         * @param fieldName the simple or fully qualified field name (fully qualified means prefixed by the class name). Must not be <code>null</code>.
389         * @param value the value to be assigned. May be <code>null</code>.
390         */
391        public static void setFieldValue(final Object object, final String fieldName, final Object value) {
392                requireNonNull(object, "object");
393                requireNonNull(fieldName, "fieldName");
394
395                // TODO pretty inefficient implementation - make better!
396
397                String className = null;
398                String simpleFieldName = fieldName;
399
400                final int lastDotIndex = fieldName.lastIndexOf('.');
401                if (lastDotIndex >= 0) {
402                        className = fieldName.substring(0, lastDotIndex);
403                        simpleFieldName = fieldName.substring(lastDotIndex + 1);
404                }
405
406                final List<Field> declaredFields = getAllDeclaredFields(object.getClass());
407                for (final Field field : declaredFields) {
408                        if (className != null && !className.equals(field.getDeclaringClass().getName()))
409                                continue;
410
411                        if (!simpleFieldName.equals(field.getName()))
412                                continue;
413
414                        field.setAccessible(true);
415                        try {
416                                field.set(object, value);
417                        } catch (IllegalAccessException e) {
418                                throw new RuntimeException(e);
419                        }
420                        return;
421                }
422
423                throw new IllegalArgumentException(String.format("object's class %s does not have this field: %s", object.getClass(), fieldName));
424        }
425
426        public static Set<Class<?>> getAllInterfaces(final Class<?> clazz) {
427                requireNonNull(clazz, "clazz");
428
429                final Set<Class<?>> interfaces = new LinkedHashSet<>();
430
431                Class<?> c = clazz;
432                while (c != null) {
433                        populateInterfaces(interfaces, c);
434                        c = c.getSuperclass();
435                }
436                return interfaces;
437        }
438
439        private static void populateInterfaces(Collection<Class<?>> interfaces, Class<?> clazz) {
440                for (final Class<?> iface : clazz.getInterfaces())
441                        interfaces.add(iface);
442
443                for (final Class<?> iface : clazz.getInterfaces())
444                        populateInterfaces(interfaces, iface);
445        }
446
447        /**
448         * Resolves the actual type arguments of a base-class declared in a concrete sub-class.
449         * <p>
450         * This is a convenience method delegating to {@link #resolveActualTypeArguments(Class, Class)}
451         * passing {@code concreteObject.getClass()} as {@code concreteClass}.
452         * @param baseClass the base class. Must not be <code>null</code>.
453         * @param concreteObject an instance of a sub-class of the generic {@code baseClass}.
454         * @return the resolved type arguments. Never <code>null</code> (empty array for a non-generic base-class).
455         */
456        public static final <T> Type[] resolveActualTypeArguments(final Class<T> baseClass, final T concreteObject) {
457                requireNonNull(baseClass, "baseClass");
458                requireNonNull(concreteObject, "concreteObject");
459                @SuppressWarnings("unchecked")
460                final Class<? extends T> concreteClass = (Class<? extends T>) concreteObject.getClass();
461                return resolveActualTypeArguments(baseClass, concreteClass);
462        }
463
464        /**
465         * Resolves the actual type arguments of a base-class declared in a concrete sub-class.
466         * <p>
467         * The length as well as the order of the resolved type arguments matches the declaration order
468         * in the base-class. If a type argument could successfully be resolved, it is usually an instance of
469         * {@link Class}. If it could not be resolved (because the sub-class does not specify the generic type info
470         * - directly or indirectly), it is an instance of {@link TypeVariable}.
471         * <p>
472         * A typical use-case is this:
473         * <pre>
474         * public abstract class MyBase&lt;A, B, C&gt; {
475         *   final Class&lt;A&gt; actualTypeArgumentA;
476         *   final Class&lt;B&gt; actualTypeArgumentB;
477         *   final Class&lt;C&gt; actualTypeArgumentC;
478         *
479         *   public MyBase() {
480         *     final Type[] actualTypeArguments = resolveActualTypeArguments(MyBase.class, this);
481         *
482         *     // The following assignments fail - of course -, if the concrete class lacks
483         *     // generic type info - like the example class "MyFail" below.
484         *     actualTypeArgumentA = (Class&lt;A&gt;) actualTypeArguments[0];
485         *     actualTypeArgumentB = (Class&lt;B&gt;) actualTypeArguments[1];
486         *     actualTypeArgumentC = (Class&lt;C&gt;) actualTypeArguments[2];
487         *   }
488         * }
489         *
490         * public class MyConcrete extends MyBase&lt;Long, Boolean, String&gt; {
491         * }
492         *
493         * public class MyFail extends MyBase {
494         * }
495         * </pre>
496         *
497         * @param baseClass the base class. Must not be <code>null</code>.
498         * @param concreteClass a sub-class of the generic {@code baseClass}.
499         * @return the resolved type arguments. Never <code>null</code> (empty array for a non-generic base-class).
500         */
501        public static final <T> Type[] resolveActualTypeArguments(final Class<T> baseClass, final Class<? extends T> concreteClass) {
502                return _resolveActualTypeArgs(baseClass, concreteClass);
503        }
504
505        private static final <T> Type[] _resolveActualTypeArgs(final Class<T> baseClass, final Class<? extends T> concreteClass, final Type... actualArgs) {
506                requireNonNull(baseClass, "baseClass");
507                requireNonNull(concreteClass, "concreteClass");
508                requireNonNull(actualArgs, "actualArgs");
509
510            if (actualArgs.length != 0 && actualArgs.length != concreteClass.getTypeParameters().length)
511                throw new IllegalArgumentException("actualArgs.length != 0 && actualArgs.length != concreteClass.typeParameters.length");
512
513            final Type[] _actualArgs = actualArgs.length == 0 ? concreteClass.getTypeParameters() : actualArgs;
514
515            // map type parameters into the actual types
516            Map<String, Type> typeVariables = new HashMap<String, Type>();
517            for (int i = 0; i < _actualArgs.length; i++) {
518                TypeVariable<?> typeVariable = concreteClass.getTypeParameters()[i];
519                typeVariables.put(typeVariable.getName(), _actualArgs[i]);
520            }
521
522            // Find direct ancestors (superclass, interfaces)
523            List<Type> ancestors = new LinkedList<Type>();
524            if (concreteClass.getGenericSuperclass() != null) {
525                ancestors.add(concreteClass.getGenericSuperclass());
526            }
527            for (Type t : concreteClass.getGenericInterfaces()) {
528                ancestors.add(t);
529            }
530
531            // Recurse into ancestors (superclass, interfaces)
532            for (Type type : ancestors) {
533                if (type instanceof Class<?>) {
534                    // ancestor is non-parameterized. Recurse only if it matches the base class.
535                    Class<?> ancestorClass = (Class<?>) type;
536                    if (baseClass.isAssignableFrom(ancestorClass)) {
537                        Type[] result = _resolveActualTypeArgs(baseClass, (Class<? extends T>) ancestorClass);
538                        if (result != null) {
539                            return result;
540                        }
541                    }
542                }
543                if (type instanceof ParameterizedType) {
544                    // ancestor is parameterized. Recurse only if the raw type matches the base class.
545                    ParameterizedType parameterizedType = (ParameterizedType) type;
546                    Type rawType = parameterizedType.getRawType();
547                    if (rawType instanceof Class<?>) {
548                        Class<?> rawTypeClass = (Class<?>) rawType;
549                        if (baseClass.isAssignableFrom(rawTypeClass)) {
550
551                            // loop through all type arguments and replace type variables with the actually known types
552                            List<Type> resolvedTypes = new LinkedList<Type>();
553                            for (Type t : parameterizedType.getActualTypeArguments()) {
554                                if (t instanceof TypeVariable<?>) {
555                                    Type resolvedType = typeVariables.get(((TypeVariable<?>) t).getName());
556                                    resolvedTypes.add(resolvedType != null ? resolvedType : t);
557                                } else {
558                                    resolvedTypes.add(t);
559                                }
560                            }
561
562                            Type[] result = _resolveActualTypeArgs(baseClass, (Class<? extends T>) rawTypeClass, resolvedTypes.toArray(new Type[] {}));
563                            if (result != null) {
564                                return result;
565                            }
566                        }
567                    }
568                }
569            }
570
571            // we have a result if we reached the base class.
572            return concreteClass.equals(baseClass) ? _actualArgs : null;
573        }
574}