001// Generated by delombok at Sat Feb 25 17:58:34 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 var 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 var 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            }
181            log.trace(IGNORING_METHOD_ON_CLASS, method.getName(), clazz);
182        }
183        return Optional.empty();
184    }
185
186    /**
187     * @param assignableSource the type to be checked
188     * @param queryType to be checked for
189     * @return boolean indicating whether the given parameter, identified by their class attributes
190     *         match
191     */
192    public static boolean checkWhetherParameterIsAssignable(final Class<?> assignableSource, final Class<?> queryType) {
193        requireNonNull(assignableSource);
194        requireNonNull(queryType);
195        if (assignableSource.equals(queryType)) {
196            log.trace("Parameter-type matches exactly \'%s\'", assignableSource);
197            return true;
198        }
199        if (assignableSource.isAssignableFrom(queryType)) {
200            log.trace("Parameter \'%s\' is assignable from \'%s\'", assignableSource, queryType);
201            return true;
202        }
203        final Class<?> boxedSource = resolveWrapperTypeForPrimitive(assignableSource);
204        final Class<?> boxedQuery = resolveWrapperTypeForPrimitive(queryType);
205        if (boxedSource.equals(boxedQuery)) {
206            log.trace("Parameter-type matches exactly after autoboxing \'%s\'", assignableSource);
207            return true;
208        }
209        return boxedSource.isAssignableFrom(boxedQuery);
210    }
211
212    /**
213     * Helper class for converting a primitive to a wrapper type.
214     *
215     * @param check must not be null
216     * @return the wrapper type if the given type represents a primitive, the given type otherwise.
217     */
218    static Class<?> resolveWrapperTypeForPrimitive(final Class<?> check) {
219        if (!check.isPrimitive()) {
220            return check;
221        }
222        switch (check.getName()) {
223        case "boolean": 
224            return Boolean.class;
225        case "byte": 
226            return Byte.class;
227        case "char": 
228            return Character.class;
229        case "short": 
230            return Short.class;
231        case "int": 
232            return Integer.class;
233        case "long": 
234            return Long.class;
235        case "double": 
236            return Double.class;
237        case "float": 
238            return Float.class;
239        default: 
240            log.warn("Unable to determine wrapper type for \'{}\', ", check);
241            return check;
242        }
243    }
244
245    /**
246     * Determines the modifier methods of a given class for a property. A modifier method is defined
247     * as being a public not static single-argument method that is prefixed with either "set" or
248     * consists of the ropertyName only. This
249     * will implicitly return all possible setter or builder methods, e.g.
250     * {@code setPropertyName(String name)}, {@code propertyName(String name)} and
251     * {@code setPropertyName(Collection<String> name)} will all be part of
252     * the result.
253     *
254     * @param clazz to be checked
255     * @param propertyName to be checked, must not be null
256     * @return the found modifier-methods or an empty {@link Collection} if none could be found
257     */
258    public static Collection<Method> retrieveWriteMethodCandidates(final Class<?> clazz, final String propertyName) {
259        requireNotEmpty(propertyName);
260        final var builder = new CollectionBuilder<Method>();
261        for (final Method method : retrievePublicObjectMethods(clazz)) {
262            if (1 == method.getParameterCount()) {
263                final var name = method.getName();
264                if (propertyName.equals(name)) {
265                    log.debug("Returning found method \'{}\' on class \'{}\'", name, clazz);
266                    builder.add(method);
267                }
268                if (name.startsWith("set") && computePropertyNameFromMethodName(name).equalsIgnoreCase(propertyName)) {
269                    log.debug("Returning found method \'{}\' on class \'{}\'", name, clazz);
270                    builder.add(method);
271                }
272            } else {
273                log.trace(IGNORING_METHOD_ON_CLASS, method.getName(), clazz);
274            }
275        }
276        return builder.toImmutableList();
277    }
278
279    /**
280     * Retrieves the access-method for a given property Name. See
281     * {@link #retrieveAccessMethods(Class)} for the
282     * definition of an access-method
283     *
284     * @param clazz must not be null
285     * @param propertyName to be accessed
286     * @return {@link Optional#empty()} in case no method could be found, an {@link Optional} with
287     *         the found method
288     *         otherwise.
289     */
290    public static Optional<Method> retrieveAccessMethod(final Class<?> clazz, final String propertyName) {
291        requireNotEmpty(propertyName);
292        for (final Method method : retrieveAccessMethods(clazz)) {
293            if (computePropertyNameFromMethodName(method.getName()).equalsIgnoreCase(propertyName)) {
294                return Optional.of(method);
295            }
296        }
297        return Optional.empty();
298    }
299
300    /**
301     * Helper method that extract the property-name from a given accessor-method name.
302     *
303     * @param methodName must not be null nor empty
304     * @return the possible attribute name of a given method-name, e.g. it return 'name' for
305     *         getName/setName/isName. If
306     *         none of the prefixes 'get', 'set', 'is' is found it returns the passed String.
307     */
308    public static String computePropertyNameFromMethodName(final String methodName) {
309        requireNotEmpty(methodName);
310        if (methodName.startsWith("get") || methodName.startsWith("set")) {
311            if (methodName.length() > 3) {
312                return methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
313            }
314            log.debug("Name to short for extracting attributeName \'{}\'", methodName);
315        }
316        if (methodName.startsWith("is")) {
317            if (methodName.length() > 2) {
318                return methodName.substring(2, 3).toLowerCase() + methodName.substring(3);
319            }
320            log.debug("Name to short for extracting attributeName \'{}\'", methodName);
321        }
322        return methodName;
323    }
324
325    /**
326     * Helper class for extracting <em>all</em> annotations of a given class including from their
327     * ancestors.
328     *
329     * @param <A> the concrete annotation type
330     * @param annotatedType the (possibly) annotated type. If it is null or
331     *            {@link Object#getClass()} it will return an
332     *            empty list
333     * @param annotation the annotation to be extracted, must not be null
334     * @return an immutable List with all annotations found at the given object or one of its
335     *         ancestors. May be empty
336     *         but never null
337     */
338    public static <A extends Annotation> List<A> extractAllAnnotations(final Class<?> annotatedType, final Class<A> annotation) {
339        if (null == annotatedType || Object.class.equals(annotatedType.getClass())) {
340            return Collections.emptyList();
341        }
342        final var builder = new CollectionBuilder<A>();
343        builder.add(annotatedType.getAnnotationsByType(annotation));
344        builder.add(extractAllAnnotations(annotatedType.getSuperclass(), annotation));
345        return builder.toImmutableList();
346    }
347
348    /**
349     * Helper class for extracting an annotation of a given class including from their ancestors.
350     *
351     * @param <A> the concrete annotation type
352     * @param annotatedType the (possibly) annotated type. If it is null or
353     *            {@link Object#getClass()} {@link
354     *            Optional#empty()}
355     * @param annotation the annotation to be extracted, must not be null
356     * @return an {@link Optional} on the annotated Object if the annotation can be found. In case
357     *         the annotation is found multiple times the first element will be returned.
358     */
359    public static <A extends Annotation> Optional<A> extractAnnotation(final Class<?> annotatedType, final Class<A> annotation) {
360        requireNonNull(annotation);
361        final List<A> extracted = extractAllAnnotations(annotatedType, annotation);
362        if (extracted.isEmpty()) {
363            return Optional.empty();
364        }
365        return Optional.of(extracted.iterator().next());
366    }
367
368    // owolff: The unchecked casting is necessary
369    /**
370     * Extracts the first generic type argument for the given type.
371     *
372     * @param <T> identifying the type to be looked for
373     * @param typeToBeExtractedFrom must not be null
374     * @return an {@link Optional} of the KeyStoreType-Argument of the given class.
375     * @throws IllegalArgumentException in case the given type does not represent a generic.
376     */
377    @SuppressWarnings("unchecked")
378    public static <T> Class<T> extractFirstGenericTypeArgument(final Class<?> typeToBeExtractedFrom) {
379        final var parameterizedType = extractParameterizedType(typeToBeExtractedFrom).orElseThrow(() -> new IllegalArgumentException("Given type defines no generic KeyStoreType: " + typeToBeExtractedFrom));
380        requireNotEmpty(parameterizedType.getActualTypeArguments(), "No type argument found for " + typeToBeExtractedFrom.getName());
381        final Class<?> firstType = extractGenericTypeCovariantly(parameterizedType.getActualTypeArguments()[0]).orElseThrow(() -> new IllegalArgumentException("Unable to determine genric type for " + typeToBeExtractedFrom));
382        try {
383            return (Class<T>) firstType;
384        } catch (final ClassCastException e) {
385            throw new IllegalArgumentException("No type argument can be extracted from " + typeToBeExtractedFrom.getName(), e);
386        }
387    }
388
389    /**
390     * @param type to be extracted from
391     * @return if applicable the actual type argument for the given type. If the type represents
392     *         already a {@link Class}
393     *         it will be returned directly. Otherwise, the super-type will be checked by calling
394     *         the superclass
395     */
396    @SuppressWarnings("rawtypes")
397    public static Optional<Class<?>> extractGenericTypeCovariantly(final Type type) {
398        if (null == type) {
399            log.trace("No KeyStoreType given, returning empty");
400            return Optional.empty();
401        }
402        if (type instanceof Class) {
403            log.debug("Found actual class returning as result {}", type);
404            return Optional.of((Class) type);
405        }
406        if (type instanceof ParameterizedType) {
407            log.debug("found Parameterized type, for {}, calling recursively", type);
408            return extractGenericTypeCovariantly(((ParameterizedType) type).getRawType());
409        }
410        log.warn("Unable to determines generic-type for {}", type);
411        return Optional.empty();
412    }
413
414    /**
415     * Extracts a {@link ParameterizedType} view for the given type
416     *
417     * @param typeToBeExtractedFrom must not be null
418     * @return an {@link Optional} of the {@link ParameterizedType} view of the given class.
419     */
420    public static Optional<ParameterizedType> extractParameterizedType(final Class<?> typeToBeExtractedFrom) {
421        log.debug("Extracting ParameterizedType from {}", typeToBeExtractedFrom);
422        if (null == typeToBeExtractedFrom) {
423            return Optional.empty();
424        }
425        if (Object.class.equals(typeToBeExtractedFrom)) {
426            log.debug("java.lang.Object is not a ParameterizedType");
427            return Optional.empty();
428        }
429        final var genericSuperclass = typeToBeExtractedFrom.getGenericSuperclass();
430        if (genericSuperclass instanceof ParameterizedType) {
431            return Optional.of((ParameterizedType) genericSuperclass);
432        }
433        // Check the tree
434        return extractParameterizedType(typeToBeExtractedFrom.getSuperclass());
435    }
436
437    /**
438     * Returns a proxy instance that implements {@code interfaceType} by dispatching method
439     * invocations to {@code handler}. The class loader of {@code interfaceType} will be used to
440     * define the proxy class. To implement multiple
441     * interfaces or specify a class loader, use Proxy#newProxyInstance(Class, Constructor,
442     * InvocationHandler).
443     *
444     * @param interfaceType must not be null
445     * @param handler the invocation handler
446     * @param <T> the target type of the proxy
447     * @return the created Proxy-instance
448     * @throws IllegalArgumentException if {@code interfaceType} does not specify the type of Java
449     *             interface
450     * @author https://github.com/google/guava/blob/master/guava/src/com/google/common/reflect/Reflection.java
451     */
452    public static <T> T newProxy(final Class<T> interfaceType, final InvocationHandler handler) {
453        requireNonNull(handler);
454        Preconditions.checkArgument(interfaceType.isInterface(), "%s is not an interface", interfaceType);
455        final var object = Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class<?>[] {interfaceType}, handler);
456        return interfaceType.cast(object);
457    }
458
459    /**
460     * Try to detect class from call stack which was the previous, before the marker class name
461     *
462     * @param markerClasses class names which could be used as marker before the real caller name.
463     *            Collection must not
464     *            be {@code null}.
465     * @return option of detected caller class name
466     */
467    public static Optional<String> findCaller(final Collection<String> markerClasses) {
468        final var callerElement = findCallerElement(null, markerClasses);
469        return callerElement.map(StackTraceElement::getClassName);
470    }
471
472    /**
473     * Tries to detect class from call stack which was the previous, before the marker class name
474     *
475     * @param throwable is an optional parameter, will be used to access the stack
476     * @param markerClasses class names which could be used as marker before the real caller name.
477     *            Collection must not be {@code null}.
478     * @return option of detected caller class name
479     */
480    public static Optional<StackTraceElement> findCallerElement(final Throwable throwable, final Collection<String> markerClasses) {
481        Objects.requireNonNull(markerClasses, "Marker class names are missing");
482        final StackTraceElement[] stackTraceElements;
483        if (null == throwable) {
484            stackTraceElements = Thread.currentThread().getStackTrace();
485        } else {
486            stackTraceElements = throwable.getStackTrace();
487        }
488        if (null == stackTraceElements || stackTraceElements.length < 5) {
489            return Optional.empty();
490        }
491        for (var index = 2; index < stackTraceElements.length; index++) {
492            final var element = stackTraceElements[index];
493            if (markerClasses.contains(element.getClassName())) {
494                if (stackTraceElements.length > index + 1) {
495                    return Optional.of(stackTraceElements[index + 1]);
496                }
497                return Optional.empty();
498            }
499        }
500        return Optional.empty();
501    }
502
503    @java.lang.SuppressWarnings("all")
504    @lombok.Generated
505    private MoreReflection() {
506        throw new java.lang.UnsupportedOperationException("This is a utility class and cannot be instantiated");
507    }
508}