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 without a concrete executor class: the method's own declaration, then the class-level declaration on the class that declared the mapping in source. Most-derived wins, the same precedence SenderTypeValidator applies for @CmdTarget, and the precedent Spring's @Transactional and Spring Security's @PreAuthorize both document for a class-versus-method conflict.

      Convenience delegate to resolveMethodOrClassAnnotation(Method, Class, Class) with executorClass as null; that overload's javadoc is the single statement of the rule, including how a ProxyFactory-generated override is mapped back to the declaration its author wrote (case 36). Prefer the 3-argument overload whenever the dispatching executor's concrete class is known: only it sees a class-level annotation declared on a concrete subclass that inherits the mapping unoverridden (WR-02).

      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 class-level annotation of the class that declared the mapping in source, 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)
      Resolves annotationType for a command mapping in three steps, most-derived first:
      1. method's own declaration;
      2. executorClass's own class-level declaration -- the concrete executor class actually dispatching, the same class PluginManager's load-time gate inspects through executor.getClass() (WR-02);
      3. the class-level declaration of the class that declared the mapping in source.
      Neither @CmdCD nor @UsageLimit is @Inherited, so no step walks a class's ancestors. Step 2 is what lets a class-level annotation on a concrete subclass govern a mapping it inherits unoverridden; step 3 is what lets a shared abstract base that declares both the mapping and the annotation govern every inheritor.

      Generated proxies (case 36). When method is an override generated by ProxyFactory (its declaring class is a proxy, by ProxyFactory.isProxyClass), step 3 does not read the generated class: it reads the class that declared the overridden method in source -- the most derived non-proxy declaration that the override overrides, by overrides(Method, Method). The proxy copies only the proxied class's own type annotations and the intercepted methods' own annotations, so reading the generated class here would lose a base class's class-level annotation for exactly the mappings that carry an AOP annotation. Steps 1 and 2 are unchanged on a proxy (the copied method annotations and the copied type annotations of the proxied class), so the precedence is the same for proxied and ordinary executors, for inherited and for overridden mappings alike. An override declared in the concrete class is governed by that class, not by its base, with or without a proxy.

      Consumers. CooldownValidator (whether a mapping is bound, its seconds, and the unresolved-binding failure), UsageLockValidator (the lock scope) and PluginManager's config-binding resolution at module load all call this method, so load-time resolution and dispatch-time enforcement share this one answer.

      Type Parameters:
      A - the annotation type
      Parameters:
      method - the matched command mapping method; may be a ProxyFactory override
      executorClass - the concrete BaseCommandExecutor class dispatching this command -- the same class PluginManager's load-time gate inspects -- or null to skip step 2
      annotationType - the annotation type to resolve
      Returns:
      the resolved annotation, or null if none of the three steps finds one
      Since:
      6.3.0
    • sourceDeclarationOf

      private static Method sourceDeclarationOf(Method method)
      The declaration a module author wrote for method: for an override generated by ProxyFactory, the most derived non-proxy declaration in the proxy's superclass chain that the override overrides; otherwise method itself.

      Proxy identity is read from ProxyFactory.isProxyClass, its one source. Bridge and synthetic methods are skipped, as in getAllMethods(Class). Nothing is cached: a static map keyed by Method or Class would pin module class loaders after the module unloads, and the walk only runs for a method declared by a proxy.

      Parameters:
      method - the method to map back, never null
      Returns:
      the author's declaration, or method when it is not a proxy override or no declaration it overrides is found
    • 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
    • getEffectiveCommandMappings

      @Internal public static Map<String,Method> getEffectiveCommandMappings(@Nullable Class<?> clazz)
      The command mappings of clazz, keyed by @CmdMapping format: the one statement of which methods of a command executor are mappings.

      The rule: walk getAllMethods(Class) -- subclass first, each overridden method collapsed to its most derived declaration (#190, so an AOP proxy, which declares overrides only for intercepted methods, keeps every mapping) -- and keep each method carrying @CmdMapping. The first method for a format wins, so a subclass's own mapping shadows a base mapping that uses the same format. Inherited, unoverridden mappings are included; an override that does not repeat @CmdMapping is not a mapping, because the most derived declaration is the one consulted.

      BaseCommandExecutor builds its dispatch table from this map, and CmdTargetComposition checks exactly these mappings at load (case 57), so the two cannot disagree about which inherited mappings exist. On a proxy class an intercepted mapping appears as the proxy's override; resolveMethodOrClassAnnotation(Method, Class, Class) maps such an override back to its author's declaration for class-level metadata (case 36).

      Parameters:
      clazz - the command class to scan, may be null
      Returns:
      an unmodifiable map from format to mapping method, in discovery order; empty if clazz is null
      Since:
      6.3.0
    • 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