Skip to content
Luke Hutchison edited this page Dec 21, 2022 · 14 revisions

See also the ClassGraph API overview.

ClassGraph parses classfile-internal type signature and type descriptor strings (like "Ljava/lang/String;") into type signature objects ClassTypeSignature, MethodTypeSignature, and TypeSignature.

Contents

Type signature class hierarchy

The hierarchy of type signature classes reflects the structure of Java's internal type signature system.

Type signature class hierarchy

Example usage

The following methods can be used to get a TypeSignature (getTypeSignatureOrTypeDescriptor() is preferable to calling either getTypeSignature() or getTypeDescriptor(), since it will use the type signature, including generic information, if available, otherwise it will fall back to using the type descriptor, without generic information):

  • Generic class type signature: ClassInfo#getTypeSignature() -- returns a ClassTypeSignature for the type of the class, if the class is generic, otherwise returns null. (There is no method ClassInfo#getTypeDescriptor() -- classes don't have a type descriptor.)
  • Field type signature: FieldInfo#getTypeSignatureOrTypeDescriptor() -- returns a TypeSignature for the type of the field.
  • Method type signature:
    • Method result type signature: call MethodInfo#getTypeSignatureOrTypeDescriptor() to get a MethodTypeSignature for the type of the method, then MethodTypeSignature#getResultType() to get the result type of the method as a TypeSignature.
    • Method parameter type signatures: call MethodInfo#getParameterInfo() to get an array of MethodParameterInfo objects for the method parameters, then for each method parameter, call MethodParameterInfo#getTypeSignatureOrTypeDescriptor() to get a TypeSignature for the method parameter.

For example, to check the result type of a method:

try (ScanResult scanResult = new ClassGraph().enableAllInfo()
        .acceptPackages(packageName).scan()) {
    ClassInfo classInfo = scanResult.getClassInfo(className);
    MethodInfoList methodInfoList = classInfo.getMethodInfo(methodName);
    MethodInfo methodInfo = methodInfoList.get(0);  // First method named {methodName}
    MethodTypeSignature methodTypeSignature = methodInfo
            .getTypeSignatureOrTypeDescriptor();
    TypeSignature resultTypeSignature = methodTypeSignature.getResultType();
    if (resultTypeSignature instanceof ArrayTypeSignature) {
        ArrayTypeSignature arrayTypeSignature =
                (ArrayTypeSignature) resultTypeSignature;
        System.out.println("Method " + methodInfo.getName() + " returns a "
                + arrayTypeSignature.getNumDimensions() + "-dimensional array of "
                + arrayTypeSignature.getElementTypeSignature());
    } else if (resultTypeSignature instanceof BaseTypeSignature) {
        BaseTypeSignature baseTypeSignature =
                (BaseTypeSignature) resultTypeSignature;
        System.out.println("Method " + methodInfo.getName() + " returns "
                + baseTypeSignature.getTypeStr());
    } else if (resultTypeSignature instanceof ClassRefTypeSignature) {
        ClassRefTypeSignature classRefTypeSignature =
                (ClassRefTypeSignature) resultTypeSignature;
        System.out.println("Method " + methodInfo.getName() + " returns "
                + classRefTypeSignature.getClassInfo().getName());
    } else if (resultTypeSignature instanceof TypeVariableSignature) {
        TypeVariableSignature typeVariableSignature =
                (TypeVariableSignature) resultTypeSignature;
        // Attempt to resolve type variable
        TypeParameter typeParameter = typeVariableSignature.resolve();
        System.out.println("Method " + methodInfo.getName()
                + " returns type variable " + typeVariableSignature.getName()
                + ", which resolves to " + typeParameter);
    }
}

You probably have to cast a value of TypeSignature returned by the ClassGraph API into the correct subclass type (e.g. ClassRefTypeSignature) to get any useful information. For example:

public class TestReadingTypeArgs {
    static class A<X> {
    }

    static abstract class B {
        abstract A<Integer> a();
    }

    public static void main(String[] args) {
        try (ScanResult scanResult = new ClassGraph()
                .acceptPackages(TestReadingTypeArgs.class.getPackage().getName())
                .enableAllInfo().scan()) {
            ClassInfo bClass = scanResult.getClassInfo(B.class.getName());
            MethodInfo aMethodInfo = bClass.getMethodInfo("a").get(0);
            MethodTypeSignature aType = aMethodInfo.getTypeSignatureOrTypeDescriptor();
            TypeSignature aResultType = aType.getResultType();
            ClassRefTypeSignature aResultTypeConcrete = (ClassRefTypeSignature) aResultType;
            String aTypeBaseClassName = aResultTypeConcrete.getBaseClassName();
            List<TypeArgument> aTypeArgs = aResultTypeConcrete.getTypeArguments();
            TypeArgument aTypeArg0 = aTypeArgs.get(0);
            String aTypeArg0BaseClassName =
                    ((ClassRefTypeSignature) aTypeArg0.getTypeSignature()).getBaseClassName();
            System.out.println("Method a() returns type " + aTypeBaseClassName
                    + " with argument " + aTypeArg0BaseClassName);
        }
    }
}

This prints:

Method a() returns type TestReadingTypeArgs$A with argument java.lang.Integer

Note on resolving type variables

ClassGraph currently does not automatically resolve type variables into their concrete generic types, i.e. does not substitute type arguments into type parameters. This means you may unexpectedly get TypeVariableSignature for the type signature of a field, method, method parameter, etc., where you were expecting a concrete actual type.

ClassGraph has the method TypeVariableSignature#resolve() for resolving type variables, by first resolving the type variable against the containing method, and then against the containing class. However, Java's type system has a lot of complex quirks, and it is complex to correctly resolve type variables in the general case. Currently this resolve() method is somewhat simplistic. Pull requests to improve type resolution are welcome.

You may also load the class using ClassInfo#loadClass(), then use a library like gentyref / GeAnTyRef to find the concrete and generic type signature of the class.

Type signature classes

ClassTypeSignature

A type signature for a generic class, returned by ClassInfo#getTypeSignature().

  • .getTypeParameters returns the type parameters of the class as a List of [TypeParameter] objects.
  • .getSuperclassSignature() returns a ClassRefTypeSignature for the superclass of this class.
  • .getSuperinterfaceSignatures() returns a List of ClassRefTypeSignature objects for the interfaces implemented by this class.

MethodTypeSignature

A type signature for a method, returned by ClassInfo#getTypeSignatureOrTypeDescriptor().

  • .getTypeParameters returns the type parameters of the class as a List of TypeParameter objects.
  • .getResultType() returns a TypeSignature for the result type of the method.
  • .getThrowsSignatures() returns a List of ClassRefOrTypeVariableSignature objects for any exceptions or errors that can be thrown by the method.
  • To obtain method parameter type signatures, first call MethodInfo#getParameterInfo() to obtain an array of MethodParameterInfo objects, one per method parameter, and then call MethodParameterInfo#getTypeSignatureOrTypeDescriptor().
  • .getReceiverTypeAnnotationInfo() returns an AnnotationInfoList of any type annotations on an explicit receiver parameter of the method, if present, otherwise returns null.

TypeSignature

A type signature or type descriptor, representing the type of a field, the return type of a method, the type of a method parameter, etc. Subclasses are ReferenceTypeSignature and BaseTypeSignature.

All subclasses of TypeSignature include the following methods:

  • .getTypeAnnotationInfo() returns an AnnotationInfoList of any type annotations on the type signature, if present, otherwise returns null.
  • .toStringWithSimpleNames() returns a simpler rendering of the type signature than than TypeSignature#toString(), by using only the simple name of any classes or annotations in the type signature.

BaseTypeSignature

A type signature for a base type (a primitive type or void).

  • .getType() returns int.class, long.class, short.class, double.class, float.class, char.class, boolean.class, byte.class, or void.class.
  • .getTypeStr() retuns "int", "long", "short", "double", "float", "char", "boolean", "byte" or "void".

ReferenceTypeSignature

A type signature for a reference type. Subclasses are ClassRefOrTypeVariableSignature and ArrayTypeSignature.

ArrayTypeSignature

A type signature for an array type.

  • .getElementTypeSignature() returns the type of the innermost nested element type of the array (e.g. for an array of type int[][][], this returns a BaseTypeSignature representing int.class).
  • .getNumDimensions() returns the number of dimensions of the array.
  • .getNestedType() returns a TypeSignature for the array class with one dimension fewer, or the innermost element type if there are no nested array dimensions.
  • .loadClass() loads and returns the array class represented by the type signature (e.g. Point[][].class).
  • .loadElementClass() loads the class for the innermost nested element type of the array, and returns a Class<?> reference (e.g. returns Point.class for a type signature corresponding to Point[][]).
  • .getArrayClassInfo() returns an ArrayClassInfo for the array class.

ClassRefOrTypeVariableSignature

A type signature for a class reference or a type variable. Subclasses are ClassRefTypeSignature and TypeVariableSignature.

ClassRefTypeSignature

A type signature for a Class reference.

  • .getBaseClassName() returns the base name of the referenced class (without suffixes or type arguments).
  • .getFullyQualifiedClassName() returns the fully-qualified name of the referenced class (with suffixes, but without type arguments).
  • .getTypeArguments() returns the type arguments of the class reference as a List of TypeArgument objects.
  • .getSuffixes returns the class suffixes (for inner classes).
  • .getSuffixTypeArguments returns the type arguments of the class reference as a List of Lists of TypeArgument objects, one list for each suffix.
  • .getSuffixTypeAnnotationInfo returns the a List of AnnotationInfoList elements, one for each suffix, consisting of the type annotations of the suffix, or null if there are no suffix type annotations. Note that the type annotation on the base class is obtained by calling the superclass method TypeSignature#getTypeAnnotationInfo().
  • .getClassInfo() returns the ClassInfo object for the referenced class, if the referenced class was encountered during scanning (causing a ClassInfo object to be created for it). May return null if the referenced class was not encountered during scanning (e.g. if the class was rejected). Even if .getClassInfo() returns null, .loadClass() may still be able to load the class by name.
  • .loadClass() loads the referenced class, if it has not yet been loaded, and returns a Class<?> reference for the loaded class.
    • .loadClass(boolean ignoreExceptions) loads the referenced class, as above, but also allows you to ignore classloading exceptions (if there is an exception, it returns null instead).

TypeVariableSignature

A type signature for a type variable.

  • .getName() returns the name of the type variable (e.g. "T") as a String.
  • .resolve() looks up a type variable (e.g. T) in the defining method (or if that fails, looks up a type variable in the enclosing class), and returns the TypeParameter with the same name (e.g. T extends com.xyz.Cls).

Additional type-related classes

TypeArgument

A (possibly-wildcarded) generic type argument.

  • .getWildCard() returns a Wildcard enum value, which can be one of the values NONE, ANY (i.e. ? in Java syntax), EXTENDS and SUPER.
  • .getTypeSignature() returns a ReferenceTypeSignature for the type bounded by the wildcard.

TypeParameter

A generic type parameter.

  • .getName() returns the type parameter identifier (e.g. "T") as a String.
  • .getClassBound() returns the type parameter class bound as a ReferenceTypeSignature. May be null if there is no class bound.
  • .getInterfaceBounds() returns the type parameter interface bounds as a List of ReferenceTypeSignature objects. Returns the empty list if there are no interface bounds.
Clone this wiki locally