001// Generated by delombok at Mon Jan 02 16:27:27 UTC 2023
002package io.cui.tools.reflect;
003
004import static io.cui.tools.collect.MoreCollections.requireNotEmpty;
005import static java.util.Objects.requireNonNull;
006import java.lang.annotation.Annotation;
007import java.lang.reflect.Field;
008import java.lang.reflect.InvocationHandler;
009import java.lang.reflect.Method;
010import java.lang.reflect.Modifier;
011import java.lang.reflect.ParameterizedType;
012import java.lang.reflect.Proxy;
013import java.lang.reflect.Type;
014import java.util.ArrayList;
015import java.util.Collection;
016import java.util.Collections;
017import java.util.HashMap;
018import java.util.List;
019import java.util.Map;
020import java.util.Objects;
021import java.util.Optional;
022import java.util.WeakHashMap;
023import io.cui.tools.base.Preconditions;
024import io.cui.tools.collect.CollectionBuilder;
025import io.cui.tools.logging.CuiLogger;
026
027/**
028 * Provides a number of methods simplifying the usage of Reflection-based access.
029 * <h2>Caution</h2>
030 * <p>
031 * Use reflection only if there is no other way. Even if some of the problems are
032 * minimized by using this type. It should be used either in test-code, what is we actually do, and
033 * not production code. An other reason could be framework code. as for that you should exactly know
034 * what you do.
035 * </p>
036 *
037 * @author Oliver Wolff
038 */
039public final class MoreReflection {
040    @java.lang.SuppressWarnings("all")
041    @lombok.Generated
042    private static final java.lang.Object $LOCK = new java.lang.Object[0];
043    private static final String IGNORING_METHOD_ON_CLASS = "Ignoring method \'{}\' on class \'{}\'";
044    private static final CuiLogger log = new CuiLogger(MoreReflection.class);
045    /**
046     * We use {@link WeakHashMap} in order to allow the garbage collector to do its job
047     */
048    private static final Map<Class<?>, List<Method>> publicObjectMethodCache = new WeakHashMap<>();
049    private static final Map<Class<?>, Map<String, Field>> fieldCache = new WeakHashMap<>();
050
051    // owolff: computeIfAbsent is not an option because we add null
052    // to the field
053    /**
054     * Tries to access a field on a given type. If none can be found it recursively calls itself
055     * with the corresponding parent until {@link Object}.
056     * <em>Caution:</em>
057     * <p>
058     * The field elements are shared between requests (cached), therefore you must
059     * ensure that changes to the instance, like {@link Field#setAccessible(boolean)} are reseted
060     * by the client. This can be simplified by using {@link FieldWrapper}
061     * </p>
062     *
063     * @param type to be checked, must not be null
064     * @param fieldName to be checked, must not be null
065     * @return an {@link Optional} {@link Field} if it can be found
066     */
067    @SuppressWarnings("java:S3824")
068    public static Optional<Field> accessField(final Class<?> type, final String fieldName) {
069        synchronized (MoreReflection.$LOCK) {
070            requireNonNull(type);
071            requireNonNull(fieldName);
072            if (!fieldCache.containsKey(type)) {
073                fieldCache.put(type, new HashMap<>());
074            }
075            final Map<String, Field> typeMap = fieldCache.get(type);
076            if (!typeMap.containsKey(fieldName)) {
077                typeMap.put(fieldName, resolveField(type, fieldName).orElse(null));
078            }
079            return Optional.ofNullable(typeMap.get(fieldName));
080        }
081    }
082
083    private static Optional<Field> resolveField(final Class<?> type, final String fieldName) {
084        try {
085            return Optional.of(type.getDeclaredField(fieldName));
086        } catch (final NoSuchFieldException | SecurityException e) {
087            log.trace("Error while trying to read field {} on type {}", type, fieldName, e);
088            if (Object.class.equals(type.getClass()) || null == type.getSuperclass()) {
089                return Optional.empty();
090            }
091            return resolveField(type.getSuperclass(), fieldName);
092        }
093    }
094
095    /**
096     * Determines the public not static methods of a given {@link Class}. {@link Object#getClass()}
097     * will implicitly ignore
098     *
099     * @param clazz to be checked
100     * @return the found public-methods.
101     */
102    public static List<Method> retrievePublicObjectMethods(final Class<?> clazz) {
103        synchronized (MoreReflection.$LOCK) {
104            requireNonNull(clazz);
105            if (!publicObjectMethodCache.containsKey(clazz)) {
106                final List<Method> found = new ArrayList<>();
107                for (final Method method : clazz.getMethods()) {
108                    final int modifiers = method.getModifiers();
109                    if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && !"getClass".equals(method.getName())) {
110                        found.add(method);
111                    }
112                }
113                publicObjectMethodCache.put(clazz, found);
114                return found;
115            }
116            return publicObjectMethodCache.get(clazz);
117        }
118    }
119
120    /**
121     * Determines the access methods of a given class. An access method is defined as being a public
122     * not static zero-argument method that is prefixed with either "get" or "is". The Method
123     * "getClass" is explicitly filtered
124     *
125     * @param clazz to be checked
126     * @return the found access-methods.
127     */
128    public static List<Method> retrieveAccessMethods(final Class<?> clazz) {
129        final List<Method> found = new ArrayList<>();
130        for (final Method method : retrievePublicObjectMethods(clazz)) {
131            if (0 == method.getParameterCount()) {
132                final String name = method.getName();
133                if (name.startsWith("get") || name.startsWith("is")) {
134                    log.debug("Adding found method \'{}\' on class \'{}\'", name, clazz);
135                    found.add(method);
136                }
137            } else {
138                log.trace(IGNORING_METHOD_ON_CLASS, method.getName(), clazz);
139            }
140        }
141        return found;
142    }
143
144    /**
145     * Determines the access methods of a given class. An access method is defined as being a public
146     * not static zero-argument method that is prefixed with either "get" or "is". The Method
147     * "getClass" is explicitly filtered
148     *
149     * @param clazz to be checked
150     * @param ignoreProperties identifies the property by name that must be filtered from the result
151     * @return the found access-methods.
152     */
153    public static List<Method> retrieveAccessMethods(final Class<?> clazz, final Collection<String> ignoreProperties) {
154        final List<Method> found = new ArrayList<>();
155        for (final Method method : retrieveAccessMethods(clazz)) {
156            final String propertyName = computePropertyNameFromMethodName(method.getName());
157            if (!ignoreProperties.contains(propertyName)) {
158                found.add(method);
159            }
160        }
161        return found;
162    }
163
164    /**
165     * Determines the modifier methods of a given class. A modifier method is defined as being a
166     * public not static single-argument method that is prefixed with either "set" or consists of
167     * the propertyName only.
168     *
169     * @param clazz to be checked
170     * @param propertyName to be checked, must not be null
171     * @param parameterType identifying the parameter to be passed to the given method, must not be
172     *            null
173     * @return the found modifier-method or {@link Optional#empty()} if none could be found
174     */
175    public static Optional<Method> retrieveWriteMethod(final Class<?> clazz, final String propertyName, final Class<?> parameterType) {
176        requireNonNull(parameterType);
177        for (final Method method : retrieveWriteMethodCandidates(clazz, propertyName)) {
178            if (checkWhetherParameterIsAssignable(method.getParameterTypes()[0], parameterType)) {
179                return Optional.of(method);
180            } else {
181                log.trace(IGNORING_METHOD_ON_CLASS, method.getName(), clazz);
182            }
183        }
184        return Optional.empty();
185    }
186
187    /**
188     * @param assignableSource the type to be checked
189     * @param queryType to be checked for
190     * @return boolean indicating whether the given parameter, identified by their class attributes
191     *         match
192     */
193    public static boolean checkWhetherParameterIsAssignable(final Class<?> assignableSource, final Class<?> queryType) {
194        requireNonNull(assignableSource);
195        requireNonNull(queryType);
196        if (assignableSource.equals(queryType)) {
197            log.trace("Parameter-type matches exactly \'%s\'", assignableSource);
198            return true;
199        }
200        if (assignableSource.isAssignableFrom(queryType)) {
201            log.trace("Parameter \'%s\' is assignable from \'%s\'", assignableSource, queryType);
202            return true;
203        }
204        final Class<?> boxedSource = resolveWrapperTypeForPrimitive(assignableSource);
205        final Class<?> boxedQuery = resolveWrapperTypeForPrimitive(queryType);
206        if (boxedSource.equals(boxedQuery)) {
207            log.trace("Parameter-type matches exactly after autoboxing \'%s\'", assignableSource);
208            return true;
209        }
210        return boxedSource.isAssignableFrom(boxedQuery);
211    }
212
213    /**
214     * Helper class for converting a primitive to a wrapper type.
215     *
216     * @param check must not be null
217     * @return the wrapper type if the given type represents a primitive, the given type otherwise.
218     */
219    static Class<?> resolveWrapperTypeForPrimitive(final Class<?> check) {
220        if (!check.isPrimitive()) {
221            return check;
222        }
223        switch (check.getName()) {
224        case "boolean": 
225            return Boolean.class;
226        case "byte": 
227            return Byte.class;
228        case "char": 
229            return Character.class;
230        case "short": 
231            return Short.class;
232        case "int": 
233            return Integer.class;
234        case "long": 
235            return Long.class;
236        case "double": 
237            return Double.class;
238        case "float": 
239            return Float.class;
240        default: 
241            log.warn("Unable to determine wrapper type for \'{}\', ", check);
242            return check;
243        }
244    }
245
246    /**
247     * Determines the modifier methods of a given class for a property. A modifier method is defined
248     * as being a public not static single-argument method that is prefixed with either "set" or
249     * consists of the ropertyName only. This
250     * will implicitly return all possible setter or builder methods, e.g.
251     * {@code setPropertyName(String name)}, {@code propertyName(String name)} and
252     * {@code setPropertyName(Collection<String> name)} will all be part of
253     * the result.
254     *
255     * @param clazz to be checked
256     * @param propertyName to be checked, must not be null
257     * @return the found modifier-methods or an empty {@link Collection} if none could be found
258     */
259    public static Collection<Method> retrieveWriteMethodCandidates(final Class<?> clazz, final String propertyName) {
260        requireNotEmpty(propertyName);
261        final CollectionBuilder<Method> builder = new CollectionBuilder<>();
262        for (final Method method : retrievePublicObjectMethods(clazz)) {
263            if (1 == method.getParameterCount()) {
264                final String name = method.getName();
265                if (propertyName.equals(name)) {
266                    log.debug("Returning found method \'{}\' on class \'{}\'", name, clazz);
267                    builder.add(method);
268                }
269                if (name.startsWith("set") && computePropertyNameFromMethodName(name).equalsIgnoreCase(propertyName)) {
270                    log.debug("Returning found method \'{}\' on class \'{}\'", name, clazz);
271                    builder.add(method);
272                }
273            } else {
274                log.trace(IGNORING_METHOD_ON_CLASS, method.getName(), clazz);
275            }
276        }
277        return builder.toImmutableList();
278    }
279
280    /**
281     * Retrieves the access-method for a given property Name. See
282     * {@link #retrieveAccessMethods(Class)} for the
283     * definition of an access-method
284     *
285     * @param clazz must not be null
286     * @param propertyName to be accessed
287     * @return {@link Optional#empty()} in case no method could be found, an {@link Optional} with
288     *         the found method
289     *         otherwise.
290     */
291    public static Optional<Method> retrieveAccessMethod(final Class<?> clazz, final String propertyName) {
292        requireNotEmpty(propertyName);
293        for (final Method method : retrieveAccessMethods(clazz)) {
294            if (computePropertyNameFromMethodName(method.getName()).equalsIgnoreCase(propertyName)) {
295                return Optional.of(method);
296            }
297        }
298        return Optional.empty();
299    }
300
301    /**
302     * Helper method that extract the property-name from a given accessor-method name.
303     *
304     * @param methodName must not be null nor empty
305     * @return the possible attribute name of a given method-name, e.g. it return 'name' for
306     *         getName/setName/isName. If
307     *         none of the prefixes 'get', 'set', 'is' is found it returns the passed String.
308     */
309    public static String computePropertyNameFromMethodName(final String methodName) {
310        requireNotEmpty(methodName);
311        if (methodName.startsWith("get") || methodName.startsWith("set")) {
312            if (methodName.length() > 3) {
313                return methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
314            } else {
315                log.debug("Name to short for extracting attributeName \'{}\'", methodName);
316            }
317        }
318        if (methodName.startsWith("is")) {
319            if (methodName.length() > 2) {
320                return methodName.substring(2, 3).toLowerCase() + methodName.substring(3);
321            } else {
322                log.debug("Name to short for extracting attributeName \'{}\'", methodName);
323            }
324        }
325        return methodName;
326    }
327
328    /**
329     * Helper class for extracting <em>all</em> annotations of a given class including from their
330     * ancestors.
331     *
332     * @param <A> the concrete annotation type
333     * @param annotatedType the (possibly) annotated type. If it is null or
334     *            {@link Object#getClass()} it will return an
335     *            empty list
336     * @param annotation the annotation to be extracted, must not be null
337     * @return an immutable List with all annotations found at the given object or one of its
338     *         ancestors. May be empty
339     *         but never null
340     */
341    public static <A extends Annotation> List<A> extractAllAnnotations(final Class<?> annotatedType, final Class<A> annotation) {
342        if (null == annotatedType || Object.class.equals(annotatedType.getClass())) {
343            return Collections.emptyList();
344        }
345        final CollectionBuilder<A> builder = new CollectionBuilder<>();
346        builder.add(annotatedType.getAnnotationsByType(annotation));
347        builder.add(extractAllAnnotations(annotatedType.getSuperclass(), annotation));
348        return builder.toImmutableList();
349    }
350
351    /**
352     * Helper class for extracting an annotation of a given class including from their ancestors.
353     *
354     * @param <A> the concrete annotation type
355     * @param annotatedType the (possibly) annotated type. If it is null or
356     *            {@link Object#getClass()} {@link
357     *            Optional#empty()}
358     * @param annotation the annotation to be extracted, must not be null
359     * @return an {@link Optional} on the annotated Object if the annotation can be found. In case
360     *         the annotation is found multiple times the first element will be returned.
361     */
362    public static <A extends Annotation> Optional<A> extractAnnotation(final Class<?> annotatedType, final Class<A> annotation) {
363        requireNonNull(annotation);
364        final List<A> extracted = extractAllAnnotations(annotatedType, annotation);
365        if (extracted.isEmpty()) {
366            return Optional.empty();
367        }
368        return Optional.of(extracted.iterator().next());
369    }
370
371    // owolff: The unchecked casting is necessary
372    /**
373     * Extracts the first generic type argument for the given type.
374     *
375     * @param <T> identifying the type to be looked for
376     * @param typeToBeExtractedFrom must not be null
377     * @return an {@link Optional} of the KeyStoreType-Argument of the given class.
378     * @throws IllegalArgumentException in case the given type does not represent a generic.
379     */
380    @SuppressWarnings("unchecked")
381    public static <T> Class<T> extractFirstGenericTypeArgument(final Class<?> typeToBeExtractedFrom) {
382        final ParameterizedType parameterizedType = extractParameterizedType(typeToBeExtractedFrom).orElseThrow(() -> new IllegalArgumentException("Given type defines no generic KeyStoreType: " + typeToBeExtractedFrom));
383        requireNotEmpty(parameterizedType.getActualTypeArguments(), "No type argument found for " + typeToBeExtractedFrom.getName());
384        final Class<?> firstType = extractGenericTypeCovariantly(parameterizedType.getActualTypeArguments()[0]).orElseThrow(() -> new IllegalArgumentException("Unable to determine genric type for " + typeToBeExtractedFrom));
385        try {
386            return (Class<T>) firstType;
387        } catch (final ClassCastException e) {
388            throw new IllegalArgumentException("No type argument can be extracted from " + typeToBeExtractedFrom.getName(), e);
389        }
390    }
391
392    /**
393     * @param type to be extracted from
394     * @return if applicable the actual type argument for the given type. If the type represents
395     *         already a {@link Class}
396     *         it will be returned directly. Otherwise, the super-type will be checked by calling
397     *         the superclass
398     */
399    @SuppressWarnings("rawtypes")
400    public static Optional<Class<?>> extractGenericTypeCovariantly(final Type type) {
401        if (null == type) {
402            log.trace("No KeyStoreType given, returning empty");
403            return Optional.empty();
404        }
405        if (type instanceof Class) {
406            log.debug("Found actual class returning as result {}", type);
407            return Optional.of((Class) type);
408        }
409        if (type instanceof ParameterizedType) {
410            log.debug("found Parameterized type, for {}, calling recursively", type);
411            return extractGenericTypeCovariantly(((ParameterizedType) type).getRawType());
412        }
413        log.warn("Unable to determines generic-type for {}", type);
414        return Optional.empty();
415    }
416
417    /**
418     * Extracts a {@link ParameterizedType} view for the given type
419     *
420     * @param typeToBeExtractedFrom must not be null
421     * @return an {@link Optional} of the {@link ParameterizedType} view of the given class.
422     */
423    public static Optional<ParameterizedType> extractParameterizedType(final Class<?> typeToBeExtractedFrom) {
424        log.debug("Extracting ParameterizedType from {}", typeToBeExtractedFrom);
425        if (null == typeToBeExtractedFrom) {
426            return Optional.empty();
427        }
428        if (Object.class.equals(typeToBeExtractedFrom)) {
429            log.debug("java.lang.Object is not a ParameterizedType");
430            return Optional.empty();
431        }
432        final Type genericSuperclass = typeToBeExtractedFrom.getGenericSuperclass();
433        if (genericSuperclass instanceof ParameterizedType) {
434            return Optional.of((ParameterizedType) genericSuperclass);
435        }
436        // Check the tree
437        return extractParameterizedType(typeToBeExtractedFrom.getSuperclass());
438    }
439
440    /**
441     * Returns a proxy instance that implements {@code interfaceType} by dispatching method
442     * invocations to {@code handler}. The class loader of {@code interfaceType} will be used to
443     * define the proxy class. To implement multiple
444     * interfaces or specify a class loader, use Proxy#newProxyInstance(Class, Constructor,
445     * InvocationHandler).
446     *
447     * @param interfaceType must not be null
448     * @param handler the invocation handler
449     * @param <T> the target type of the proxy
450     * @return the created Proxy-instance
451     * @throws IllegalArgumentException if {@code interfaceType} does not specify the type of Java
452     *             interface
453     * @author https://github.com/google/guava/blob/master/guava/src/com/google/common/reflect/Reflection.java
454     */
455    public static <T> T newProxy(final Class<T> interfaceType, final InvocationHandler handler) {
456        requireNonNull(handler);
457        Preconditions.checkArgument(interfaceType.isInterface(), "%s is not an interface", interfaceType);
458        final Object object = Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class<?>[] {interfaceType}, handler);
459        return interfaceType.cast(object);
460    }
461
462    /**
463     * Try to detect class from call stack which was the previous, before the marker class name
464     *
465     * @param markerClasses class names which could be used as marker before the real caller name.
466     *            Collection must not
467     *            be {@code null}.
468     * @return option of detected caller class name
469     */
470    public static Optional<String> findCaller(final Collection<String> markerClasses) {
471        final Optional<StackTraceElement> callerElement = findCallerElement(null, markerClasses);
472        return callerElement.map(StackTraceElement::getClassName);
473    }
474
475    /**
476     * Tries to detect class from call stack which was the previous, before the marker class name
477     *
478     * @param throwable is an optional parameter, will be used to access the stack
479     * @param markerClasses class names which could be used as marker before the real caller name.
480     *            Collection must not be {@code null}.
481     * @return option of detected caller class name
482     */
483    public static Optional<StackTraceElement> findCallerElement(final Throwable throwable, final Collection<String> markerClasses) {
484        Objects.requireNonNull(markerClasses, "Marker class names are missing");
485        final StackTraceElement[] stackTraceElements;
486        if (null == throwable) {
487            stackTraceElements = Thread.currentThread().getStackTrace();
488        } else {
489            stackTraceElements = throwable.getStackTrace();
490        }
491        if (null == stackTraceElements || stackTraceElements.length < 5) {
492            return Optional.empty();
493        }
494        for (int index = 2; index < stackTraceElements.length; index++) {
495            final StackTraceElement element = stackTraceElements[index];
496            if (markerClasses.contains(element.getClassName())) {
497                if (stackTraceElements.length > index + 1) {
498                    return Optional.of(stackTraceElements[index + 1]);
499                } else {
500                    return Optional.empty();
501                }
502            }
503        }
504        return Optional.empty();
505    }
506
507    @java.lang.SuppressWarnings("all")
508    @lombok.Generated
509    private MoreReflection() {
510        throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated");
511    }
512}