
org.perfectable.introspection.MethodSignature Maven / Gradle / Ivy
package org.perfectable.introspection;
import java.lang.reflect.Array;
import java.util.Collection;
import com.google.common.base.CharMatcher;
import com.google.common.collect.ImmutableList;
final class MethodSignature {
static final CharMatcher IDENTIFIER_BREAKING = CharMatcher.anyOf(";.[:><").or(CharMatcher.whitespace());
private final ImmutableList formalTypeParameters;
private final ImmutableList formalParameters;
private final ReturnType returnType;
private final ImmutableList thrownTypes;
static MethodSignature read(String signatureString) {
CharacterReader reader = new CharacterReader(signatureString);
ImmutableList typeParameters =
TypeParameter.readFormalTypeParametersFrom(reader);
ImmutableList formalParameters = parseFormalParameters(reader);
ReturnType returnType = ReturnType.readReturnTypeFrom(reader);
ImmutableList thrownTypes = parseThrownTypes(reader);
return new MethodSignature(typeParameters, formalParameters, returnType, thrownTypes);
}
private MethodSignature(ImmutableList typeParameters,
ImmutableList formalParameters,
ReturnType returnType,
ImmutableList thrownTypes) {
this.formalTypeParameters = typeParameters;
this.formalParameters = formalParameters;
this.returnType = returnType;
this.thrownTypes = thrownTypes;
}
public Class>[] runtimeParameterTypes(ClassLoader loader) {
return formalParameters.stream()
.map(typeSignature -> typeSignature.asRuntimeClass(loader, formalTypeParameters))
.toArray(Class>[]::new);
}
public Class> runtimeResultType(ClassLoader loader) {
return returnType.asRuntimeClass(loader, formalTypeParameters);
}
public Class>[] runtimeDeclaredExceptionTypes(ClassLoader loader) {
return thrownTypes.stream()
.map(fieldType -> fieldType.asRuntimeClass(loader, formalTypeParameters))
.toArray(Class>[]::new);
}
private static ImmutableList parseFormalParameters(CharacterReader reader) {
reader.advanceAssuming('(');
ImmutableList.Builder typeSignatures = ImmutableList.builder();
while (!reader.currentIs(')')) {
FieldType typeSignature = FieldType.readFieldTypeFrom(reader);
typeSignatures.add(typeSignature);
}
ImmutableList signatures = typeSignatures.build();
reader.advanceAssuming(')');
return signatures;
}
private static ImmutableList parseThrownTypes(CharacterReader reader) {
ImmutableList.Builder resultBuilder = ImmutableList.builder();
while (reader.currentIsThenSkip('^')) {
FieldType throwsSignature = FieldType.readFieldTypeFrom(reader);
if (throwsSignature instanceof ArrayType) {
throw new IllegalArgumentException("Throws cannot be array");
}
resultBuilder.add(throwsSignature);
}
return resultBuilder.build();
}
private static final class CharacterReader {
private final char[] input;
private int position;
CharacterReader(String input) {
this.input = input.toCharArray();
}
void advanceAssuming(char expected) {
if (!currentIs(expected)) {
throw new IllegalArgumentException("Expected " + expected);
}
position++;
}
String readUntil(CharMatcher breaking) {
StringBuilder result = new StringBuilder();
while (position < input.length) {
char current = input[position];
if (breaking.matches(current)) {
break;
}
result.append(current);
position++;
}
return result.toString();
}
private boolean currentIs(char expected) {
return currentIn(CharMatcher.is(expected));
}
boolean currentIn(CharMatcher characters) {
if (position >= input.length) {
return false;
}
char current = input[position];
return characters.matches(current);
}
boolean currentIsThenSkip(char expected) {
if (!currentIs(expected)) {
return false;
}
position++;
return true;
}
}
@SuppressWarnings("InterfaceWithOnlyStatics")
private interface TypeArgument {
static TypeArgument readTypeArgumentFrom(CharacterReader reader) {
if (reader.currentIsThenSkip('*')) {
return Wildcard.createWild();
}
if (reader.currentIsThenSkip('+')) {
FieldType upper = FieldType.readFieldTypeFrom(reader);
return Wildcard.createWithUpperBound(upper);
}
if (reader.currentIsThenSkip('-')) {
FieldType lower = FieldType.readFieldTypeFrom(reader);
return Wildcard.createWithLowerBound(lower);
}
return FieldType.readFieldTypeFrom(reader);
}
static ImmutableList readTypeArgumentsFrom(CharacterReader reader) {
reader.advanceAssuming('<');
ImmutableList.Builder typeArgumentBuilder = ImmutableList.builder();
while (!reader.currentIs('>')) {
TypeArgument typeArgument = readTypeArgumentFrom(reader);
typeArgumentBuilder.add(typeArgument);
}
reader.advanceAssuming('>');
return typeArgumentBuilder.build();
}
}
private abstract static class Wildcard implements TypeArgument {
Wildcard() {
// no fields yet
}
static Wildcard createWild() {
return new Wildcard() {
};
}
static Wildcard createWithUpperBound(FieldType upper) {
return new Wildcard() {
};
}
static Wildcard createWithLowerBound(FieldType lower) {
return new Wildcard() {
};
}
}
private interface ReturnType {
ReturnType VOID = new ReturnType() {
@Override
public Class> asRuntimeClass(ClassLoader loader, Collection formals) {
return void.class;
}
};
Class> asRuntimeClass(ClassLoader loader, Collection formals);
static ReturnType readReturnTypeFrom(CharacterReader reader) {
if (reader.currentIsThenSkip('V')) {
return ReturnType.VOID;
}
return FieldType.readFieldTypeFrom(reader);
}
}
private interface FieldType extends ReturnType, TypeArgument {
static FieldType readFieldTypeFrom(CharacterReader reader) {
if (reader.currentIs('L')) {
return ObjectType.readSimpleClassTypeSignature(reader);
}
if (reader.currentIs('T')) {
return TypeVariable.readTypeVariableSignature(reader);
}
if (reader.currentIs('[')) {
return ArrayType.readArrayTypeSignatureFrom(reader);
}
if (reader.currentIn(PrimitiveType.CHARACTERS)) {
return PrimitiveType.readPrimitiveSignature(reader);
}
throw new IllegalArgumentException("Expected Field Type Signature");
}
}
private static final class ArrayType implements FieldType {
private final FieldType nested;
private ArrayType(FieldType nested) {
this.nested = nested;
}
@Override
public Class> asRuntimeClass(ClassLoader loader, Collection formals) {
Class> componentType = nested.asRuntimeClass(loader, formals);
return Array.newInstance(componentType, 0).getClass();
}
static ArrayType readArrayTypeSignatureFrom(CharacterReader reader) {
reader.advanceAssuming('[');
FieldType nested = FieldType.readFieldTypeFrom(reader);
return new ArrayType(nested);
}
}
private static final class TypeParameter implements TypeArgument {
private final String identifier;
private final ImmutableList bounds;
private TypeParameter(String identifier, ImmutableList bounds) {
this.identifier = identifier;
this.bounds = bounds;
}
static ImmutableList readFormalTypeParametersFrom(CharacterReader reader) {
if (!reader.currentIsThenSkip('<')) {
return ImmutableList.of();
}
ImmutableList.Builder resultBuilder = ImmutableList.builder();
while (!reader.currentIs('>')) {
TypeParameter typeParameter = TypeParameter.readFormalTypeParameterFrom(reader);
resultBuilder.add(typeParameter);
}
reader.advanceAssuming('>');
return resultBuilder.build();
}
static TypeParameter readFormalTypeParameterFrom(CharacterReader reader) {
final String identifier = reader.readUntil(IDENTIFIER_BREAKING);
ImmutableList.Builder boundsBuilder = ImmutableList.builder();
reader.advanceAssuming(':');
if (!reader.currentIs(':')) {
FieldType bound = FieldType.readFieldTypeFrom(reader);
boundsBuilder.add(bound);
}
while (reader.currentIsThenSkip(':')) {
FieldType bound = FieldType.readFieldTypeFrom(reader);
boundsBuilder.add(bound);
}
ImmutableList bounds = boundsBuilder.build();
return new TypeParameter(identifier, bounds);
}
FieldType firstBound() {
if (bounds.isEmpty()) {
return ObjectType.OBJECT;
}
return bounds.get(0);
}
boolean hasIdentifier(String candidate) {
return this.identifier.equals(candidate);
}
}
private static final class ObjectType implements FieldType {
static final ObjectType OBJECT =
ObjectType.createWithoutTypeArguments("java.lang.Object");
private final String className;
@SuppressWarnings("unused")
private final ImmutableList typeArguments;
private ObjectType(String className, ImmutableList typeArguments) {
this.className = className;
this.typeArguments = typeArguments;
}
static ObjectType readSimpleClassTypeSignature(CharacterReader reader) {
reader.advanceAssuming('L');
String identifier = reader.readUntil(IDENTIFIER_BREAKING);
String qualified = identifier.replaceAll("/", ".");
if (reader.currentIsThenSkip(';')) {
return ObjectType.createWithoutTypeArguments(qualified);
}
if (reader.currentIs('<')) {
ImmutableList typeArguments = TypeArgument.readTypeArgumentsFrom(reader);
reader.advanceAssuming(';');
return ObjectType.create(qualified, typeArguments);
}
throw new IllegalArgumentException("expected '<' or ';' or '.'");
}
static ObjectType create(String identifier, ImmutableList typeArguments) {
return new ObjectType(identifier, typeArguments);
}
static ObjectType createWithoutTypeArguments(String identifier) {
return new ObjectType(identifier, ImmutableList.of());
}
@Override
public Class> asRuntimeClass(ClassLoader loader, Collection formals) {
try {
return loader.loadClass(className);
}
catch (ClassNotFoundException e) {
throw new AssertionError(e);
}
}
}
private static final class TypeVariable implements FieldType {
private final String identifier;
private TypeVariable(String identifier) {
this.identifier = identifier;
}
private static TypeVariable readTypeVariableSignature(CharacterReader reader) {
reader.advanceAssuming('T');
String identifier = reader.readUntil(IDENTIFIER_BREAKING);
TypeVariable typeVariableSignature = new TypeVariable(identifier);
reader.advanceAssuming(';');
return typeVariableSignature;
}
@Override
public Class> asRuntimeClass(ClassLoader loader, Collection formals) {
TypeParameter typeParameter = formals.stream()
.filter(formal -> formal.hasIdentifier(identifier))
.findAny()
.orElseThrow(() -> new IllegalArgumentException("Cannot resolve parameter " + identifier));
return typeParameter.firstBound().asRuntimeClass(loader, formals);
}
}
private static final class PrimitiveType implements FieldType {
static final CharMatcher CHARACTERS = CharMatcher.anyOf("BCDFIJSZ");
@SuppressWarnings("NPathComplexity")
static PrimitiveType readPrimitiveSignature(CharacterReader reader) {
if (reader.currentIsThenSkip('B')) {
return PrimitiveType.BYTE;
}
if (reader.currentIsThenSkip('C')) {
return PrimitiveType.CHAR;
}
if (reader.currentIsThenSkip('D')) {
return PrimitiveType.DOUBLE;
}
if (reader.currentIsThenSkip('F')) {
return PrimitiveType.FLOAT;
}
if (reader.currentIsThenSkip('I')) {
return PrimitiveType.INT;
}
if (reader.currentIsThenSkip('J')) {
return PrimitiveType.LONG;
}
if (reader.currentIsThenSkip('S')) {
return PrimitiveType.SHORT;
}
if (reader.currentIsThenSkip('Z')) {
return PrimitiveType.BOOLEAN;
}
throw new IllegalArgumentException("expected primitive type");
}
static final PrimitiveType BYTE = new PrimitiveType(byte.class);
static final PrimitiveType CHAR = new PrimitiveType(char.class);
static final PrimitiveType DOUBLE = new PrimitiveType(double.class);
static final PrimitiveType FLOAT = new PrimitiveType(float.class);
static final PrimitiveType INT = new PrimitiveType(int.class);
static final PrimitiveType LONG = new PrimitiveType(long.class);
static final PrimitiveType SHORT = new PrimitiveType(short.class);
static final PrimitiveType BOOLEAN = new PrimitiveType(boolean.class);
private final Class> type;
private PrimitiveType(Class> type) {
this.type = type;
}
@Override
public Class> asRuntimeClass(ClassLoader loader, Collection formals) {
return type;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy