Class ReflectionUtil

java.lang.Object
com.ultikits.ultitools.utils.ReflectionUtil

public final class ReflectionUtil extends Object
Reflection utility class.

Replaces hutool's ReflectUtil / AnnotationUtil.

Since:
6.2.0
Author:
wisdomme
  • Constructor Details

    • ReflectionUtil

      private ReflectionUtil()
  • Method Details

    • getAllFields

      public static List<Field> getAllFields(Class<?> clazz)
      Gets all fields of a class (including superclasses).
      Parameters:
      clazz - the class
      Returns:
      the field list
    • getFields

      public static Field[] getFields(Class<?> clazz)
      Gets all fields of a class (including superclasses).
      Parameters:
      clazz - the class
      Returns:
      the field array
    • getFieldValue

      public static Object getFieldValue(Object obj, Field field)
      Gets a field's value.
      Parameters:
      obj - the object
      field - the field
      Returns:
      the field value
    • getFieldValue

      public static Object getFieldValue(Object obj, String fieldName)
      Gets a field's value.
      Parameters:
      obj - the object
      fieldName - the field name
      Returns:
      the field value
    • setFieldValue

      public static void setFieldValue(Object obj, Field field, Object value)
      Sets a field's value.
      Parameters:
      obj - the object
      field - the field
      value - the value
    • setFieldValue

      public static void setFieldValue(Object obj, String fieldName, Object value)
      Sets a field's value.
      Parameters:
      obj - the object
      fieldName - the field name
      value - the value
    • getField

      public static Field getField(Class<?> clazz, String fieldName)
      Gets the specified field.
      Parameters:
      clazz - the class
      fieldName - the field name
      Returns:
      the field, or null if not found
    • newInstance

      public static <T> T newInstance(Class<T> clazz)
      Creates an instance (using the no-arg constructor).
      Type Parameters:
      T - the type
      Parameters:
      clazz - the class
      Returns:
      the instance
    • newInstance

      public static <T> T newInstance(Class<T> clazz, Object... params)
      Creates an instance (using a parameterized constructor).
      Type Parameters:
      T - the type
      Parameters:
      clazz - the class
      params - the constructor parameters
      Returns:
      the instance
    • isAssignable

      private static boolean isAssignable(Class<?> target, Class<?> source)
      Whether the type is assignable.
    • getPrimitiveWrapper

      private static Class<?> getPrimitiveWrapper(Class<?> primitive)
      Gets the wrapper class corresponding to a primitive type.
    • newInstanceIfPossible

      public static <T> T newInstanceIfPossible(Class<T> clazz)
      Creates an instance (using the no-arg constructor, private constructors included).
      Type Parameters:
      T - the type
      Parameters:
      clazz - the class
      Returns:
      the instance
    • getAnnotation

      public static <A extends Annotation> A getAnnotation(Class<?> clazz, Class<A> annotationClass)
      Gets an annotation on a class.
      Type Parameters:
      A - the annotation type
      Parameters:
      clazz - the class
      annotationClass - the annotation type
      Returns:
      the annotation instance, or null if absent
    • getAnnotation

      public static <A extends Annotation> A getAnnotation(Field field, Class<A> annotationClass)
      Gets an annotation on a field.
      Type Parameters:
      A - the annotation type
      Parameters:
      field - the field
      annotationClass - the annotation type
      Returns:
      the annotation instance, or null if absent
    • hasAnnotation

      public static boolean hasAnnotation(Class<?> clazz, Class<? extends Annotation> annotationClass)
      Whether a class carries the specified annotation.
      Parameters:
      clazz - the class
      annotationClass - the annotation type
      Returns:
      whether the annotation is present
    • hasAnnotation

      public static boolean hasAnnotation(Field field, Class<? extends Annotation> annotationClass)
      Whether a field carries the specified annotation.
      Parameters:
      field - the field
      annotationClass - the annotation type
      Returns:
      whether the annotation is present
    • resolveMethodOrClassAnnotation

      public static <A extends Annotation> A resolveMethodOrClassAnnotation(Method method, Class<A> annotationType)
      Resolves annotationType for method, preferring a declaration on the method itself and falling back to one on the method's declaring class -- most-derived wins, the same precedence SenderTypeValidator already applies for @CmdTarget inside this same validator chain, and the precedent Spring's @Transactional and Spring Security's @PreAuthorize both document for a class-vs-method annotation conflict.

      Only the method's OWN declaring class is consulted (via method.getDeclaringClass().getAnnotation(...)) -- not the class's own ancestors, since neither @CmdCD nor @UsageLimit is @Inherited. This matches getAllMethods(Class)'s own hierarchy walk: a method inherited from a superclass without being overridden is returned with that superclass already as its getDeclaringClass(), so a class-level annotation on that superclass is still found without any extra ancestor walk here.

      Convenience delegate to resolveMethodOrClassAnnotation(Method, Class, Class) with executorClass as null -- kept for callers (and existing tests) that only ever had a Method to resolve against, not the dispatching executor's concrete class. Prefer the 3-argument overload when the concrete executor class is known: it also checks that class's own declaration, closing WR-02 (05-REVIEW.md) -- a class-level annotation declared on a concrete executor SUBCLASS, inherited by an unoverridden @CmdMapping method whose getDeclaringClass() is an ancestor, is invisible to this 2-argument form.

      Type Parameters:
      A - the annotation type
      Parameters:
      method - the matched command mapping method
      annotationType - the annotation type to resolve
      Returns:
      the method-level annotation if present, otherwise the declaring class's annotation, or null if neither declares it
      Since:
      6.3.0
    • resolveMethodOrClassAnnotation

      public static <A extends Annotation> A resolveMethodOrClassAnnotation(Method method, @Nullable Class<?> executorClass, Class<A> annotationType)
      WR-02 (05-REVIEW.md): resolves annotationType in THREE steps, most-derived-first: (1) method's own declaration, (2) executorClass's own class-level declaration -- the CONCRETE, most-derived executor class actually loaded, the SAME class PluginManager's load-time gate inspects via executor.getClass() -- (3) method.getDeclaringClass()'s class-level declaration, for a shared abstract base that declares BOTH the mapping method and the annotation together.

      Step (2) is what resolveMethodOrClassAnnotation(Method, Class) (the 2-arg overload) cannot do: for an inherited, unoverridden @CmdMapping method, method.getDeclaringClass() is whatever ANCESTOR first declared it -- never the concrete subclass, since neither @CmdCD nor @UsageLimit is @Inherited. A class-level annotation declared ONLY on such a subclass previously passed PluginManager's load-time gate (which correctly checks executor.getClass()) but was never found by CooldownValidator/UsageLockValidator at runtime -- the gate's "fine to load" was a false assurance. Step (3) is kept as a further fallback so a shared abstract base that declares both the mapping and the annotation together -- the pre-WR-02 working case -- is unaffected.

      executorClass is checked as a DIRECT declaration only (no ancestor walk of its own): if the concrete class itself does not carry the annotation, step (3) already covers the "declared on an ancestor of the mapping method" case, and there is no third distinct class to consult for the same annotation type.

      Type Parameters:
      A - the annotation type
      Parameters:
      method - the matched command mapping method
      executorClass - the concrete BaseCommandExecutor class dispatching this command -- the SAME class PluginManager's load-time gate inspects -- or null to fall back to the pre-WR-02, declaring-class-only resolution
      annotationType - the annotation type to resolve
      Returns:
      the resolved annotation, or null if none of method, executorClass, or the method's declaring class carries one
      Since:
      6.3.0
    • invoke

      public static <T> T invoke(Object obj, Method method, Object... args)
      Invokes a method.
      Type Parameters:
      T - the return type
      Parameters:
      obj - the object
      method - the method
      args - the arguments
      Returns:
      the method's return value
    • getMethods

      public static Method[] getMethods(Class<?> clazz, Predicate<Method> filter)
      Gets the methods that satisfy a condition.

      Delegates to getAllMethods(Class) so callers get the same de-duplicated, bridge/synthetic-free view of the hierarchy - a raw getDeclaredMethods() walk here would double-count an overridden method (or, once a bean is AOP-proxied, could return both the proxy's intercepted override and the original method as separate hits for the same logical method).

      Parameters:
      clazz - the class
      filter - the filter condition
      Returns:
      the method array
    • getMethods

      public static Method[] getMethods(Class<?> clazz)
      Gets all methods of a class (including superclasses).
      Parameters:
      clazz - the class
      Returns:
      the method array
    • getAllMethods

      public static List<Method> getAllMethods(Class<?> clazz)
      Collects every method visible on the given class, walking the hierarchy and keeping only the most specific declaration of each overridable method.

      Class.getDeclaredMethods() returns only the methods a class declares itself, and Class.getMethods() returns only public ones. Neither is right for annotation scanning on a bean that may be an AOP proxy: the proxy declares overrides only for intercepted methods, so scanning it directly loses every annotation on the rest (issue #190). Walking the hierarchy recovers them, and collapsing overrides keeps a callback from firing once per level when an override repeats its parent's annotation.

      Declarations are grouped into slots using overrides(Method, Method), and each slot contributes exactly one entry: the most derived declaration, which is the one whose annotations a scanner should see. A slot remembers every declaration folded into it, not just that representative, because overriding is transitive (JLS 8.4.8.1): in x.A.m() (package-private) → x.B.m() (public) → y.C.m() (public), the leaf does not directly override the root - different packages - but does so through the middle declaration, and all three are one method. Testing a candidate only against the surviving representative would keep the root as a spurious second entry.

      private and static methods never participate in overriding, so a same-named, same-parameter declaration at another level always starts its own slot and both survive.

      Bridge and synthetic methods are skipped: they carry no author-written annotations and, being compiler artifacts, are never the method a scanner means to find.

      Object's own methods are excluded.

      Parameters:
      clazz - the class to scan, may be null
      Returns:
      the methods, subclass overrides first; empty if clazz is null
    • findSlotOverriding

      private static List<Method> findSlotOverriding(List<List<Method>> bucket, Method candidate)
      Finds the slot in bucket whose declarations override candidate, or null if none does.

      Every declaration in a slot is tested, not just its representative - see getAllMethods(Class) for why transitivity makes that necessary.

      Parameters:
      bucket - the slots sharing candidate's name and parameter count
      candidate - the declaration being placed
      Returns:
      the slot candidate belongs to, or null if it opens a new one
    • overrides

      public static boolean overrides(Method sub, Method sup)
      Whether sub overrides sup per JLS 8.4.8.1.

      Overriding is directional and is not an equivalence relation, so it cannot be expressed as equality of any symmetric key - which is why this predicate exists rather than a comparison against a symmetric signature key. All of the following must hold:

      1. same name and same parameter types, in order;
      2. sup's declaring class is a proper supertype of sub's;
      3. neither declaration is private or static - private methods are dispatched with invokespecial and are not even inherited, and static methods are hidden rather than overridden;
      4. if sup is package-private, the two declaring classes are in the same package. A public or protected sup carries no package condition.
      The first condition is signature equality (JLS 8.4.2), not the subsignature relation that JLS 8.4.8.1 actually requires. Parameter types are compared as erased Class objects, so a generic declaration and an override that matches only its erasure are treated as distinct methods, even though a subsignature comparison - and javac - accept them as one:
      
       class GenBase<T>                        { public void take(T t) { } }   // erases to take(Object)
       class GenChild extends GenBase<String>  { @Override public void take(String t) { } }
       
      overrides(GenChild.take, GenBase.take) returns false here, and the two are reported as distinct methods even though javac accepts the @Override.

      There is deliberately no condition on sub's own access: Java permits an override to widen access, and a package-private method widened to public by a same-package subclass is a genuine override.

      Public so that FinalContractValidator shares this one implementation instead of re-deriving the rule - two independent copies are how the two consumers drifted apart in the first place (issue #190).

      Packages are compared by name, matching how the rest of this class treats them; two same-named packages defined by different class loaders are distinct runtime packages to the JVM, a distinction this check does not make.

      Parameters:
      sub - the potentially overriding declaration, may be null
      sup - the potentially overridden declaration, may be null
      Returns:
      true if sub overrides sup; false if either is null
    • isPackagePrivate

      public static boolean isPackagePrivate(int modifiers)
      True for default (package-private) access: neither public, protected, nor private.

      Public so that consumers deciding the same question share this implementation. See the note on overrides(Method, Method) for why a second copy is a hazard rather than a convenience.

      Parameters:
      modifiers - the modifiers to test, from Member#getModifiers()
      Returns:
      true if the modifiers describe default access
    • packageNameOf

      public static String packageNameOf(Class<?> clazz)
      The class's package name, or the empty string for the unnamed package.

      Public for the same reason as isPackagePrivate(int): package-private access is decided here and by AopEligibility, and the two must not answer differently.

      Parameters:
      clazz - the class to inspect
      Returns:
      the package name, or the empty string