-
-
Notifications
You must be signed in to change notification settings - Fork 309
TypeSignature API
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.
- Type signature class hierarchy
- Example usage
- Resolving type variables
- Type signature classes
- Additional type-related classes
The hierarchy of type signature classes reflects the structure of Java's internal type signature system.
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 aClassTypeSignaturefor the type of the class, if the class is generic, otherwise returnsnull. A class has no type descriptor in the classfile, soClassInfo#getTypeDescriptor()synthesizes one from the class name, the superclass name and the implemented interface names, andClassInfo#getTypeSignatureOrTypeDescriptor()falls back to that for non-generic classes. -
Field type signature:
FieldInfo#getTypeSignatureOrTypeDescriptor()-- returns aTypeSignaturefor the type of the field. -
Method type signature:
-
Method result type signature: call
MethodInfo#getTypeSignatureOrTypeDescriptor()to get aMethodTypeSignaturefor the type of the method, thenMethodTypeSignature#getResultType()to get the result type of the method as aTypeSignature. -
Method parameter type signatures: call
MethodInfo#getParameterInfo()to get aListofMethodParameterInfoobjects for the method parameters, then for each method parameter, callMethodParameterInfo#getTypeSignatureOrTypeDescriptor()to get aTypeSignaturefor the method parameter.
-
Method result type signature: call
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) {
System.out.println("Method " + methodInfo.getName() + " returns a "
+ arrayTypeSignature.getNumDimensions() + "-dimensional array of "
+ arrayTypeSignature.getElementTypeSignature());
} else if (resultTypeSignature instanceof BaseTypeSignature baseTypeSignature) {
System.out.println("Method " + methodInfo.getName() + " returns "
+ baseTypeSignature.getTypeName());
} else if (resultTypeSignature instanceof ClassRefTypeSignature classRefTypeSignature) {
System.out.println("Method " + methodInfo.getName() + " returns "
+ classRefTypeSignature.getFullyQualifiedClassName());
} else if (resultTypeSignature instanceof TypeVariableSignature typeVariableSignature) {
// 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
ClassGraph does not substitute type arguments into type parameters automatically, so you may get a TypeVariableSignature for the type signature of a field, method, method parameter, etc., where you were expecting a concrete type. This is not an omission: a type variable has no single concrete value. Given
interface Base<T> {
T getT();
}
class DerivedA implements Base<String> { }
class DerivedB implements Base<Integer> { }the result type of Base#getT() is genuinely just T. It is String only when viewed through DerivedA, and Integer only when viewed through DerivedB.
Use this method to substitute the type arguments that a particular subtype supplies:
ClassInfo base = scanResult.getClassInfo("Base");
ClassInfo derivedA = scanResult.getClassInfo("DerivedA");
TypeSignature resultType = base.getMethodInfo("getT").get(0)
.getTypeSignatureOrTypeDescriptor().getResultType();
resultType.toString(); // "T"
resultType.resolveTypeVariables(derivedA).toString(); // "java.lang.String"contextClass is the subtype you are viewing the declaring class through — it is the answer to "what type does this method return when called on a DerivedA?". It is not an enclosing class, and it is required rather than inferred because a MethodInfo or FieldInfo belongs to the class that declares it and is shared by every subclass that inherits it, so it does not itself record which subtype you reached it through.
Because the method is defined on TypeSignature, it applies equally to method result types, method parameter types and field types:
// Method parameter type
methodInfo.getParameterInfo().get(0).getTypeSignatureOrTypeDescriptor()
.resolveTypeVariables(contextClass);
// Field type
fieldInfo.getTypeSignatureOrTypeDescriptor().resolveTypeVariables(contextClass);Resolution walks up the superclass and superinterface chain of the context class, composing the type arguments each level supplies for the level above it, so bindings passed through intermediate classes are resolved too:
class Mid<U> implements Base<U> { }
class Derived extends Mid<Integer> { } // Base's T resolves to IntegerType variables are substituted inside type arguments at any depth (Map<A, List<B>>) and inside array element types (T[][]). The original type signature is never modified — a new one is returned, or the same object if nothing was substituted.
A type variable is deliberately left unchanged when it cannot be resolved:
- the context class is not a subtype of the class declaring the type variable;
- the supertype is used in raw form, so supplies no type argument;
- the type variable is shadowed by a type parameter of the method that declares the type (
<T> T identity(T t)declares its ownT); - the type argument is a wildcard with no equivalent outside type-argument position —
?or? super X. An upper-bounded wildcard? extends Xresolves toX. Within type-argument position, wildcards are substituted verbatim, soList<T>withT := ? extends Numberresolves toList<? extends Number>; - the type variable is one that an inner class inherits from its enclosing class, since the classfile records it as declared by the inner class, which is not on the supertype chain of a subclass of the enclosing class.
TypeVariableSignature#resolve() resolves a type variable to its declaration (its TypeParameter), by looking first at the containing method and then at the containing class. That gives you the variable's bounds, not a substituted type argument.
If you are willing to load the classes, you can instead use the reflection API together with a library like gentyref / GeAnTyRef, which can find the concrete and generic type signature of a loaded class.
A type signature for a generic class, returned by ClassInfo#getTypeSignature().
-
.getTypeParameters()returns the type parameters of the class as aListofTypeParameterobjects. -
.getSuperclassSignature()returns aClassRefTypeSignaturefor the superclass of this class. -
.getSuperinterfaceSignatures()returns aListofClassRefTypeSignatureobjects for the interfaces implemented by this class.
A type signature for a method, returned by MethodInfo#getTypeSignature() or MethodInfo#getTypeSignatureOrTypeDescriptor().
-
.getTypeParameters()returns the type parameters of the method as aListofTypeParameterobjects, or the empty list if the method is not generic. -
.getResultType()returns aTypeSignaturefor the result type of the method. -
.getThrowsSignatures()returns aListofClassRefOrTypeVariableSignatureobjects for any exceptions or errors that can be thrown by the method. - To obtain method parameter type signatures, first call
MethodInfo#getParameterInfo()to obtain aListofMethodParameterInfoobjects, one per method parameter, and then callMethodParameterInfo#getTypeSignatureOrTypeDescriptor()on each of them. -
.getReceiverTypeAnnotationInfo()returns anAnnotationInfoListof any type annotations on an explicit receiver parameter of the method, if present, otherwise returns null.
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:
-
.resolveTypeVariables(ClassInfo contextClass)returns this type signature with any type variables substituted by the type arguments thatcontextClasssupplies for them, or this type signature itself if nothing could be substituted. See Resolving type variables. -
.equalsIgnoringTypeParams(TypeSignature other)returnstrueif this type signature and the other type signature are equal, ignoring any type parameters. A type variable is considered equal to a class reference if the type variable's bound can be reconciled with that class, so that a type signature parsed from a generic signature can be compared with the corresponding erased type descriptor.
The following two methods are available on every type signature class on this page, including ClassTypeSignature, MethodTypeSignature, TypeArgument and TypeParameter, which are not subclasses of TypeSignature:
-
.getTypeAnnotationInfo()returns anAnnotationInfoListof any type annotations on the type signature, if present, otherwise returns null. -
.toStringWithSimpleNames()returns a simpler rendering of the type signature thantoString(), by using only the simple name of any classes or annotations in the type signature.
A type signature for a base type (a primitive type or void).
-
.getType()returnsint.class,long.class,short.class,double.class,float.class,char.class,boolean.class,byte.class, orvoid.class. -
.getTypeName()returns"int","long","short","double","float","char","boolean","byte"or"void", matchingClass#getTypeName(). -
.getTypeSignatureChar()returns the single character used to represent the type in a classfile-internal type signature:I,J,S,D,F,C,Z,BorVrespectively.
A type signature for a reference type. Subclasses are ClassRefOrTypeVariableSignature and 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 typeint[][][], this returns aBaseTypeSignaturerepresentingint.class). -
.getNumDimensions()returns the number of dimensions of the array. -
.getNestedType()returns aTypeSignaturefor the array class with one dimension fewer, or the innermost element type if there are no nested array dimensions. -
.getTypeSignatureString()returns the raw classfile-internal array type signature string, e.g."[[I"forint[][]. -
.getArrayClassInfo()returns anArrayClassInfofor the array class.
A type signature for a class reference or a type variable. Subclasses are ClassRefTypeSignature and TypeVariableSignature.
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 aListofTypeArgumentobjects. -
.getSuffixes()returns the class suffixes (for inner classes). -
.getSuffixTypeArguments()returns the type arguments of the class reference as aListofLists ofTypeArgumentobjects, one list for each suffix. -
.getSuffixTypeAnnotationInfo()returns aListofAnnotationInfoListelements, one for each suffix, consisting of the type annotations of that suffix, ornullif there are no suffix type annotations. Note that the type annotation on the base class is obtained by calling the superclass methodTypeSignature#getTypeAnnotationInfo(). -
.getClassInfo()returns theClassInfoobject for the referenced class, if the referenced class was encountered during scanning (causing aClassInfoobject to be created for it). Returns null if the referenced class was not encountered during scanning, e.g. if it was rejected -- call.getFullyQualifiedClassName()if you need the class name in that case.
A type signature for a type variable.
-
.getName()returns the name of the type variable (e.g."T") as aString. -
.resolve()looks up a type variable (e.g.T) in the defining method (or if that fails, in the enclosing class), and returns theTypeParameterwith the same name (e.g.T extends com.xyz.Cls). If neither the method nor the class declares a type parameter of that name, an unboundedTypeParameterwith just the type variable's name is returned. ThrowsIllegalStateExceptionif the enclosing class was not found during the scan. -
.toStringWithTypeBound()returns the type variable along with its type bound, if available (e.g."T extends com.xyz.Cls"), or just the type variable if it has no bound or the bound could not be determined. This is the string form of what.resolve()returns.
A (possibly-wildcarded) generic type argument.
-
.getWildcard()returns aWildcardenum value, which can be one of the valuesNONE,ANY(i.e.?in Java syntax),EXTENDSandSUPER. -
.getTypeSignature()returns aReferenceTypeSignaturefor the type bounded by the wildcard.
A generic type parameter.
-
.getName()returns the type parameter identifier (e.g."T") as aString. -
.getClassBound()returns the type parameter class bound as aReferenceTypeSignature. May be null if there is no class bound. -
.getInterfaceBounds()returns the type parameter interface bounds as aListofReferenceTypeSignatureobjects. Returns the empty list if there are no interface bounds.
