diff --git a/src/main/java/io/quarkus/gizmo/ClassCreator.java b/src/main/java/io/quarkus/gizmo/ClassCreator.java index 38c4dd3..4eb2daa 100644 --- a/src/main/java/io/quarkus/gizmo/ClassCreator.java +++ b/src/main/java/io/quarkus/gizmo/ClassCreator.java @@ -42,6 +42,8 @@ import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; +import io.quarkus.gizmo.SignatureBuilder.ClassSignatureBuilder; + public class ClassCreator implements AutoCloseable, AnnotatedElement, SignatureElement { public static Builder builder() { @@ -65,7 +67,8 @@ public static Builder interfaceBuilder() { private final Map superclassAccessors = new LinkedHashMap<>(); private final AtomicInteger accessorCount = new AtomicInteger(); - ClassCreator(BytecodeCreatorImpl enclosing, ClassOutput classOutput, String name, String signature, String superClass, int access, String... interfaces) { + ClassCreator(BytecodeCreatorImpl enclosing, ClassOutput classOutput, String name, String signature, String superClass, + int access, String... interfaces) { this.enclosing = enclosing; this.classOutput = classOutput; this.superClass = superClass.replace('.', '/'); @@ -159,7 +162,8 @@ MethodDescriptor getSupertypeAccessor(MethodDescriptor descriptor, String supert for (int i = 0; i < params.length; ++i) { params[i] = ctor.getMethodParam(i); } - MethodDescriptor superDescriptor = MethodDescriptor.ofMethod(supertype, descriptor.getName(), descriptor.getReturnType(), descriptor.getParameterTypes()); + MethodDescriptor superDescriptor = MethodDescriptor.ofMethod(supertype, descriptor.getName(), + descriptor.getReturnType(), descriptor.getParameterTypes()); ResultHandle ret; if (isInterface) { ret = ctor.invokeSpecialInterfaceMethod(superDescriptor, ctor.getThis(), params); @@ -183,7 +187,7 @@ public void writeTo(ClassOutput classOutput) { Writer sourceWriter = classOutput.getSourceWriter(className); ClassVisitor cv; if (sourceWriter != null) { - cv = new GizmoClassVisitor(Gizmo.ASM_API_VERSION, file, sourceWriter); + cv = new GizmoClassVisitor(Gizmo.ASM_API_VERSION, file, sourceWriter); } else { cv = file; } @@ -202,7 +206,7 @@ public void writeTo(ClassOutput classOutput) { if (requiresCtor) { // constructor if (cv instanceof GizmoClassVisitor) { - ((GizmoClassVisitor)cv).append("// Auto-generated constructor").newLine(); + ((GizmoClassVisitor) cv).append("// Auto-generated constructor").newLine(); } MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, MethodDescriptor.INIT, "()V", null, null); mv.visitVarInsn(ALOAD, 0); // push `this` to the operand stack @@ -220,9 +224,10 @@ public void writeTo(ClassOutput classOutput) { for (Map.Entry method : methods.entrySet()) { method.getValue().write(cv); } - for(AnnotationCreatorImpl annotation : annotations) { - AnnotationVisitor av = cv.visitAnnotation(DescriptorUtils.extToInt(annotation.getAnnotationType()), annotation.getRetentionPolicy() == RetentionPolicy.RUNTIME); - for(Map.Entry e : annotation.getValues().entrySet()) { + for (AnnotationCreatorImpl annotation : annotations) { + AnnotationVisitor av = cv.visitAnnotation(DescriptorUtils.extToInt(annotation.getAnnotationType()), + annotation.getRetentionPolicy() == RetentionPolicy.RUNTIME); + for (Map.Entry e : annotation.getValues().entrySet()) { AnnotationUtils.visitAnnotationValue(av, e.getKey(), e.getValue()); } av.visitEnd(); @@ -234,7 +239,7 @@ public void writeTo(ClassOutput classOutput) { } /** - * Finish the class creator. If a class output was configured for this class creator, the class bytes + * Finish the class creator. If a class output was configured for this class creator, the class bytes * will immediately be written there. */ @Override @@ -317,6 +322,30 @@ public Builder signature(String signature) { return this; } + /** + * The raw types of the superclass and superinterfaces are extracted and passed to {@link #superClass(String)} and + * {@link #interfaces(String...)} respectively. + * + * @param signatureBuilder + * @return self + */ + public Builder signature(ClassSignatureBuilder signatureBuilder) { + ClassSignatureBuilderImpl signatureBuilderImpl = (ClassSignatureBuilderImpl) signatureBuilder; + Type superClass = signatureBuilderImpl.superClass; + if (superClass != null) { + superClass(getRawType(superClass)); + } + if (!signatureBuilderImpl.superInterfaces.isEmpty()) { + String[] interfaces = new String[signatureBuilderImpl.superInterfaces.size()]; + int idx = 0; + for (Type superInterface : signatureBuilderImpl.superInterfaces) { + interfaces[idx++] = getRawType(superInterface); + } + interfaces(interfaces); + } + return signature(signatureBuilder.build()); + } + public Builder superClass(String superClass) { if ((access & ACC_INTERFACE) != 0 && !"java.lang.Object".equals(superClass) @@ -360,7 +389,16 @@ public Builder interfaces(Class... interfaces) { public ClassCreator build() { Objects.requireNonNull(className); Objects.requireNonNull(superClass); - return new ClassCreator(enclosing, classOutput, className, signature, superClass, access, interfaces.toArray(new String[0])); + return new ClassCreator(enclosing, classOutput, className, signature, superClass, access, + interfaces.toArray(new String[0])); + } + + private String getRawType(Type type) { + if (type.isClass()) { + return type.asClass().name; + } else { + return type.asParameterizedType().genericClass.name; + } } } diff --git a/src/main/java/io/quarkus/gizmo/ClassSignatureBuilderImpl.java b/src/main/java/io/quarkus/gizmo/ClassSignatureBuilderImpl.java new file mode 100644 index 0000000..0b3e48a --- /dev/null +++ b/src/main/java/io/quarkus/gizmo/ClassSignatureBuilderImpl.java @@ -0,0 +1,91 @@ +package io.quarkus.gizmo; + +import java.util.ArrayList; +import java.util.List; + +import io.quarkus.gizmo.SignatureBuilder.ClassSignatureBuilder; +import io.quarkus.gizmo.Type.ClassType; +import io.quarkus.gizmo.Type.ParameterizedType; +import io.quarkus.gizmo.Type.TypeVariable; + +class ClassSignatureBuilderImpl implements ClassSignatureBuilder { + + List typeParameters = new ArrayList<>(); + Type superClass = ClassType.OBJECT; + List superInterfaces = new ArrayList<>(); + + @Override + public String build() { + StringBuilder signature = new StringBuilder(); + + // type params + if (!typeParameters.isEmpty()) { + signature.append('<'); + for (TypeVariable typeParameter : typeParameters) { + typeParameter.appendTypeParameterToSignature(signature); + } + signature.append('>'); + } + + // superclass + superClass.appendToSignature(signature); + + // interfaces + if (!superInterfaces.isEmpty()) { + for (Type superInterface : superInterfaces) { + superInterface.appendToSignature(signature); + } + } + return signature.toString(); + } + + @Override + public ClassSignatureBuilder addTypeParameter(TypeVariable typeParameter) { + typeParameters.add(typeParameter); + return this; + } + + @Override + public ClassSignatureBuilder setSuperClass(ClassType superClass) { + this.superClass = superClass; + return this; + } + + @Override + public ClassSignatureBuilder setSuperClass(ParameterizedType superClass) { + if (containsWildcard(superClass)) { + throw new IllegalArgumentException("An extended class type may not specify a wildcard"); + } + + this.superClass = superClass; + return this; + } + + @Override + public ClassSignatureBuilder addInterface(ClassType interfaceType) { + superInterfaces.add(interfaceType); + return this; + } + + @Override + public ClassSignatureBuilder addInterface(ParameterizedType interfaceType) { + if (containsWildcard(interfaceType)) { + throw new IllegalArgumentException("An implemented interface type may not specify a wildcard"); + } + superInterfaces.add(interfaceType); + return this; + } + + private boolean containsWildcard(Type type) { + if (type.isWildcard()) { + return true; + } else if (type.isParameterizedType()) { + for (Type typeArgument : type.asParameterizedType().getTypeArguments()) { + if (containsWildcard(typeArgument)) { + return true; + } + } + } + return false; + } +} diff --git a/src/main/java/io/quarkus/gizmo/FieldSignatureBuilderImpl.java b/src/main/java/io/quarkus/gizmo/FieldSignatureBuilderImpl.java new file mode 100644 index 0000000..8364a1d --- /dev/null +++ b/src/main/java/io/quarkus/gizmo/FieldSignatureBuilderImpl.java @@ -0,0 +1,22 @@ +package io.quarkus.gizmo; + +import java.util.Objects; + +import io.quarkus.gizmo.SignatureBuilder.FieldSignatureBuilder; + +class FieldSignatureBuilderImpl implements FieldSignatureBuilder { + private Type type; + + @Override + public String build() { + StringBuilder signature = new StringBuilder(); + type.appendToSignature(signature); + return signature.toString(); + } + + @Override + public FieldSignatureBuilder setType(Type type) { + this.type = Objects.requireNonNull(type); + return this; + } +} diff --git a/src/main/java/io/quarkus/gizmo/MethodSignatureBuilderImpl.java b/src/main/java/io/quarkus/gizmo/MethodSignatureBuilderImpl.java new file mode 100644 index 0000000..78d4f0b --- /dev/null +++ b/src/main/java/io/quarkus/gizmo/MethodSignatureBuilderImpl.java @@ -0,0 +1,81 @@ +package io.quarkus.gizmo; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import io.quarkus.gizmo.SignatureBuilder.MethodSignatureBuilder; +import io.quarkus.gizmo.Type.ClassType; +import io.quarkus.gizmo.Type.TypeVariable; +import io.quarkus.gizmo.Type.VoidType; + +class MethodSignatureBuilderImpl implements MethodSignatureBuilder { + private List typeParameters = new ArrayList<>(); + private Type returnType = VoidType.INSTANCE; + private List parameterTypes = new ArrayList<>(); + private List exceptions = new ArrayList<>(); + + @Override + public String build() { + StringBuilder signature = new StringBuilder(); + + // type params + if (!typeParameters.isEmpty()) { + signature.append('<'); + for (TypeVariable typeParameter : typeParameters) { + typeParameter.appendTypeParameterToSignature(signature); + } + signature.append('>'); + } + + // param types + signature.append('('); + for (Type parameterType : parameterTypes) { + parameterType.appendToSignature(signature); + } + signature.append(')'); + + // return type + returnType.appendToSignature(signature); + + // exception types + if (!exceptions.isEmpty()) { + for (Type exceptionType : exceptions) { + signature.append('^'); + exceptionType.appendToSignature(signature); + } + } + + return signature.toString(); + } + + @Override + public MethodSignatureBuilder addTypeParameter(TypeVariable typeParameter) { + typeParameters.add(Objects.requireNonNull(typeParameter)); + return this; + } + + @Override + public MethodSignatureBuilder setReturnType(Type returnType) { + this.returnType = Objects.requireNonNull(returnType); + return this; + } + + @Override + public MethodSignatureBuilder addParameterType(Type parameterType) { + this.parameterTypes.add(Objects.requireNonNull(parameterType)); + return this; + } + + @Override + public MethodSignatureBuilder addException(ClassType exceptionType) { + this.exceptions.add(Objects.requireNonNull(exceptionType)); + return this; + } + + @Override + public MethodSignatureBuilder addException(TypeVariable exceptionType) { + this.exceptions.add(Objects.requireNonNull(exceptionType)); + return this; + } +} diff --git a/src/main/java/io/quarkus/gizmo/SignatureBuilder.java b/src/main/java/io/quarkus/gizmo/SignatureBuilder.java new file mode 100644 index 0000000..7e09fbe --- /dev/null +++ b/src/main/java/io/quarkus/gizmo/SignatureBuilder.java @@ -0,0 +1,66 @@ +package io.quarkus.gizmo; + +import io.quarkus.gizmo.Type.ClassType; +import io.quarkus.gizmo.Type.ParameterizedType; +import io.quarkus.gizmo.Type.TypeVariable; + +/** + * Builds a generic signature as defined in JVMS 17, chapter "4.7.9.1. Signatures". + * + * @see SignatureElement#setSignature(String) + */ +public interface SignatureBuilder { + static ClassSignatureBuilder forClass() { + return new ClassSignatureBuilderImpl(); + } + + static MethodSignatureBuilder forMethod() { + return new MethodSignatureBuilderImpl(); + } + + static FieldSignatureBuilder forField() { + return new FieldSignatureBuilderImpl(); + } + + /** + * @return the generic signature + */ + String build(); + + /** + * Builds a generic signature of a class (including interfaces). + */ + interface ClassSignatureBuilder extends SignatureBuilder { + ClassSignatureBuilder addTypeParameter(TypeVariable typeParameter); + + ClassSignatureBuilder setSuperClass(ClassType superClass); + + ClassSignatureBuilder setSuperClass(ParameterizedType superClass); + + ClassSignatureBuilder addInterface(ClassType interfaceType); + + ClassSignatureBuilder addInterface(ParameterizedType interfaceType); + } + + /** + * Builds a generic signature of a method (including constructors). + */ + interface MethodSignatureBuilder extends SignatureBuilder { + MethodSignatureBuilder addTypeParameter(TypeVariable typeParameter); + + MethodSignatureBuilder setReturnType(Type returnType); + + MethodSignatureBuilder addParameterType(Type parameterType); + + MethodSignatureBuilder addException(ClassType exceptionType); + + MethodSignatureBuilder addException(TypeVariable exceptionType); + } + + /** + * Builds a generic signature of a field. Also usable for building generic signatures of record components. + */ + interface FieldSignatureBuilder extends SignatureBuilder { + FieldSignatureBuilder setType(Type type); + } +} diff --git a/src/main/java/io/quarkus/gizmo/SignatureElement.java b/src/main/java/io/quarkus/gizmo/SignatureElement.java index 68c2d4c..be911dc 100644 --- a/src/main/java/io/quarkus/gizmo/SignatureElement.java +++ b/src/main/java/io/quarkus/gizmo/SignatureElement.java @@ -1,11 +1,18 @@ package io.quarkus.gizmo; /** - * An element that has a signature attribute + * An element that has a signature attribute. */ public interface SignatureElement { String getSignature(); + /** + * Use the convenient {@link SignatureBuilder} to build signatures for classes, methods and fields. + * + * @param signature The generic signature as defined in JVMS 17, chapter "4.7.9.1. Signatures" + * @return the element + * @see SignatureBuilder + */ S setSignature(String signature); } diff --git a/src/main/java/io/quarkus/gizmo/Type.java b/src/main/java/io/quarkus/gizmo/Type.java new file mode 100644 index 0000000..d439fb7 --- /dev/null +++ b/src/main/java/io/quarkus/gizmo/Type.java @@ -0,0 +1,489 @@ +package io.quarkus.gizmo; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import org.jboss.jandex.DotName; +import org.jboss.jandex.PrimitiveType.Primitive; + +/** + * Used to express types when {@linkplain SignatureBuilder building} a generic signature of some declaration. + *

+ * Implementations are created via factory methods such as {@link #voidType()} and {@link #classType(Class)}. + * + * @see SignatureBuilder + */ +public abstract class Type { + // Factory methods + + public static VoidType voidType() { + return VoidType.INSTANCE; + } + + public static PrimitiveType booleanType() { + return PrimitiveType.BOOLEAN; + } + + public static PrimitiveType byteType() { + return PrimitiveType.BYTE; + } + + public static PrimitiveType shortType() { + return PrimitiveType.SHORT; + } + + public static PrimitiveType intType() { + return PrimitiveType.INT; + } + + public static PrimitiveType longType() { + return PrimitiveType.LONG; + } + + public static PrimitiveType floatType() { + return PrimitiveType.FLOAT; + } + + public static PrimitiveType doubleType() { + return PrimitiveType.DOUBLE; + } + + public static PrimitiveType charType() { + return PrimitiveType.CHAR; + } + + public static ClassType classType(DotName className) { + return classType(className.toString()); + } + + public static ClassType classType(String className) { + return new ClassType(className.replace('.', '/'), null); + } + + public static ClassType classType(Class clazz) { + return classType(clazz.getName()); + } + + public static ParameterizedType parameterizedType(ClassType genericClass, Type... typeArguments) { + if (typeArguments.length == 0) { + throw new IllegalArgumentException("No type arguments"); + } + return new ParameterizedType(genericClass, Arrays.asList(typeArguments), null); + } + + public static ArrayType arrayType(Type elementType) { + return new ArrayType(elementType, 1); + } + + public static ArrayType arrayType(Type elementType, int dimensions) { + return new ArrayType(elementType, dimensions); + } + + public static TypeVariable typeVariable(String name) { + return typeVariable(name, ClassType.OBJECT); + } + + public static TypeVariable typeVariable(String name, Type classOrTypeVariableBound) { + Type bound = Objects.requireNonNull(classOrTypeVariableBound); + if (!bound.isClass() && !bound.isParameterizedType() && !bound.isTypeVariable()) { + throw new IllegalArgumentException("Type variable bound must be a class or a type variable"); + } + return new TypeVariable(name, bound, Collections.emptyList()); + } + + public static TypeVariable typeVariable(String name, Type classBound, Type... interfaceBounds) { + if (classBound != null && !classBound.isClass() && !classBound.isParameterizedType()) { + throw new IllegalArgumentException("First type variable bound must be a class"); + } + for (Type interfaceBound : interfaceBounds) { + if (!interfaceBound.isClass() && !interfaceBound.isParameterizedType()) { + throw new IllegalArgumentException("Next type variable bounds must all be interfaces"); + } + } + + return new TypeVariable(name, classBound, Arrays.asList(interfaceBounds)); + } + + public static WildcardType wildcardTypeWithUpperBound(Type bound) { + return new WildcardType(Objects.requireNonNull(bound), null); + } + + public static WildcardType wildcardTypeWithLowerBound(Type bound) { + return new WildcardType(null, Objects.requireNonNull(bound)); + } + + public static WildcardType wildcardTypeUnbounded() { + return new WildcardType(ClassType.OBJECT, null); + } + + // implementation details + + abstract void appendToSignature(StringBuilder signature); + + boolean isVoid() { + return false; + } + + boolean isPrimitive() { + return false; + } + + boolean isClass() { + return false; + } + + boolean isArray() { + return false; + } + + boolean isParameterizedType() { + return false; + } + + boolean isTypeVariable() { + return false; + } + + boolean isWildcard() { + return false; + } + + VoidType asVoid() { + throw new IllegalStateException("Not a void"); + } + + PrimitiveType asPrimitive() { + throw new IllegalStateException("Not a primitive"); + } + + ClassType asClass() { + throw new IllegalStateException("Not a class"); + } + + ArrayType asArray() { + throw new IllegalStateException("Not an array"); + } + + ParameterizedType asParameterizedType() { + throw new IllegalStateException("Not a parameterized type"); + } + + TypeVariable asTypeVariable() { + throw new IllegalStateException("Not a type variable"); + } + + WildcardType asWildcard() { + throw new IllegalStateException("Not a wildcard type"); + } + + // Types + + public static final class VoidType extends Type { + public static final VoidType INSTANCE = new VoidType(); + + @Override + void appendToSignature(StringBuilder signature) { + signature.append("V"); + } + + @Override + boolean isVoid() { + return true; + } + + @Override + VoidType asVoid() { + return this; + } + } + + public static final class PrimitiveType extends Type { + public static final PrimitiveType BOOLEAN = new PrimitiveType(Primitive.BOOLEAN); + public static final PrimitiveType BYTE = new PrimitiveType(Primitive.BYTE); + public static final PrimitiveType SHORT = new PrimitiveType(Primitive.SHORT); + public static final PrimitiveType INT = new PrimitiveType(Primitive.INT); + public static final PrimitiveType LONG = new PrimitiveType(Primitive.LONG); + public static final PrimitiveType FLOAT = new PrimitiveType(Primitive.FLOAT); + public static final PrimitiveType DOUBLE = new PrimitiveType(Primitive.DOUBLE); + public static final PrimitiveType CHAR = new PrimitiveType(Primitive.CHAR); + + private final Primitive primitive; + + PrimitiveType(Primitive primitive) { + this.primitive = Objects.requireNonNull(primitive); + } + + @Override + void appendToSignature(StringBuilder signature) { + switch (primitive) { + case BOOLEAN: + signature.append("Z"); + break; + case BYTE: + signature.append("B"); + break; + case SHORT: + signature.append("S"); + case INT: + signature.append("I"); + break; + case LONG: + signature.append("J"); + break; + case FLOAT: + signature.append("F"); + break; + case DOUBLE: + signature.append("D"); + break; + case CHAR: + signature.append("C"); + break; + default: + throw new IllegalStateException("Unknown primitive type: " + primitive.toString()); + } + } + + @Override + boolean isPrimitive() { + return true; + } + + @Override + PrimitiveType asPrimitive() { + return this; + } + } + + public static final class ClassType extends Type { + public static final ClassType OBJECT = new ClassType("java/lang/Object", null); + + final String name; // always slash-delimited + final Type owner; + + ClassType(String name, Type owner) { + this.name = Objects.requireNonNull(name); + this.owner = owner; + } + + /** + * Allows building a signature like {@code Lcom/example/Outer.Inner;}. This is usually + * unnecessary, because {@code Lcom/example/Outer$Inner} is also a valid signature, + * but it's occasionally useful to build a signature of more complex inner types. + * + * @param simpleName simple name of the member class nested in this class + * @return the inner class + */ + public ClassType innerClass(String simpleName) { + return new ClassType(simpleName, this); + } + + /** + * Allows build a signature like {@code Lcom/example/Outer.Inner;}. This is usually + * unnecessary, because {@code Lcom/example/Outer$Inner;} is also a valid signature, + * but it's occasionally useful to build a signature of more complex inner types. + * + * @param simpleName simple name of the generic member class nested in this class + * @return the inner parameterized type + */ + public ParameterizedType innerParameterizedType(String simpleName, Type... typeArguments) { + return new ParameterizedType(new ClassType(simpleName, null), Arrays.asList(typeArguments), this); + } + + @Override + void appendToSignature(StringBuilder signature) { + if (owner != null) { + // Append the owner class and replace the last semicolon with a period + owner.appendToSignature(signature); + signature.setCharAt(signature.length() - 1, '.'); + } else { + signature.append('L'); + } + signature.append(name).append(';'); + } + + @Override + boolean isClass() { + return true; + } + + @Override + ClassType asClass() { + return this; + } + } + + public static final class ArrayType extends Type { + private final Type elementType; + private final int dimensions; + + ArrayType(Type elementType, int dimensions) { + this.elementType = Objects.requireNonNull(elementType); + this.dimensions = dimensions; + } + + @Override + void appendToSignature(StringBuilder signature) { + for (int i = 0; i < dimensions; i++) { + signature.append('['); + } + elementType.appendToSignature(signature); + } + + @Override + boolean isArray() { + return true; + } + + @Override + ArrayType asArray() { + return this; + } + } + + public static final class ParameterizedType extends Type { + + final ClassType genericClass; + final List typeArguments; + final Type owner; + + ParameterizedType(ClassType genericClass, List typeArguments, Type owner) { + this.genericClass = Objects.requireNonNull(genericClass); + this.typeArguments = Objects.requireNonNull(typeArguments); + this.owner = owner; + } + + /** + * Allows build a signature like {@code Lcom/example/Outer.Inner;}. + * + * @param simpleName simple name of the member class nested in this parameterized type + * @return the inner class + */ + public ClassType innerClass(String simpleName) { + return new ClassType(simpleName, this); + } + + /** + * Allows building a signature like {@code Lcom/example/Outer.Inner;}. + * + * @param simpleName simple name of the generic member class nested in this parameterized type + * @return the inner parameterized type + */ + public ParameterizedType innerParameterizedType(String simpleName, Type... typeArguments) { + return new ParameterizedType(new ClassType(simpleName, null), Arrays.asList(typeArguments), this); + } + + @Override + void appendToSignature(StringBuilder signature) { + if (owner != null) { + // Append the owner class and replace the last semicolon with a period + owner.appendToSignature(signature); + signature.setCharAt(signature.length() - 1, '.'); + } else { + signature.append('L'); + } + signature.append(genericClass.name); + if (!typeArguments.isEmpty()) { + signature.append('<'); + for (Type typeArgument : typeArguments) { + typeArgument.appendToSignature(signature); + } + signature.append('>'); + } + signature.append(';'); + } + + @Override + boolean isParameterizedType() { + return true; + } + + @Override + ParameterizedType asParameterizedType() { + return this; + } + + List getTypeArguments() { + return Collections.unmodifiableList(typeArguments); + } + } + + public static final class TypeVariable extends Type { + private final String name; + private final Type firstBound; // may be null if all bounds are interfaces + private final List nextBounds; + + TypeVariable(String name, Type firstBound, List nextBounds) { + this.name = Objects.requireNonNull(name); + this.firstBound = firstBound; + this.nextBounds = Objects.requireNonNull(nextBounds); + } + + @Override + void appendToSignature(StringBuilder signature) { + signature.append('T').append(name).append(';'); + } + + void appendTypeParameterToSignature(StringBuilder signature) { + signature.append(name).append(":"); + if (firstBound != null) { + firstBound.appendToSignature(signature); + } + for (Type bound : nextBounds) { + signature.append(":"); + bound.appendToSignature(signature); + } + } + + @Override + boolean isTypeVariable() { + return true; + } + + @Override + TypeVariable asTypeVariable() { + return this; + } + } + + public static final class WildcardType extends Type { + private final Type upperBound; + private final Type lowerBound; + + WildcardType(Type upperBound, Type lowerBound) { + if (upperBound == null && lowerBound == null) { + throw new NullPointerException(); + } + if (upperBound != null && lowerBound != null) { + throw new IllegalArgumentException(); + } + this.upperBound = upperBound; + this.lowerBound = lowerBound; + } + + @Override + boolean isWildcard() { + return true; + } + + @Override + WildcardType asWildcard() { + return this; + } + + @Override + void appendToSignature(StringBuilder signature) { + if (lowerBound != null) { + signature.append('-'); + lowerBound.appendToSignature(signature); + } else if (upperBound.isClass() && upperBound.asClass().name.equals(ClassType.OBJECT.name)) { + signature.append('*'); + } else { + signature.append('+'); + upperBound.appendToSignature(signature); + } + } + } +} diff --git a/src/test/java/io/quarkus/gizmo/SignaturesTest.java b/src/test/java/io/quarkus/gizmo/SignaturesTest.java new file mode 100644 index 0000000..4ba0c6a --- /dev/null +++ b/src/test/java/io/quarkus/gizmo/SignaturesTest.java @@ -0,0 +1,303 @@ +package io.quarkus.gizmo; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +import org.jboss.jandex.DotName; +import org.junit.Test; + +import io.quarkus.gizmo.SignatureBuilder.ClassSignatureBuilder; +import io.quarkus.gizmo.SignaturesTest.Nested.Inner.Inner2; +import io.quarkus.gizmo.SignaturesTest.NestedParam.InnerParam; + +public class SignaturesTest { + + @Test + public void testMethodSignatures() { + // void test() + assertEquals("()V", + SignatureBuilder.forMethod().build()); + + // void test(long l) + assertEquals("(J)V", + SignatureBuilder.forMethod().addParameterType(Type.longType()).build()); + + // List test(List list) + assertEquals("(Ljava/util/List<*>;)Ljava/util/List;", + SignatureBuilder.forMethod() + .setReturnType(Type.parameterizedType(Type.classType(List.class), Type.classType(String.class))) + .addParameterType(Type.parameterizedType(Type.classType(List.class), Type.wildcardTypeUnbounded())) + .build()); + + // Object test() + assertEquals("()Ljava/lang/Object;", + SignatureBuilder.forMethod().setReturnType(Type.classType(DotName.OBJECT_NAME)).build()); + + // > String[] test(T t) + assertEquals(";>(TT;)[Ljava/lang/String;", + SignatureBuilder.forMethod() + .addTypeParameter(Type.typeVariable("T", null, + Type.parameterizedType(Type.classType(Comparable.class), Type.typeVariable("T")))) + .setReturnType(Type.arrayType(Type.classType(String.class))) + .addParameterType(Type.typeVariable("T")) + .build()); + + // List test(int a, T t) + assertEquals("(ITT;)Ljava/util/List;", + SignatureBuilder.forMethod() + .addTypeParameter(Type.typeVariable("R")) + .setReturnType( + Type.parameterizedType(Type.classType(DotName.createSimple(List.class)), + Type.typeVariable("R"))) + .addParameterType(Type.intType()) + .addParameterType(Type.typeVariable("T")) + .build()); + + // List test(int a, T t) + assertEquals("(ITT;)Ljava/util/List;", + SignatureBuilder.forMethod() + .addTypeParameter(Type.typeVariable("R")) + .addTypeParameter(Type.typeVariable("S", Type.typeVariable("R"))) + .setReturnType( + Type.parameterizedType(Type.classType(DotName.createSimple(List.class)), + Type.typeVariable("S"))) + .addParameterType(Type.intType()) + .addParameterType(Type.typeVariable("T")) + .build()); + + // > List test(int a, T t) + assertEquals(";>(ITT;)Ljava/util/List;", + SignatureBuilder.forMethod() + .addTypeParameter(Type.typeVariable("R", null, + Type.classType(Serializable.class), + Type.parameterizedType(Type.classType(Comparable.class), Type.typeVariable("R")))) + .setReturnType( + Type.parameterizedType(Type.classType(List.class), Type.typeVariable("R"))) + .addParameterType(Type.intType()) + .addParameterType(Type.typeVariable("T")) + .build()); + + // boolean test(int i) + assertEquals("(I)Z", + SignatureBuilder.forMethod() + .setReturnType(Type.booleanType()) + .addParameterType(Type.intType()).build()); + + // , U extends Comparable, V extends Exception> T bbb(U arg, W arg2, OuterParam self) + assertEquals( + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(TU;TW;Ltest/OuterParam;)TT;", + SignatureBuilder.forMethod() + .addTypeParameter(Type.typeVariable("T", Type.classType(Number.class), + Type.parameterizedType( + Type.classType(Comparable.class), + Type.typeVariable("T")))) + .addTypeParameter(Type.typeVariable("U", null, + Type.parameterizedType(Type.classType(Comparable.class), + Type.typeVariable("U")))) + .addTypeParameter(Type.typeVariable("V", Type.classType(Exception.class))) + .setReturnType(Type.typeVariable("T")) + .addParameterType(Type.typeVariable("U")) + .addParameterType(Type.typeVariable("W")) + .addParameterType(Type.parameterizedType(Type.classType("test/OuterParam"), + Type.typeVariable("W"))) + .build()); + + // , U extends Comparable, V extends Exception> T test(List arg, W arg2, NestedParam

.Inner arg3) throws IllegalArgumentException, V + assertEquals( + ";U::Ljava/lang/Comparable;V:Ljava/lang/Exception;>(Ljava/util/List<+TU;>;TW;Lio/quarkus/gizmo/SignaturesTest$NestedParam.Inner;)TT;^Ljava/lang/IllegalArgumentException;^TV;", + SignatureBuilder.forMethod() + .addTypeParameter(Type.typeVariable("T", Type.classType(Number.class), + Type.parameterizedType(Type.classType(Comparable.class), Type.typeVariable("T")))) + .addTypeParameter(Type.typeVariable("U", null, + Type.parameterizedType(Type.classType(Comparable.class), Type.typeVariable("U")))) + .addTypeParameter(Type.typeVariable("V", Type.classType(Exception.class))) + .setReturnType(Type.typeVariable("T")) + .addParameterType(Type.parameterizedType(Type.classType(List.class), + Type.wildcardTypeWithUpperBound(Type.typeVariable("U")))) + .addParameterType(Type.typeVariable("W")) + .addParameterType(Type.parameterizedType(Type.classType(NestedParam.class), Type.typeVariable("P")) + .innerClass(NestedParam.Inner.class.getSimpleName())) + .addException(Type.classType(IllegalArgumentException.class)) + .addException(Type.typeVariable("V")) + .build()); + + // Nested.Inner.Inner2 test() + assertEquals("()Lio/quarkus/gizmo/SignaturesTest$Nested$Inner$Inner2;", + SignatureBuilder.forMethod() + .setReturnType(Type.classType(Inner2.class)) + .build()); + } + + @Test + public void testFieldSignatures() { + // List foo; + assertEquals("Ljava/util/List;", + SignatureBuilder.forField() + .setType(Type.parameterizedType(Type.classType(List.class), Type.classType(String.class))) + .build()); + + // T foo; + assertEquals("TT;", + SignatureBuilder.forField() + .setType(Type.typeVariable("T")) + .build()); + + // List foo; + assertEquals("Ljava/util/List;", + SignatureBuilder.forField() + .setType(Type.parameterizedType(Type.classType(List.class), + Type.typeVariable("T", Type.classType(Number.class)))) + .build()); + + // double foo; + assertEquals("D", + SignatureBuilder.forField() + .setType(Type.doubleType()) + .build()); + + // List foo; + assertEquals("Ljava/util/List<*>;", + SignatureBuilder.forField() + .setType(Type.parameterizedType(Type.classType(List.class), Type.wildcardTypeUnbounded())) + .build()); + + // Map foo; + assertEquals("Ljava/util/Map<+Ljava/lang/Number;-Ljava/lang/Number;>;", + SignatureBuilder.forField() + .setType(Type.parameterizedType(Type.classType(Map.class), + Type.wildcardTypeWithUpperBound(Type.classType(Number.class)), + Type.wildcardTypeWithLowerBound(Type.classType(Number.class)))) + .build()); + + // Signature not needed + // Nested foo; + assertEquals("Lio/quarkus/gizmo/SignaturesTest$Nested;", + SignatureBuilder.forField() + .setType(Type.classType(Nested.class)) + .build()); + + // Signature not needed + // NestedParam.Inner.Inner2 foo; + assertEquals("Lio/quarkus/gizmo/SignaturesTest$NestedParam$InnerParam$Inner2;", + SignatureBuilder.forField() + .setType(Type.classType(NestedParam.InnerParam.Inner2.class)) + .build()); + + // NestedParam.InnerParam foo; + assertEquals("Lio/quarkus/gizmo/SignaturesTest$NestedParam.InnerParam;", + SignatureBuilder.forField() + .setType(Type.parameterizedType(Type.classType(NestedParam.class), Type.typeVariable("P")) + .innerParameterizedType(InnerParam.class.getSimpleName(), Type.typeVariable("P"))) + .build()); + } + + @Test + public void testClassSignatures() { + // class Foo + assertEquals("Ljava/lang/Object;", + SignatureBuilder.forClass() + .addTypeParameter(Type.typeVariable("T")) + .build()); + + // class Foo extends List + assertEquals("Ljava/util/List;", + SignatureBuilder.forClass() + .addTypeParameter(Type.typeVariable("T")) + .setSuperClass(Type.parameterizedType(Type.classType(List.class), Type.typeVariable("T"))) + .build()); + + // class Foo extends List implements Serializable, Comparable + assertEquals("Ljava/util/List;Ljava/io/Serializable;Ljava/lang/Comparable;", + SignatureBuilder.forClass() + .addTypeParameter(Type.typeVariable("T")) + .setSuperClass(Type.parameterizedType(Type.classType(List.class), Type.typeVariable("T"))) + .addInterface(Type.classType(Serializable.class)) + .addInterface(Type.parameterizedType(Type.classType(Comparable.class), Type.typeVariable("T"))) + .build()); + + // public class OuterParam { + // public interface NestedParam { + // } + // + // public class InnerParam { + // public class InnerInnerRaw { + // public class InnerInnerInnerParam { + // public class Test + // extends OuterParam.InnerParam.InnerInnerRaw.InnerInnerInnerParam + // implements NestedParam { + // } + // } + // } + // } + // } + assertEquals( + "Lio/quarkus/gizmo/test/OuterParam.InnerParam.InnerInnerRaw.InnerInnerInnerParam;Lio/quarkus/gizmo/test/OuterParam$NestedParam;", + SignatureBuilder.forClass() + .addTypeParameter(Type.typeVariable("X", Type.classType(String.class))) + .addTypeParameter(Type.typeVariable("Y", Type.classType(Integer.class))) + .setSuperClass( + Type.parameterizedType(Type.classType("io.quarkus.gizmo.test.OuterParam"), + Type.typeVariable("X")) + .innerParameterizedType("InnerParam", Type.typeVariable("Y")) + .innerClass("InnerInnerRaw") + .innerParameterizedType("InnerInnerInnerParam", Type.classType(String.class))) + .addInterface(Type.parameterizedType(Type.classType("io.quarkus.gizmo.test.OuterParam$NestedParam"), + Type.typeVariable("V"))) + .build()); + + try { + SignatureBuilder.forClass() + .setSuperClass(Type.parameterizedType(Type.classType(List.class), Type.wildcardTypeUnbounded())); + fail(); + } catch (Exception expected) { + } + } + + @Test + public void testClassCreatorSignatureBuilder() { + // class Foo extends List implements Serializable, Comparable + ClassSignatureBuilder classSignature = SignatureBuilder.forClass() + .addTypeParameter(Type.typeVariable("T")) + .setSuperClass(Type.parameterizedType(Type.classType(List.class), Type.typeVariable("T"))) + .addInterface(Type.classType(Serializable.class)) + .addInterface(Type.parameterizedType(Type.classType(Comparable.class), Type.typeVariable("T"))); + + ClassCreator creator = ClassCreator.builder().signature(classSignature).className("org.acme.Foo").build(); + assertEquals("java/util/List", creator.getSuperClass()); + assertEquals(2, creator.getInterfaces().length); + assertEquals("java/io/Serializable", creator.getInterfaces()[0]); + assertEquals("java/lang/Comparable", creator.getInterfaces()[1]); + } + + public static class Nested { + + public class Inner { + + class Inner2 { + + } + } + + } + + public static class NestedParam

{ + + InnerParam

inner; + + public class Inner { + } + + public class InnerParam { + + class Inner2 { + + } + } + + } + +}