From 2282fea666f784d7a173a87ff4538203cf8b5e76 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Tue, 30 Apr 2024 16:27:46 +0100 Subject: [PATCH 01/19] Add record support --- .../java/com/palantir/javapoet/TypeSpec.java | 58 ++++++- .../com/palantir/javapoet/JavaFileTest.java | 162 ++++++++++++++++++ 2 files changed, 211 insertions(+), 9 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index cd1bd84e..6e19b212 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -203,6 +203,14 @@ public static Builder classBuilder(ClassName className) { return classBuilder(checkNotNull(className, "className == null").simpleName()); } + public static Builder recordBuilder(String name) { + return new Builder(Kind.RECORD, checkNotNull(name, "name == null"), null); + } + + public static Builder recordBuilder(ClassName className) { + return recordBuilder(checkNotNull(className, "className == null").simpleName()); + } + public static Builder interfaceBuilder(String name) { return new Builder(Kind.INTERFACE, checkNotNull(name, "name == null"), null); } @@ -299,6 +307,11 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier if (kind == Kind.INTERFACE) { extendsTypes = superinterfaces; implementsTypes = Collections.emptyList(); + } else if (kind == Kind.RECORD) { + extendsTypes = Collections.emptyList(); + implementsTypes = superinterfaces; + // Record constructor + emitRecordConstructor(codeWriter); } else { extendsTypes = superclass.equals(ClassName.OBJECT) ? Collections.emptyList() @@ -393,15 +406,18 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier } // Non-static fields. - for (FieldSpec fieldSpec : fieldSpecs) { - if (fieldSpec.modifiers().contains(Modifier.STATIC)) { - continue; - } - if (!firstMember) { - codeWriter.emit("\n"); + // If kind RECORD, ignore generate Non-static field, records remove the need for them. + if (!(Kind.RECORD == kind)) { + for (FieldSpec fieldSpec : fieldSpecs) { + if (fieldSpec.modifiers().contains(Modifier.STATIC)) { + continue; + } + if (!firstMember) { + codeWriter.emit("\n"); + } + fieldSpec.emit(codeWriter, kind.implicitFieldModifiers); + firstMember = false; } - fieldSpec.emit(codeWriter, kind.implicitFieldModifiers); - firstMember = false; } // Initializer block. @@ -459,6 +475,28 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier } } + /** + * Emits the constructor parameters for a record type. i.e. {@code (String name, int count)}. + */ + private void emitRecordConstructor(CodeWriter codeWriter) throws IOException { + codeWriter.emit("("); + // Only include non-static fields in the constructor. + List nonStaticFields = fieldSpecs.stream() + .filter(field -> !field.modifiers().contains(Modifier.STATIC)) + .toList(); + boolean firstParameter = true; + for (FieldSpec fieldSpec : nonStaticFields) { + ParameterSpec parameter = + ParameterSpec.builder(fieldSpec.type(), fieldSpec.name()).build(); + if (!firstParameter) { + codeWriter.emit(",").emitWrappingSpace(); + } + parameter.emit(codeWriter, false); + firstParameter = false; + } + codeWriter.emit(")"); + } + @Override public boolean equals(Object o) { if (this == o) { @@ -510,7 +548,9 @@ public enum Kind { Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)), Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)), Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)), - Util.immutableSet(Collections.singletonList(Modifier.STATIC))); + Util.immutableSet(Collections.singletonList(Modifier.STATIC))), + + RECORD(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()); private final Set implicitFieldModifiers; private final Set implicitMethodModifiers; diff --git a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java index 2b124e28..d560db39 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThatCode; import com.google.testing.compile.CompilationRule; +import java.io.Serializable; import java.util.Collections; import java.util.Date; import java.util.List; @@ -387,6 +388,167 @@ class Taco { """); } + @Test + public void recordNoField() { + String source = JavaFile.builder( + "com.palantir.tacos", TypeSpec.recordBuilder("Taco").build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + record Taco() { + } + """); + } + + @Test + public void recordOneField() { + String source = JavaFile.builder( + "com.palantir.tacos", + TypeSpec.recordBuilder("Taco") + .addField( + FieldSpec.builder(String.class, "name").build()) + .build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + record Taco(String name) { + } + """); + } + + @Test + public void recordTwoField() { + String source = JavaFile.builder( + "com.palantir.tacos", + TypeSpec.recordBuilder("Taco") + .addField( + FieldSpec.builder(String.class, "name").build()) + .addField( + FieldSpec.builder(Integer.class, "code").build()) + .build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + record Taco(String name, Integer code) { + } + """); + } + + @Test + public void recordOneFieldImplementsInterface() { + String source = JavaFile.builder( + "com.palantir.tacos", + TypeSpec.recordBuilder("Taco") + .addField( + FieldSpec.builder(String.class, "name").build()) + .addSuperinterface(Serializable.class) + .build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + import java.io.Serializable; + + record Taco(String name) implements Serializable { + } + """); + } + + @Test + public void recordOneFieldWithAnnotation() { + String source = JavaFile.builder( + "com.palantir.tacos", + TypeSpec.recordBuilder("Taco") + .addField( + FieldSpec.builder(String.class, "name").build()) + .addAnnotation(Deprecated.class) + .build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + @Deprecated + record Taco(String name) { + } + """); + } + + @Test + public void recordOneFieldWithMethod() { + String source = JavaFile.builder( + "com.palantir.tacos", + TypeSpec.recordBuilder("Taco") + .addField( + FieldSpec.builder(String.class, "name").build()) + .addMethod(MethodSpec.methodBuilder("name") + .returns(String.class) + .addStatement("return name") + .build()) + .build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + record Taco(String name) { + String name() { + return name; + } + } + """); + } + + @Test + public void recordTwoFieldWhereOneIsStatic() { + String source = JavaFile.builder( + "com.palantir.tacos", + TypeSpec.recordBuilder("Taco") + .addField( + FieldSpec.builder(String.class, "name").build()) + .addField(FieldSpec.builder(Integer.class, "CODE") + .addModifiers(Modifier.STATIC) + .build()) + .build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + record Taco(String name) { + static Integer CODE; + } + """); + } + @Test public void skipJavaLangImportsWithConflictingClassLast() { // Whatever is used first wins! In this case the Float in java.lang is imported. From 234533170fded72bd1ed6b23b5576a558b55e2ea Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Tue, 30 Apr 2024 20:43:14 +0100 Subject: [PATCH 02/19] Comments --- .../com/palantir/javapoet/MethodSpec.java | 49 ++- .../java/com/palantir/javapoet/TypeSpec.java | 332 ++++++++++-------- .../com/palantir/javapoet/JavaFileTest.java | 91 ++++- 3 files changed, 300 insertions(+), 172 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java index 94bb82c8..8df46831 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java @@ -42,7 +42,7 @@ /** A generated constructor or method declaration. */ public final class MethodSpec { - private static final String CONSTRUCTOR = ""; + public static final String CONSTRUCTOR = ""; private final String name; private final CodeBlock javadoc; @@ -133,7 +133,8 @@ private boolean lastParameterIsArray(List parameters) { && TypeName.asArray(parameters.get(parameters.size() - 1).type()) != null; } - void emit(CodeWriter codeWriter, String enclosingName, Set implicitModifiers) throws IOException { + void emit(CodeWriter codeWriter, String enclosingName, Set implicitModifiers, boolean compactConstructor) + throws IOException { codeWriter.emitJavadoc(javadocWithParameters()); codeWriter.emitAnnotations(annotations, false); codeWriter.emitModifiers(modifiers, implicitModifiers); @@ -143,24 +144,17 @@ void emit(CodeWriter codeWriter, String enclosingName, Set implicitMod codeWriter.emit(" "); } - if (isConstructor()) { - codeWriter.emit("$L($Z", enclosingName); + if (compactConstructor) { + codeWriter.emit("$L", enclosingName); } else { - codeWriter.emit("$T $L($Z", returnType, name); - } - - boolean firstParameter = true; - for (Iterator i = parameters.iterator(); i.hasNext(); ) { - ParameterSpec parameter = i.next(); - if (!firstParameter) { - codeWriter.emit(",").emitWrappingSpace(); + if (isConstructor()) { + codeWriter.emit("$L", enclosingName); + } else { + codeWriter.emit("$T $L", returnType, name); } - parameter.emit(codeWriter, !i.hasNext() && varargs); - firstParameter = false; + emitParameters(codeWriter, parameters, varargs); } - codeWriter.emit(")"); - if (defaultValue != null && !defaultValue.isEmpty()) { codeWriter.emit(" default "); codeWriter.emit(defaultValue); @@ -196,7 +190,28 @@ void emit(CodeWriter codeWriter, String enclosingName, Set implicitMod codeWriter.popTypeVariables(typeVariables); } + static void emitParameters(CodeWriter codeWriter, Iterable parameters, boolean varargs) + throws IOException { + codeWriter.emit(CodeBlock.of("($Z")); + + boolean firstParameter = true; + for (Iterator i = parameters.iterator(); i.hasNext(); ) { + ParameterSpec parameter = i.next(); + if (!firstParameter) { + codeWriter.emit(",").emitWrappingSpace(); + } + parameter.emit(codeWriter, !i.hasNext() && varargs); + firstParameter = false; + } + + codeWriter.emit(")"); + } + private CodeBlock javadocWithParameters() { + return makeJavadocWithParameters(javadoc, parameters); + } + + static CodeBlock makeJavadocWithParameters(CodeBlock javadoc, Iterable parameters) { CodeBlock.Builder builder = javadoc.toBuilder(); boolean emitTagNewline = true; for (ParameterSpec parameterSpec : parameters) { @@ -236,7 +251,7 @@ public String toString() { StringBuilder out = new StringBuilder(); try { CodeWriter codeWriter = new CodeWriter(out); - emit(codeWriter, "Constructor", Collections.emptySet()); + emit(codeWriter, "Constructor", Collections.emptySet(), false); return out.toString(); } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index 6e19b212..0a1cd232 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -66,6 +66,9 @@ public final class TypeSpec { private final Set nestedTypesSimpleNames; private final List originatingElements; private final Set alwaysQualifiedNames; + private final List recordComponents; + private final boolean varargs; + private final MethodSpec compactConstructor; private TypeSpec(Builder builder) { this.kind = builder.kind; @@ -85,6 +88,9 @@ private TypeSpec(Builder builder) { this.methodSpecs = Util.immutableList(builder.methodSpecs); this.typeSpecs = Util.immutableList(builder.typeSpecs); this.alwaysQualifiedNames = Util.immutableSet(builder.alwaysQualifiedNames); + this.recordComponents = Util.immutableList(builder.recordComponents); + this.varargs = builder.varargs; + this.compactConstructor = builder.compactConstructor; nestedTypesSimpleNames = new HashSet<>(); List originatingElementsMutable = new ArrayList<>(); @@ -121,6 +127,9 @@ private TypeSpec(TypeSpec type) { this.originatingElements = Collections.emptyList(); this.nestedTypesSimpleNames = Collections.emptySet(); this.alwaysQualifiedNames = Collections.emptySet(); + this.recordComponents = Collections.emptyList(); + this.varargs = false; + this.compactConstructor = null; } public Kind kind() { @@ -263,101 +272,102 @@ public Builder toBuilder() { return builder; } - void emit(CodeWriter codeWriter, String enumName, Set implicitModifiers) throws IOException { - // Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put - // it back afterwards when this type is complete. - int previousStatementLine = codeWriter.statementLine; - codeWriter.statementLine = -1; - - try { - if (enumName != null) { - codeWriter.emitJavadoc(javadoc); - codeWriter.emitAnnotations(annotations, false); - codeWriter.emit("$L", enumName); - if (!anonymousTypeArguments.formatParts().isEmpty()) { - codeWriter.emit("("); - codeWriter.emit(anonymousTypeArguments); - codeWriter.emit(")"); - } - if (fieldSpecs.isEmpty() && methodSpecs.isEmpty() && typeSpecs.isEmpty()) { - return; // Avoid unnecessary braces "{}". - } - codeWriter.emit(" {\n"); - } else if (anonymousTypeArguments != null) { - TypeName supertype = !superinterfaces.isEmpty() ? superinterfaces.get(0) : superclass; - codeWriter.emit("new $T(", supertype); + /** + * Emits the header of the type spec and returns if the body should be emitted. + */ + private boolean emitHeader(CodeWriter codeWriter, String enumName, Set implicitModifiers) + throws IOException { + if (enumName != null) { + codeWriter.emitJavadoc(javadoc); + codeWriter.emitAnnotations(annotations, false); + codeWriter.emit("$L", enumName); + if (!anonymousTypeArguments.formatParts().isEmpty()) { + codeWriter.emit("("); codeWriter.emit(anonymousTypeArguments); - codeWriter.emit(") {\n"); + codeWriter.emit(")"); + } + if (fieldSpecs.isEmpty() && methodSpecs.isEmpty() && typeSpecs.isEmpty()) { + return false; // Avoid unnecessary braces "{}". + } + codeWriter.emit(" {\n"); + } else if (anonymousTypeArguments != null) { + TypeName supertype = !superinterfaces.isEmpty() ? superinterfaces.get(0) : superclass; + codeWriter.emit("new $T(", supertype); + codeWriter.emit(anonymousTypeArguments); + codeWriter.emit(") {\n"); + } else { + // Push an empty type (specifically without nested types) for type-resolution. + codeWriter.pushType(new TypeSpec(this)); + + codeWriter.emitJavadoc( + kind == Kind.RECORD ? MethodSpec.makeJavadocWithParameters(javadoc, recordComponents) : javadoc); + codeWriter.emitAnnotations(annotations, false); + codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers)); + if (kind == Kind.ANNOTATION) { + codeWriter.emit("$L $L", "@interface", name); } else { - // Push an empty type (specifically without nested types) for type-resolution. - codeWriter.pushType(new TypeSpec(this)); - - codeWriter.emitJavadoc(javadoc); - codeWriter.emitAnnotations(annotations, false); - codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers)); - if (kind == Kind.ANNOTATION) { - codeWriter.emit("$L $L", "@interface", name); - } else { - codeWriter.emit("$L $L", kind.toString(), name); - } - codeWriter.emitTypeVariables(typeVariables); + codeWriter.emit("$L $L", kind.name().toLowerCase(Locale.US), name); + } + codeWriter.emitTypeVariables(typeVariables); - List extendsTypes; - List implementsTypes; - if (kind == Kind.INTERFACE) { - extendsTypes = superinterfaces; - implementsTypes = Collections.emptyList(); - } else if (kind == Kind.RECORD) { - extendsTypes = Collections.emptyList(); - implementsTypes = superinterfaces; - // Record constructor - emitRecordConstructor(codeWriter); - } else { - extendsTypes = superclass.equals(ClassName.OBJECT) - ? Collections.emptyList() - : Collections.singletonList(superclass); - implementsTypes = superinterfaces; - } + // Record components. + if (kind == Kind.RECORD) { + MethodSpec.emitParameters(codeWriter, recordComponents, varargs); + } - if (!extendsTypes.isEmpty()) { - codeWriter.emit(" extends"); - boolean firstType = true; - for (TypeName type : extendsTypes) { - if (!firstType) { - codeWriter.emit(","); - } - codeWriter.emit(" $T", type); - firstType = false; - } - } + List extendsTypes; + List implementsTypes; + if (kind == Kind.INTERFACE) { + extendsTypes = superinterfaces; + implementsTypes = Collections.emptyList(); + } else { + extendsTypes = superclass.equals(ClassName.OBJECT) + ? Collections.emptyList() + : Collections.singletonList(superclass); + implementsTypes = superinterfaces; + } - if (!implementsTypes.isEmpty()) { - codeWriter.emit(" implements"); - boolean firstType = true; - for (TypeName type : implementsTypes) { - if (!firstType) { - codeWriter.emit(","); - } - codeWriter.emit(" $T", type); - firstType = false; + if (!extendsTypes.isEmpty()) { + codeWriter.emit(" extends"); + boolean firstType = true; + for (TypeName type : extendsTypes) { + if (!firstType) { + codeWriter.emit(","); } + codeWriter.emit(" $T", type); + firstType = false; } + } - if (!permittedSubclasses.isEmpty()) { - codeWriter.emit(" permits"); - boolean firstType = true; - for (TypeName type : permittedSubclasses) { - if (!firstType) { - codeWriter.emit(","); - } - codeWriter.emit(" $T", type); - firstType = false; + if (!implementsTypes.isEmpty()) { + codeWriter.emit(" implements"); + boolean firstType = true; + for (TypeName type : implementsTypes) { + if (!firstType) { + codeWriter.emit(","); } + codeWriter.emit(" $T", type); + firstType = false; } + } + + codeWriter.popType(); + codeWriter.emit(" {\n"); + } - codeWriter.popType(); + return true; + } - codeWriter.emit(" {\n"); + void emit(CodeWriter codeWriter, String enumName, Set implicitModifiers) throws IOException { + // Nested classes interrupt wrapped line indentation. + // Stash the current wrapping state and put + // it back afterwards when this type is complete. + int previousStatementLine = codeWriter.statementLine; + codeWriter.statementLine = -1; + + try { + if (!emitHeader(codeWriter, enumName, implicitModifiers)) { + return; } codeWriter.pushType(this); @@ -406,58 +416,50 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier } // Non-static fields. - // If kind RECORD, ignore generate Non-static field, records remove the need for them. - if (!(Kind.RECORD == kind)) { - for (FieldSpec fieldSpec : fieldSpecs) { - if (fieldSpec.modifiers().contains(Modifier.STATIC)) { - continue; - } - if (!firstMember) { - codeWriter.emit("\n"); - } - fieldSpec.emit(codeWriter, kind.implicitFieldModifiers); - firstMember = false; + for (FieldSpec fieldSpec : fieldSpecs) { + if (fieldSpec.modifiers().contains(Modifier.STATIC)) { + continue; + } + if (!firstMember) { + codeWriter.emit("\n"); } + fieldSpec.emit(codeWriter, kind.implicitFieldModifiers); + firstMember = false; } // Initializer block. if (!initializerBlock.isEmpty()) { - if (!firstMember) { - codeWriter.emit("\n"); - } + if (!firstMember) codeWriter.emit("\n"); codeWriter.emit(initializerBlock); firstMember = false; } + // Compact constructor. + if (compactConstructor != null) { + if (!firstMember) codeWriter.emit("\n"); + compactConstructor.emit(codeWriter, name, kind.implicitMethodModifiers, true); + firstMember = false; + } + // Constructors. for (MethodSpec methodSpec : methodSpecs) { - if (!methodSpec.isConstructor()) { - continue; - } - if (!firstMember) { - codeWriter.emit("\n"); - } - methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers); + if (!methodSpec.isConstructor()) continue; + if (!firstMember) codeWriter.emit("\n"); + methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers, false); firstMember = false; } // Methods (static and non-static). for (MethodSpec methodSpec : methodSpecs) { - if (methodSpec.isConstructor()) { - continue; - } - if (!firstMember) { - codeWriter.emit("\n"); - } - methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers); + if (methodSpec.isConstructor()) continue; + if (!firstMember) codeWriter.emit("\n"); + methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers, false); firstMember = false; } // Types. for (TypeSpec typeSpec : typeSpecs) { - if (!firstMember) { - codeWriter.emit("\n"); - } + if (!firstMember) codeWriter.emit("\n"); typeSpec.emit(codeWriter, null, kind.implicitTypeModifiers); firstMember = false; } @@ -475,28 +477,6 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier } } - /** - * Emits the constructor parameters for a record type. i.e. {@code (String name, int count)}. - */ - private void emitRecordConstructor(CodeWriter codeWriter) throws IOException { - codeWriter.emit("("); - // Only include non-static fields in the constructor. - List nonStaticFields = fieldSpecs.stream() - .filter(field -> !field.modifiers().contains(Modifier.STATIC)) - .toList(); - boolean firstParameter = true; - for (FieldSpec fieldSpec : nonStaticFields) { - ParameterSpec parameter = - ParameterSpec.builder(fieldSpec.type(), fieldSpec.name()).build(); - if (!firstParameter) { - codeWriter.emit(",").emitWrappingSpace(); - } - parameter.emit(codeWriter, false); - firstParameter = false; - } - codeWriter.emit(")"); - } - @Override public boolean equals(Object o) { if (this == o) { @@ -596,6 +576,10 @@ public static final class Builder { private final List originatingElements = new ArrayList<>(); private final Set alwaysQualifiedNames = new LinkedHashSet<>(); + private final List recordComponents = new ArrayList<>(); + private boolean varargs; + private MethodSpec compactConstructor; + private Builder(Kind kind, String name, CodeBlock anonymousTypeArguments) { checkArgument(name == null || SourceVersion.isName(name), "not a valid name: %s", name); this.kind = kind; @@ -731,6 +715,52 @@ public Builder addSuperinterface(TypeMirror superinterface, boolean avoidNestedT return this; } + public Builder addRecordComponents(Iterable parameterSpecs) { + checkArgument(parameterSpecs != null, "fieldSpecs == null"); + for (ParameterSpec parameterSpec : parameterSpecs) { + addRecordComponent(parameterSpec); + } + return this; + } + + public Builder addRecordComponent(ParameterSpec parameterSpec) { + recordComponents.add(parameterSpec); + return this; + } + + public Builder varargs() { + return varargs(true); + } + + public Builder varargs(boolean varargs) { + this.varargs = varargs; + return this; + } + + /** + * Sets the compact constructor for this builder. Its parameters are solely used for javadoc + * generation. + */ + public Builder compactConstructor(MethodSpec methodSpec) { + if (kind != Kind.RECORD) { + throw new UnsupportedOperationException(kind + " can't have compact constructors"); + } + + checkState(compactConstructor == null, "%s already has a compact constructor", name); + + checkState(methodSpec.name().equals(MethodSpec.CONSTRUCTOR), "compact constructor is not a constructor"); + EnumSet possibleModifiers = EnumSet.of(Modifier.PUBLIC, Modifier.PROTECTED, Modifier.PRIVATE); + checkState( + possibleModifiers.containsAll(methodSpec.modifiers()), + "compact constructor has non-access modifiers"); + checkState(methodSpec.modifiers().size() <= 1, "compact constructor has too many access modifiers"); + checkState(methodSpec.typeVariables().isEmpty(), "compact constructor has type variables"); + checkState(methodSpec.exceptions().isEmpty(), "compact constructor has exceptions"); + checkState(methodSpec.defaultValue() == null, "compact constructor has a default value"); + compactConstructor = methodSpec; + return this; + } + public Builder addPermittedSubclasses(Iterable permittedSubclasses) { checkArgument(permittedSubclasses != null, "permittedSubclasses == null"); for (TypeName permittedSubclass : permittedSubclasses) { @@ -953,6 +983,17 @@ public TypeSpec build() { } } + if (kind == Kind.RECORD) { + checkState(!modifiers.contains(Modifier.ABSTRACT), "abstract record"); + } + + if (!recordComponents.isEmpty()) { + checkState(kind == Kind.RECORD, "%s is not record", this.name); + for (ParameterSpec recordComponent : recordComponents) { + checkState(recordComponent != null, "recordComponents contain null"); + checkState(recordComponent.modifiers().isEmpty(), "recordComponents has modifier"); + } + } for (TypeName superinterface : superinterfaces) { checkArgument(superinterface != null, "superinterfaces contains null"); } @@ -988,6 +1029,15 @@ public TypeSpec build() { fieldSpec.name(), check); } + + if (kind == Kind.RECORD) { + checkState( + fieldSpec.modifiers().contains(Modifier.STATIC), + "%s %s.%s must be static", + kind, + name, + fieldSpec.name()); + } } for (MethodSpec methodSpec : methodSpecs) { @@ -1035,6 +1085,14 @@ public TypeSpec build() { name, methodSpec.name()); } + if (kind == Kind.RECORD) { + checkState( + !methodSpec.modifiers().contains(Modifier.NATIVE), + "%s %s.%s cannot be native", + kind, + name, + methodSpec.name()); + } } for (TypeSpec typeSpec : typeSpecs) { @@ -1047,7 +1105,7 @@ public TypeSpec build() { kind.implicitTypeModifiers); } - boolean isAbstract = modifiers.contains(Modifier.ABSTRACT) || kind != Kind.CLASS; + boolean isAbstract = modifiers.contains(Modifier.ABSTRACT) || (kind != Kind.CLASS && kind != Kind.RECORD); for (MethodSpec methodSpec : methodSpecs) { checkArgument( isAbstract || !methodSpec.modifiers().contains(Modifier.ABSTRACT), diff --git a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java index d560db39..ce2d6437 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java @@ -410,8 +410,8 @@ public void recordOneField() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addField( - FieldSpec.builder(String.class, "name").build()) + .addRecordComponent(ParameterSpec.builder(String.class, "name") + .build()) .build()) .skipJavaLangImports(true) .build() @@ -426,15 +426,42 @@ record Taco(String name) { """); } + @Test + public void recordOneFieldWithGeneric() { + String source = JavaFile.builder( + "com.palantir.tacos", + TypeSpec.recordBuilder("Taco") + .addTypeVariable(TypeVariableName.get("T")) + .addRecordComponent(ParameterSpec.builder( + ParameterizedTypeName.get( + ClassName.get(List.class), TypeVariableName.get("T")), + "names") + .build()) + .build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + import java.util.List; + + record Taco(List names) { + } + """); + } + @Test public void recordTwoField() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addField( - FieldSpec.builder(String.class, "name").build()) - .addField( - FieldSpec.builder(Integer.class, "code").build()) + .addRecordComponent(ParameterSpec.builder(String.class, "name") + .build()) + .addRecordComponent(ParameterSpec.builder(Integer.class, "code") + .build()) .build()) .skipJavaLangImports(true) .build() @@ -454,8 +481,8 @@ public void recordOneFieldImplementsInterface() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addField( - FieldSpec.builder(String.class, "name").build()) + .addRecordComponent(ParameterSpec.builder(String.class, "name") + .build()) .addSuperinterface(Serializable.class) .build()) .skipJavaLangImports(true) @@ -478,8 +505,8 @@ public void recordOneFieldWithAnnotation() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addField( - FieldSpec.builder(String.class, "name").build()) + .addRecordComponent(ParameterSpec.builder(String.class, "name") + .build()) .addAnnotation(Deprecated.class) .build()) .skipJavaLangImports(true) @@ -501,8 +528,8 @@ public void recordOneFieldWithMethod() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addField( - FieldSpec.builder(String.class, "name").build()) + .addRecordComponent(ParameterSpec.builder(String.class, "name") + .build()) .addMethod(MethodSpec.methodBuilder("name") .returns(String.class) .addStatement("return name") @@ -525,14 +552,40 @@ String name() { } @Test - public void recordTwoFieldWhereOneIsStatic() { + public void recordWithVarArgs() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addField( - FieldSpec.builder(String.class, "name").build()) - .addField(FieldSpec.builder(Integer.class, "CODE") - .addModifiers(Modifier.STATIC) + .addRecordComponent(ParameterSpec.builder(String.class, "id") + .build()) + .addRecordComponent( + ParameterSpec.builder(ArrayTypeName.of(ClassName.get(String.class)), "names") + .build()) + .varargs() + .build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + record Taco(String id, String... names) { + } + """); + } + + @Test + public void secondaryConstructorRecord() { + String source = JavaFile.builder( + "com.palantir.tacos", + TypeSpec.recordBuilder("Taco") + .addRecordComponent(ParameterSpec.builder(ClassName.get(String.class), "name") + .build()) + .addMethod(MethodSpec.constructorBuilder() + .addParameter(TypeName.INT, "number") + .addCode("this.name = $T.toString(number);", ClassName.get(Integer.class)) .build()) .build()) .skipJavaLangImports(true) @@ -544,7 +597,9 @@ public void recordTwoFieldWhereOneIsStatic() { package com.palantir.tacos; record Taco(String name) { - static Integer CODE; + Taco(int number) { + this.name = Integer.toString(number); + } } """); } From d5ef9c7f7e327d87163b6f32a791bd7d60d6beeb Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Tue, 30 Apr 2024 20:44:56 +0100 Subject: [PATCH 03/19] format --- .../java/com/palantir/javapoet/TypeSpec.java | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index 0a1cd232..b4d44931 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -429,37 +429,51 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier // Initializer block. if (!initializerBlock.isEmpty()) { - if (!firstMember) codeWriter.emit("\n"); + if (!firstMember) { + codeWriter.emit("\n"); + } codeWriter.emit(initializerBlock); firstMember = false; } // Compact constructor. if (compactConstructor != null) { - if (!firstMember) codeWriter.emit("\n"); + if (!firstMember) { + codeWriter.emit("\n"); + } compactConstructor.emit(codeWriter, name, kind.implicitMethodModifiers, true); firstMember = false; } // Constructors. for (MethodSpec methodSpec : methodSpecs) { - if (!methodSpec.isConstructor()) continue; - if (!firstMember) codeWriter.emit("\n"); + if (!methodSpec.isConstructor()) { + continue; + } + if (!firstMember) { + codeWriter.emit("\n"); + } methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers, false); firstMember = false; } // Methods (static and non-static). for (MethodSpec methodSpec : methodSpecs) { - if (methodSpec.isConstructor()) continue; - if (!firstMember) codeWriter.emit("\n"); + if (methodSpec.isConstructor()) { + continue; + } + if (!firstMember) { + codeWriter.emit("\n"); + } methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers, false); firstMember = false; } // Types. for (TypeSpec typeSpec : typeSpecs) { - if (!firstMember) codeWriter.emit("\n"); + if (!firstMember) { + codeWriter.emit("\n"); + } typeSpec.emit(codeWriter, null, kind.implicitTypeModifiers); firstMember = false; } From 285cb59dbe220a0d4b5b258e8c54f7a80a02befd Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Tue, 30 Apr 2024 21:54:40 +0100 Subject: [PATCH 04/19] fic --- .../java/com/palantir/javapoet/TypeSpec.java | 12 +++++++ .../com/palantir/javapoet/JavaFileTest.java | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index b4d44931..585a991b 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -351,6 +351,18 @@ private boolean emitHeader(CodeWriter codeWriter, String enumName, Set } } + if (!permittedSubclasses.isEmpty()) { + codeWriter.emit(" permits"); + boolean firstType = true; + for (TypeName type : permittedSubclasses) { + if (!firstType) { + codeWriter.emit(","); + } + codeWriter.emit(" $T", type); + firstType = false; + } + } + codeWriter.popType(); codeWriter.emit(" {\n"); } diff --git a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java index ce2d6437..1320fea8 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java @@ -604,6 +604,42 @@ record Taco(String name) { """); } + @Test + public void recordWithCompactConstructor() { + ParameterSpec name = + ParameterSpec.builder(ClassName.get(String.class), "name").build(); + String source = JavaFile.builder( + "com.palantir.tacos", + TypeSpec.recordBuilder("Taco") + .addRecordComponent(name) + .compactConstructor(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addCode(CodeBlock.builder() + .beginControlFlow("if ($N.isEmpty())", name) + .addStatement( + "throw new $T()", ClassName.get(IllegalArgumentException.class)) + .endControlFlow() + .build()) + .build()) + .build()) + .skipJavaLangImports(true) + .build() + .toString(); + assertThat(source) + .isEqualTo( + """ + package com.palantir.tacos; + + record Taco(String name) { + public Taco { + if (name.isEmpty()) { + throw new IllegalArgumentException(); + } + } + } + """); + } + @Test public void skipJavaLangImportsWithConflictingClassLast() { // Whatever is used first wins! In this case the Float in java.lang is imported. From 0a4123df90a1112b8e688451526398625b7c46fe Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Tue, 30 Apr 2024 22:02:43 +0100 Subject: [PATCH 05/19] more tests --- .../com/palantir/javapoet/TypeSpecTest.java | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java index 44cdc558..73b1f613 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java @@ -851,6 +851,62 @@ sealed interface Taco extends Serializable, Comparable permits BeefTaco, C """); } + @Test + public void recordOneField() { + TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") + .addRecordComponent(ParameterSpec.builder(String.class, "name").build()) + .build(); + assertThat(toString(typeSpec)) + .isEqualTo( + """ + package com.palantir.tacos; + + import java.lang.String; + + record Taco(String name) { + } + """); + } + + @Test + public void recordTwoFields() { + TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") + .addRecordComponent(ParameterSpec.builder(String.class, "name").build()) + .addRecordComponent(ParameterSpec.builder(Integer.class, "size").build()) + .build(); + assertThat(toString(typeSpec)) + .isEqualTo( + """ + package com.palantir.tacos; + + import java.lang.Integer; + import java.lang.String; + + record Taco(String name, Integer size) { + } + """); + } + + @Test + public void recordWithVarArgs() { + TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") + .addRecordComponent(ParameterSpec.builder(String.class, "id").build()) + .addRecordComponent(ParameterSpec.builder(ArrayTypeName.of(ClassName.get(String.class)), "names") + .build()) + .varargs() + .build(); + assertThat(toString(typeSpec)) + .isEqualTo( + """ + package com.palantir.tacos; + + import java.lang.String; + + record Taco(String id, String... names) { + } + """); + } + @Test public void nestedClasses() { ClassName taco = ClassName.get(tacosPackage, "Combo", "Taco"); From 0e18151416bb04dceee7e0a46ba8fb743174b68f Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Tue, 30 Apr 2024 22:12:55 +0100 Subject: [PATCH 06/19] more test --- .../com/palantir/javapoet/MethodSpec.java | 10 +++++--- .../com/palantir/javapoet/TypeSpecTest.java | 24 +++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java index 8df46831..ed35e9d6 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java @@ -195,18 +195,22 @@ static void emitParameters(CodeWriter codeWriter, Iterable parame codeWriter.emit(CodeBlock.of("($Z")); boolean firstParameter = true; - for (Iterator i = parameters.iterator(); i.hasNext(); ) { - ParameterSpec parameter = i.next(); + for (Iterator parameterSpec = parameters.iterator(); parameterSpec.hasNext(); ) { + ParameterSpec parameter = parameterSpec.next(); if (!firstParameter) { codeWriter.emit(",").emitWrappingSpace(); } - parameter.emit(codeWriter, !i.hasNext() && varargs); + parameter.emit(codeWriter, isVarargs(varargs, parameterSpec)); firstParameter = false; } codeWriter.emit(")"); } + private static boolean isVarargs(boolean varargs, Iterator parameterSpec) { + return !parameterSpec.hasNext() && varargs; + } + private CodeBlock javadocWithParameters() { return makeJavadocWithParameters(javadoc, parameters); } diff --git a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java index 73b1f613..c156dbd3 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java @@ -907,6 +907,30 @@ record Taco(String id, String... names) { """); } + @Test + public void recordWithJavadoc() { + TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") + .addRecordComponent(ParameterSpec.builder(String.class, "id") + .addJavadoc("Id of the taco.") + .build()) + .addJavadoc("A taco class that stores the id of a taco.") + .build(); + assertThat(toString(typeSpec)) + .isEqualTo( + """ + package com.palantir.tacos; + + import java.lang.String; + + /** + * A taco class that stores the id of a taco. + * @param id Id of the taco. + */ + record Taco(String id) { + } + """); + } + @Test public void nestedClasses() { ClassName taco = ClassName.get(tacosPackage, "Combo", "Taco"); From 7c02b21919e103e8389ced9979418cda2462a5dd Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Tue, 30 Apr 2024 22:15:25 +0100 Subject: [PATCH 07/19] more tests --- .../com/palantir/javapoet/TypeSpecTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java index c156dbd3..affb0b15 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java @@ -38,6 +38,7 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; +import org.jetbrains.annotations.NotNull; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -931,6 +932,26 @@ record Taco(String id) { """); } + @Test + public void recordWithAnnotationOnParam() { + TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") + .addRecordComponent(ParameterSpec.builder(String.class, "id") + .addAnnotation(NotNull.class) + .build()) + .build(); + assertThat(toString(typeSpec)) + .isEqualTo( + """ + package com.palantir.tacos; + + import java.lang.String; + import org.jetbrains.annotations.NotNull; + + record Taco(@NotNull String id) { + } + """); + } + @Test public void nestedClasses() { ClassName taco = ClassName.get(tacosPackage, "Combo", "Taco"); From 339c5000660798ac46cb553bdf5c7048437adf99 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Wed, 1 May 2024 13:45:37 +0100 Subject: [PATCH 08/19] test --- .../src/test/java/com/palantir/javapoet/TypeSpecTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java index affb0b15..b9bf0f6b 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java @@ -38,7 +38,6 @@ import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; -import org.jetbrains.annotations.NotNull; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -936,7 +935,7 @@ record Taco(String id) { public void recordWithAnnotationOnParam() { TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") .addRecordComponent(ParameterSpec.builder(String.class, "id") - .addAnnotation(NotNull.class) + .addAnnotation(Deprecated.class) .build()) .build(); assertThat(toString(typeSpec)) @@ -944,10 +943,10 @@ public void recordWithAnnotationOnParam() { """ package com.palantir.tacos; + import java.lang.Deprecated; import java.lang.String; - import org.jetbrains.annotations.NotNull; - record Taco(@NotNull String id) { + record Taco(@Deprecated String id) { } """); } From 4083c47ba723453383d1ff28a20d11f64c226903 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Wed, 1 May 2024 18:00:16 +0100 Subject: [PATCH 09/19] first set of comments --- .../com/palantir/javapoet/CodeWriter.java | 17 +++++++++ .../com/palantir/javapoet/MethodSpec.java | 35 ++++--------------- .../java/com/palantir/javapoet/TypeSpec.java | 34 ++++-------------- .../com/palantir/javapoet/TypeSpecTest.java | 22 ++++++------ 4 files changed, 41 insertions(+), 67 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/CodeWriter.java b/javapoet/src/main/java/com/palantir/javapoet/CodeWriter.java index c2d3d79a..17ba7e83 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/CodeWriter.java +++ b/javapoet/src/main/java/com/palantir/javapoet/CodeWriter.java @@ -25,6 +25,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -224,6 +225,22 @@ public void emitTypeVariables(List typeVariables) throws IOExc emit(">"); } + public void emitParameters(Iterable parameters, boolean varargs) throws IOException { + emit(CodeBlock.of("($Z")); + + boolean firstParameter = true; + for (Iterator parameterSpec = parameters.iterator(); parameterSpec.hasNext(); ) { + ParameterSpec parameter = parameterSpec.next(); + if (!firstParameter) { + emit(",").emitWrappingSpace(); + } + parameter.emit(this, !parameterSpec.hasNext() && varargs); + firstParameter = false; + } + + emit(")"); + } + public void popTypeVariables(List typeVariables) { typeVariables.forEach(typeVariable -> currentTypeVariables.remove(typeVariable.name())); } diff --git a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java index ed35e9d6..4c3d01fb 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java @@ -24,7 +24,6 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collections; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -42,7 +41,7 @@ /** A generated constructor or method declaration. */ public final class MethodSpec { - public static final String CONSTRUCTOR = ""; + private static final String CONSTRUCTOR = ""; private final String name; private final CodeBlock javadoc; @@ -146,13 +145,12 @@ void emit(CodeWriter codeWriter, String enclosingName, Set implicitMod if (compactConstructor) { codeWriter.emit("$L", enclosingName); + } else if (isConstructor()) { + codeWriter.emit("$L", enclosingName); + codeWriter.emitParameters(parameters, varargs); } else { - if (isConstructor()) { - codeWriter.emit("$L", enclosingName); - } else { - codeWriter.emit("$T $L", returnType, name); - } - emitParameters(codeWriter, parameters, varargs); + codeWriter.emit("$T $L", returnType, name); + codeWriter.emitParameters(parameters, varargs); } if (defaultValue != null && !defaultValue.isEmpty()) { @@ -190,27 +188,6 @@ void emit(CodeWriter codeWriter, String enclosingName, Set implicitMod codeWriter.popTypeVariables(typeVariables); } - static void emitParameters(CodeWriter codeWriter, Iterable parameters, boolean varargs) - throws IOException { - codeWriter.emit(CodeBlock.of("($Z")); - - boolean firstParameter = true; - for (Iterator parameterSpec = parameters.iterator(); parameterSpec.hasNext(); ) { - ParameterSpec parameter = parameterSpec.next(); - if (!firstParameter) { - codeWriter.emit(",").emitWrappingSpace(); - } - parameter.emit(codeWriter, isVarargs(varargs, parameterSpec)); - firstParameter = false; - } - - codeWriter.emit(")"); - } - - private static boolean isVarargs(boolean varargs, Iterator parameterSpec) { - return !parameterSpec.hasNext() && varargs; - } - private CodeBlock javadocWithParameters() { return makeJavadocWithParameters(javadoc, parameters); } diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index 585a991b..d98531eb 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -272,9 +272,6 @@ public Builder toBuilder() { return builder; } - /** - * Emits the header of the type spec and returns if the body should be emitted. - */ private boolean emitHeader(CodeWriter codeWriter, String enumName, Set implicitModifiers) throws IOException { if (enumName != null) { @@ -310,9 +307,8 @@ private boolean emitHeader(CodeWriter codeWriter, String enumName, Set } codeWriter.emitTypeVariables(typeVariables); - // Record components. if (kind == Kind.RECORD) { - MethodSpec.emitParameters(codeWriter, recordComponents, varargs); + codeWriter.emitParameters(recordComponents, varargs); } List extendsTypes; @@ -364,6 +360,7 @@ private boolean emitHeader(CodeWriter codeWriter, String enumName, Set } codeWriter.popType(); + codeWriter.emit(" {\n"); } @@ -371,8 +368,7 @@ private boolean emitHeader(CodeWriter codeWriter, String enumName, Set } void emit(CodeWriter codeWriter, String enumName, Set implicitModifiers) throws IOException { - // Nested classes interrupt wrapped line indentation. - // Stash the current wrapping state and put + // Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put // it back afterwards when this type is complete. int previousStatementLine = codeWriter.statementLine; codeWriter.statementLine = -1; @@ -537,6 +533,7 @@ public String toString() { @SuppressWarnings("ImmutableEnumChecker") public enum Kind { CLASS(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()), + RECORD(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()), INTERFACE( Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)), @@ -554,9 +551,7 @@ public enum Kind { Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)), Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)), Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)), - Util.immutableSet(Collections.singletonList(Modifier.STATIC))), - - RECORD(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()); + Util.immutableSet(Collections.singletonList(Modifier.STATIC))); private final Set implicitFieldModifiers; private final Set implicitMethodModifiers; @@ -742,7 +737,7 @@ public Builder addSuperinterface(TypeMirror superinterface, boolean avoidNestedT } public Builder addRecordComponents(Iterable parameterSpecs) { - checkArgument(parameterSpecs != null, "fieldSpecs == null"); + checkArgument(parameterSpecs != null, "parameterSpecs == null"); for (ParameterSpec parameterSpec : parameterSpecs) { addRecordComponent(parameterSpec); } @@ -763,26 +758,10 @@ public Builder varargs(boolean varargs) { return this; } - /** - * Sets the compact constructor for this builder. Its parameters are solely used for javadoc - * generation. - */ public Builder compactConstructor(MethodSpec methodSpec) { if (kind != Kind.RECORD) { throw new UnsupportedOperationException(kind + " can't have compact constructors"); } - - checkState(compactConstructor == null, "%s already has a compact constructor", name); - - checkState(methodSpec.name().equals(MethodSpec.CONSTRUCTOR), "compact constructor is not a constructor"); - EnumSet possibleModifiers = EnumSet.of(Modifier.PUBLIC, Modifier.PROTECTED, Modifier.PRIVATE); - checkState( - possibleModifiers.containsAll(methodSpec.modifiers()), - "compact constructor has non-access modifiers"); - checkState(methodSpec.modifiers().size() <= 1, "compact constructor has too many access modifiers"); - checkState(methodSpec.typeVariables().isEmpty(), "compact constructor has type variables"); - checkState(methodSpec.exceptions().isEmpty(), "compact constructor has exceptions"); - checkState(methodSpec.defaultValue() == null, "compact constructor has a default value"); compactConstructor = methodSpec; return this; } @@ -1020,6 +999,7 @@ public TypeSpec build() { checkState(recordComponent.modifiers().isEmpty(), "recordComponents has modifier"); } } + for (TypeName superinterface : superinterfaces) { checkArgument(superinterface != null, "superinterfaces contains null"); } diff --git a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java index b9bf0f6b..d18688e3 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java @@ -859,13 +859,13 @@ public void recordOneField() { assertThat(toString(typeSpec)) .isEqualTo( """ - package com.palantir.tacos; + package com.palantir.tacos; - import java.lang.String; + import java.lang.String; - record Taco(String name) { - } - """); + record Taco(String name) { + } + """); } @Test @@ -877,14 +877,14 @@ public void recordTwoFields() { assertThat(toString(typeSpec)) .isEqualTo( """ - package com.palantir.tacos; + package com.palantir.tacos; - import java.lang.Integer; - import java.lang.String; + import java.lang.Integer; + import java.lang.String; - record Taco(String name, Integer size) { - } - """); + record Taco(String name, Integer size) { + } + """); } @Test From a7d92b342deffc90b2f9f9542a34b9689d522209 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Wed, 1 May 2024 18:52:37 +0100 Subject: [PATCH 10/19] refactor to put everything in a method spec --- .../com/palantir/javapoet/MethodSpec.java | 28 ++-- .../java/com/palantir/javapoet/TypeSpec.java | 69 ++++------ .../com/palantir/javapoet/JavaFileTest.java | 123 +++--------------- .../com/palantir/javapoet/TypeSpecTest.java | 42 ++++-- 4 files changed, 89 insertions(+), 173 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java index 4c3d01fb..5e3c94c6 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java @@ -42,7 +42,6 @@ /** A generated constructor or method declaration. */ public final class MethodSpec { private static final String CONSTRUCTOR = ""; - private final String name; private final CodeBlock javadoc; private final List annotations; @@ -55,6 +54,8 @@ public final class MethodSpec { private final CodeBlock code; private final CodeBlock defaultValue; + private final boolean compactConstructor; + private MethodSpec(Builder builder) { CodeBlock code = builder.code.build(); checkArgument( @@ -77,6 +78,7 @@ private MethodSpec(Builder builder) { this.exceptions = Util.immutableList(builder.exceptions); this.defaultValue = builder.defaultValue; this.code = code; + this.compactConstructor = builder.compactConstructor; } public String name() { @@ -127,13 +129,16 @@ public boolean isConstructor() { return name.equals(CONSTRUCTOR); } + public boolean isCompactConstructor() { + return compactConstructor; + } + private boolean lastParameterIsArray(List parameters) { return !parameters.isEmpty() && TypeName.asArray(parameters.get(parameters.size() - 1).type()) != null; } - void emit(CodeWriter codeWriter, String enclosingName, Set implicitModifiers, boolean compactConstructor) - throws IOException { + void emit(CodeWriter codeWriter, String enclosingName, Set implicitModifiers) throws IOException { codeWriter.emitJavadoc(javadocWithParameters()); codeWriter.emitAnnotations(annotations, false); codeWriter.emitModifiers(modifiers, implicitModifiers); @@ -232,7 +237,7 @@ public String toString() { StringBuilder out = new StringBuilder(); try { CodeWriter codeWriter = new CodeWriter(out); - emit(codeWriter, "Constructor", Collections.emptySet(), false); + emit(codeWriter, "Constructor", Collections.emptySet()); return out.toString(); } catch (IOException e) { throw new UncheckedIOException(e); @@ -240,11 +245,15 @@ public String toString() { } public static Builder methodBuilder(String name) { - return new Builder(name); + return new Builder(name, false); } public static Builder constructorBuilder() { - return new Builder(CONSTRUCTOR); + return new Builder(CONSTRUCTOR, false); + } + + public static Builder compactConstructorBuilder() { + return new Builder(CONSTRUCTOR, true); } /** @@ -332,7 +341,7 @@ public static Builder overriding(ExecutableElement method, DeclaredType enclosin } public Builder toBuilder() { - Builder builder = new Builder(name); + Builder builder = new Builder(name, compactConstructor); builder.javadoc.add(javadoc); builder.annotations.addAll(annotations); builder.modifiers.addAll(modifiers); @@ -361,8 +370,11 @@ public static final class Builder { private final List modifiers = new ArrayList<>(); private final List parameters = new ArrayList<>(); - private Builder(String name) { + private final boolean compactConstructor; + + private Builder(String name, boolean compactConstructor) { setName(name); + this.compactConstructor = compactConstructor; } public Builder setName(String name) { diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index d98531eb..303e878f 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -66,9 +66,7 @@ public final class TypeSpec { private final Set nestedTypesSimpleNames; private final List originatingElements; private final Set alwaysQualifiedNames; - private final List recordComponents; - private final boolean varargs; - private final MethodSpec compactConstructor; + private final MethodSpec recordConstructor; private TypeSpec(Builder builder) { this.kind = builder.kind; @@ -88,9 +86,7 @@ private TypeSpec(Builder builder) { this.methodSpecs = Util.immutableList(builder.methodSpecs); this.typeSpecs = Util.immutableList(builder.typeSpecs); this.alwaysQualifiedNames = Util.immutableSet(builder.alwaysQualifiedNames); - this.recordComponents = Util.immutableList(builder.recordComponents); - this.varargs = builder.varargs; - this.compactConstructor = builder.compactConstructor; + this.recordConstructor = builder.recordConstructor; nestedTypesSimpleNames = new HashSet<>(); List originatingElementsMutable = new ArrayList<>(); @@ -127,9 +123,7 @@ private TypeSpec(TypeSpec type) { this.originatingElements = Collections.emptyList(); this.nestedTypesSimpleNames = Collections.emptySet(); this.alwaysQualifiedNames = Collections.emptySet(); - this.recordComponents = Collections.emptyList(); - this.varargs = false; - this.compactConstructor = null; + this.recordConstructor = null; } public Kind kind() { @@ -297,7 +291,9 @@ private boolean emitHeader(CodeWriter codeWriter, String enumName, Set codeWriter.pushType(new TypeSpec(this)); codeWriter.emitJavadoc( - kind == Kind.RECORD ? MethodSpec.makeJavadocWithParameters(javadoc, recordComponents) : javadoc); + kind == Kind.RECORD && recordConstructor != null + ? MethodSpec.makeJavadocWithParameters(javadoc, recordConstructor.parameters()) + : javadoc); codeWriter.emitAnnotations(annotations, false); codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers)); if (kind == Kind.ANNOTATION) { @@ -308,7 +304,12 @@ private boolean emitHeader(CodeWriter codeWriter, String enumName, Set codeWriter.emitTypeVariables(typeVariables); if (kind == Kind.RECORD) { - codeWriter.emitParameters(recordComponents, varargs); + if (recordConstructor != null) { + codeWriter.emitParameters(recordConstructor.parameters(), recordConstructor.varargs()); + } else { + // If there is no constructor then emit an empty parameter list. + codeWriter.emitParameters(List.of(), false); + } } List extendsTypes; @@ -445,11 +446,14 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier } // Compact constructor. - if (compactConstructor != null) { + if (recordConstructor != null + && recordConstructor.code() != null + && !recordConstructor.code().isEmpty() + && recordConstructor.isCompactConstructor()) { if (!firstMember) { codeWriter.emit("\n"); } - compactConstructor.emit(codeWriter, name, kind.implicitMethodModifiers, true); + recordConstructor.emit(codeWriter, name, kind.implicitMethodModifiers); firstMember = false; } @@ -461,7 +465,7 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier if (!firstMember) { codeWriter.emit("\n"); } - methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers, false); + methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers); firstMember = false; } @@ -473,7 +477,7 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier if (!firstMember) { codeWriter.emit("\n"); } - methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers, false); + methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers); firstMember = false; } @@ -596,10 +600,7 @@ public static final class Builder { private final List typeSpecs = new ArrayList<>(); private final List originatingElements = new ArrayList<>(); private final Set alwaysQualifiedNames = new LinkedHashSet<>(); - - private final List recordComponents = new ArrayList<>(); - private boolean varargs; - private MethodSpec compactConstructor; + private MethodSpec recordConstructor; private Builder(Kind kind, String name, CodeBlock anonymousTypeArguments) { checkArgument(name == null || SourceVersion.isName(name), "not a valid name: %s", name); @@ -736,33 +737,11 @@ public Builder addSuperinterface(TypeMirror superinterface, boolean avoidNestedT return this; } - public Builder addRecordComponents(Iterable parameterSpecs) { - checkArgument(parameterSpecs != null, "parameterSpecs == null"); - for (ParameterSpec parameterSpec : parameterSpecs) { - addRecordComponent(parameterSpec); - } - return this; - } - - public Builder addRecordComponent(ParameterSpec parameterSpec) { - recordComponents.add(parameterSpec); - return this; - } - - public Builder varargs() { - return varargs(true); - } - - public Builder varargs(boolean varargs) { - this.varargs = varargs; - return this; - } - - public Builder compactConstructor(MethodSpec methodSpec) { + public Builder recordConstructor(MethodSpec methodSpec) { if (kind != Kind.RECORD) { throw new UnsupportedOperationException(kind + " can't have compact constructors"); } - compactConstructor = methodSpec; + recordConstructor = methodSpec; return this; } @@ -992,9 +971,9 @@ public TypeSpec build() { checkState(!modifiers.contains(Modifier.ABSTRACT), "abstract record"); } - if (!recordComponents.isEmpty()) { + if (recordConstructor != null && !recordConstructor.parameters().isEmpty()) { checkState(kind == Kind.RECORD, "%s is not record", this.name); - for (ParameterSpec recordComponent : recordComponents) { + for (ParameterSpec recordComponent : recordConstructor.parameters()) { checkState(recordComponent != null, "recordComponents contain null"); checkState(recordComponent.modifiers().isEmpty(), "recordComponents has modifier"); } diff --git a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java index 1320fea8..75bb9bb8 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java @@ -405,37 +405,18 @@ record Taco() { """); } - @Test - public void recordOneField() { - String source = JavaFile.builder( - "com.palantir.tacos", - TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "name") - .build()) - .build()) - .skipJavaLangImports(true) - .build() - .toString(); - assertThat(source) - .isEqualTo( - """ - package com.palantir.tacos; - - record Taco(String name) { - } - """); - } - @Test public void recordOneFieldWithGeneric() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") .addTypeVariable(TypeVariableName.get("T")) - .addRecordComponent(ParameterSpec.builder( - ParameterizedTypeName.get( - ClassName.get(List.class), TypeVariableName.get("T")), - "names") + .recordConstructor(MethodSpec.constructorBuilder() + .addParameter(ParameterSpec.builder( + ParameterizedTypeName.get( + ClassName.get(List.class), TypeVariableName.get("T")), + "names") + .build()) .build()) .build()) .skipJavaLangImports(true) @@ -453,35 +434,14 @@ record Taco(List names) { """); } - @Test - public void recordTwoField() { - String source = JavaFile.builder( - "com.palantir.tacos", - TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "name") - .build()) - .addRecordComponent(ParameterSpec.builder(Integer.class, "code") - .build()) - .build()) - .skipJavaLangImports(true) - .build() - .toString(); - assertThat(source) - .isEqualTo( - """ - package com.palantir.tacos; - - record Taco(String name, Integer code) { - } - """); - } - @Test public void recordOneFieldImplementsInterface() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "name") + .recordConstructor(MethodSpec.constructorBuilder() + .addParameter(ParameterSpec.builder(String.class, "name") + .build()) .build()) .addSuperinterface(Serializable.class) .build()) @@ -505,7 +465,9 @@ public void recordOneFieldWithAnnotation() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "name") + .recordConstructor(MethodSpec.constructorBuilder() + .addParameter(ParameterSpec.builder(String.class, "name") + .build()) .build()) .addAnnotation(Deprecated.class) .build()) @@ -523,65 +485,14 @@ record Taco(String name) { """); } - @Test - public void recordOneFieldWithMethod() { - String source = JavaFile.builder( - "com.palantir.tacos", - TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "name") - .build()) - .addMethod(MethodSpec.methodBuilder("name") - .returns(String.class) - .addStatement("return name") - .build()) - .build()) - .skipJavaLangImports(true) - .build() - .toString(); - assertThat(source) - .isEqualTo( - """ - package com.palantir.tacos; - - record Taco(String name) { - String name() { - return name; - } - } - """); - } - - @Test - public void recordWithVarArgs() { - String source = JavaFile.builder( - "com.palantir.tacos", - TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "id") - .build()) - .addRecordComponent( - ParameterSpec.builder(ArrayTypeName.of(ClassName.get(String.class)), "names") - .build()) - .varargs() - .build()) - .skipJavaLangImports(true) - .build() - .toString(); - assertThat(source) - .isEqualTo( - """ - package com.palantir.tacos; - - record Taco(String id, String... names) { - } - """); - } - @Test public void secondaryConstructorRecord() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(ClassName.get(String.class), "name") + .recordConstructor(MethodSpec.constructorBuilder() + .addParameter(ParameterSpec.builder(ClassName.get(String.class), "name") + .build()) .build()) .addMethod(MethodSpec.constructorBuilder() .addParameter(TypeName.INT, "number") @@ -611,9 +522,9 @@ public void recordWithCompactConstructor() { String source = JavaFile.builder( "com.palantir.tacos", TypeSpec.recordBuilder("Taco") - .addRecordComponent(name) - .compactConstructor(MethodSpec.constructorBuilder() + .recordConstructor(MethodSpec.compactConstructorBuilder() .addModifiers(Modifier.PUBLIC) + .addParameter(name) .addCode(CodeBlock.builder() .beginControlFlow("if ($N.isEmpty())", name) .addStatement( diff --git a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java index d18688e3..511c319c 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java @@ -854,7 +854,10 @@ sealed interface Taco extends Serializable, Comparable permits BeefTaco, C @Test public void recordOneField() { TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "name").build()) + .recordConstructor(MethodSpec.constructorBuilder() + .addParameter( + ParameterSpec.builder(String.class, "name").build()) + .build()) .build(); assertThat(toString(typeSpec)) .isEqualTo( @@ -871,8 +874,12 @@ record Taco(String name) { @Test public void recordTwoFields() { TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "name").build()) - .addRecordComponent(ParameterSpec.builder(Integer.class, "size").build()) + .recordConstructor(MethodSpec.constructorBuilder() + .addParameter( + ParameterSpec.builder(String.class, "name").build()) + .addParameter( + ParameterSpec.builder(Integer.class, "size").build()) + .build()) .build(); assertThat(toString(typeSpec)) .isEqualTo( @@ -882,18 +889,21 @@ public void recordTwoFields() { import java.lang.Integer; import java.lang.String; - record Taco(String name, Integer size) { - } - """); + record Taco(String name, Integer size) { + } + """); } @Test public void recordWithVarArgs() { TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "id").build()) - .addRecordComponent(ParameterSpec.builder(ArrayTypeName.of(ClassName.get(String.class)), "names") + .recordConstructor(MethodSpec.constructorBuilder() + .addParameter( + ParameterSpec.builder(String.class, "name").build()) + .addParameter(ParameterSpec.builder(ArrayTypeName.of(ClassName.get(String.class)), "names") + .build()) + .varargs() .build()) - .varargs() .build(); assertThat(toString(typeSpec)) .isEqualTo( @@ -902,7 +912,7 @@ public void recordWithVarArgs() { import java.lang.String; - record Taco(String id, String... names) { + record Taco(String name, String... names) { } """); } @@ -910,8 +920,10 @@ record Taco(String id, String... names) { @Test public void recordWithJavadoc() { TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "id") - .addJavadoc("Id of the taco.") + .recordConstructor(MethodSpec.constructorBuilder() + .addParameter(ParameterSpec.builder(String.class, "id") + .addJavadoc("Id of the taco.") + .build()) .build()) .addJavadoc("A taco class that stores the id of a taco.") .build(); @@ -934,8 +946,10 @@ record Taco(String id) { @Test public void recordWithAnnotationOnParam() { TypeSpec typeSpec = TypeSpec.recordBuilder("Taco") - .addRecordComponent(ParameterSpec.builder(String.class, "id") - .addAnnotation(Deprecated.class) + .recordConstructor(MethodSpec.constructorBuilder() + .addParameter(ParameterSpec.builder(String.class, "id") + .addAnnotation(Deprecated.class) + .build()) .build()) .build(); assertThat(toString(typeSpec)) From 2c2d0cecf6e783b3b28565004f91017db5cbf972 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Wed, 1 May 2024 18:54:16 +0100 Subject: [PATCH 11/19] remove emitHeader --- .../java/com/palantir/javapoet/TypeSpec.java | 181 +++++++++--------- 1 file changed, 86 insertions(+), 95 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index 303e878f..65e20b13 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -266,117 +266,108 @@ public Builder toBuilder() { return builder; } - private boolean emitHeader(CodeWriter codeWriter, String enumName, Set implicitModifiers) - throws IOException { - if (enumName != null) { - codeWriter.emitJavadoc(javadoc); - codeWriter.emitAnnotations(annotations, false); - codeWriter.emit("$L", enumName); - if (!anonymousTypeArguments.formatParts().isEmpty()) { - codeWriter.emit("("); + void emit(CodeWriter codeWriter, String enumName, Set implicitModifiers) throws IOException { + // Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put + // it back afterwards when this type is complete. + int previousStatementLine = codeWriter.statementLine; + codeWriter.statementLine = -1; + + try { + if (enumName != null) { + codeWriter.emitJavadoc(javadoc); + codeWriter.emitAnnotations(annotations, false); + codeWriter.emit("$L", enumName); + if (!anonymousTypeArguments.formatParts().isEmpty()) { + codeWriter.emit("("); + codeWriter.emit(anonymousTypeArguments); + codeWriter.emit(")"); + } + if (fieldSpecs.isEmpty() && methodSpecs.isEmpty() && typeSpecs.isEmpty()) { + return; // Avoid unnecessary braces "{}". + } + codeWriter.emit(" {\n"); + } else if (anonymousTypeArguments != null) { + TypeName supertype = !superinterfaces.isEmpty() ? superinterfaces.get(0) : superclass; + codeWriter.emit("new $T(", supertype); codeWriter.emit(anonymousTypeArguments); - codeWriter.emit(")"); - } - if (fieldSpecs.isEmpty() && methodSpecs.isEmpty() && typeSpecs.isEmpty()) { - return false; // Avoid unnecessary braces "{}". - } - codeWriter.emit(" {\n"); - } else if (anonymousTypeArguments != null) { - TypeName supertype = !superinterfaces.isEmpty() ? superinterfaces.get(0) : superclass; - codeWriter.emit("new $T(", supertype); - codeWriter.emit(anonymousTypeArguments); - codeWriter.emit(") {\n"); - } else { - // Push an empty type (specifically without nested types) for type-resolution. - codeWriter.pushType(new TypeSpec(this)); - - codeWriter.emitJavadoc( - kind == Kind.RECORD && recordConstructor != null - ? MethodSpec.makeJavadocWithParameters(javadoc, recordConstructor.parameters()) - : javadoc); - codeWriter.emitAnnotations(annotations, false); - codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers)); - if (kind == Kind.ANNOTATION) { - codeWriter.emit("$L $L", "@interface", name); + codeWriter.emit(") {\n"); } else { - codeWriter.emit("$L $L", kind.name().toLowerCase(Locale.US), name); - } - codeWriter.emitTypeVariables(typeVariables); - - if (kind == Kind.RECORD) { - if (recordConstructor != null) { - codeWriter.emitParameters(recordConstructor.parameters(), recordConstructor.varargs()); + // Push an empty type (specifically without nested types) for type-resolution. + codeWriter.pushType(new TypeSpec(this)); + + codeWriter.emitJavadoc( + kind == Kind.RECORD && recordConstructor != null + ? MethodSpec.makeJavadocWithParameters(javadoc, recordConstructor.parameters()) + : javadoc); + codeWriter.emitAnnotations(annotations, false); + codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers)); + if (kind == Kind.ANNOTATION) { + codeWriter.emit("$L $L", "@interface", name); } else { - // If there is no constructor then emit an empty parameter list. - codeWriter.emitParameters(List.of(), false); + codeWriter.emit("$L $L", kind.name().toLowerCase(Locale.US), name); } - } + codeWriter.emitTypeVariables(typeVariables); - List extendsTypes; - List implementsTypes; - if (kind == Kind.INTERFACE) { - extendsTypes = superinterfaces; - implementsTypes = Collections.emptyList(); - } else { - extendsTypes = superclass.equals(ClassName.OBJECT) - ? Collections.emptyList() - : Collections.singletonList(superclass); - implementsTypes = superinterfaces; - } - - if (!extendsTypes.isEmpty()) { - codeWriter.emit(" extends"); - boolean firstType = true; - for (TypeName type : extendsTypes) { - if (!firstType) { - codeWriter.emit(","); + if (kind == Kind.RECORD) { + if (recordConstructor != null) { + codeWriter.emitParameters(recordConstructor.parameters(), recordConstructor.varargs()); + } else { + // If there is no constructor then emit an empty parameter list. + codeWriter.emitParameters(List.of(), false); } - codeWriter.emit(" $T", type); - firstType = false; } - } - if (!implementsTypes.isEmpty()) { - codeWriter.emit(" implements"); - boolean firstType = true; - for (TypeName type : implementsTypes) { - if (!firstType) { - codeWriter.emit(","); - } - codeWriter.emit(" $T", type); - firstType = false; + List extendsTypes; + List implementsTypes; + if (kind == Kind.INTERFACE) { + extendsTypes = superinterfaces; + implementsTypes = Collections.emptyList(); + } else { + extendsTypes = superclass.equals(ClassName.OBJECT) + ? Collections.emptyList() + : Collections.singletonList(superclass); + implementsTypes = superinterfaces; } - } - if (!permittedSubclasses.isEmpty()) { - codeWriter.emit(" permits"); - boolean firstType = true; - for (TypeName type : permittedSubclasses) { - if (!firstType) { - codeWriter.emit(","); + if (!extendsTypes.isEmpty()) { + codeWriter.emit(" extends"); + boolean firstType = true; + for (TypeName type : extendsTypes) { + if (!firstType) { + codeWriter.emit(","); + } + codeWriter.emit(" $T", type); + firstType = false; } - codeWriter.emit(" $T", type); - firstType = false; } - } - - codeWriter.popType(); - codeWriter.emit(" {\n"); - } + if (!implementsTypes.isEmpty()) { + codeWriter.emit(" implements"); + boolean firstType = true; + for (TypeName type : implementsTypes) { + if (!firstType) { + codeWriter.emit(","); + } + codeWriter.emit(" $T", type); + firstType = false; + } + } - return true; - } + if (!permittedSubclasses.isEmpty()) { + codeWriter.emit(" permits"); + boolean firstType = true; + for (TypeName type : permittedSubclasses) { + if (!firstType) { + codeWriter.emit(","); + } + codeWriter.emit(" $T", type); + firstType = false; + } + } - void emit(CodeWriter codeWriter, String enumName, Set implicitModifiers) throws IOException { - // Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put - // it back afterwards when this type is complete. - int previousStatementLine = codeWriter.statementLine; - codeWriter.statementLine = -1; + codeWriter.popType(); - try { - if (!emitHeader(codeWriter, enumName, implicitModifiers)) { - return; + codeWriter.emit(" {\n"); } codeWriter.pushType(this); From e951479880974893d46b98ba76625a51919217ce Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Wed, 1 May 2024 19:01:52 +0100 Subject: [PATCH 12/19] test format --- .../com/palantir/javapoet/JavaFileTest.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java index 75bb9bb8..d4138660 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java @@ -398,11 +398,11 @@ public void recordNoField() { assertThat(source) .isEqualTo( """ - package com.palantir.tacos; + package com.palantir.tacos; - record Taco() { - } - """); + record Taco() { + } + """); } @Test @@ -425,13 +425,13 @@ public void recordOneFieldWithGeneric() { assertThat(source) .isEqualTo( """ - package com.palantir.tacos; + package com.palantir.tacos; - import java.util.List; + import java.util.List; - record Taco(List names) { - } - """); + record Taco(List names) { + } + """); } @Test @@ -451,13 +451,13 @@ public void recordOneFieldImplementsInterface() { assertThat(source) .isEqualTo( """ - package com.palantir.tacos; + package com.palantir.tacos; - import java.io.Serializable; + import java.io.Serializable; - record Taco(String name) implements Serializable { - } - """); + record Taco(String name) implements Serializable { + } + """); } @Test @@ -477,12 +477,12 @@ public void recordOneFieldWithAnnotation() { assertThat(source) .isEqualTo( """ - package com.palantir.tacos; + package com.palantir.tacos; - @Deprecated - record Taco(String name) { - } - """); + @Deprecated + record Taco(String name) { + } + """); } @Test From 5f0b2dacdd72f4aca3b44c531e64fbf1ace639a0 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Wed, 1 May 2024 19:04:28 +0100 Subject: [PATCH 13/19] nit --- javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java | 1 - 1 file changed, 1 deletion(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java index 5e3c94c6..b728ec7c 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java @@ -53,7 +53,6 @@ public final class MethodSpec { private final List exceptions; private final CodeBlock code; private final CodeBlock defaultValue; - private final boolean compactConstructor; private MethodSpec(Builder builder) { From b334e3ad06d3fed2b644ed93a2dd7658e78b7aae Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Sun, 2 Jun 2024 18:31:35 +0100 Subject: [PATCH 14/19] comments --- .../com/palantir/javapoet/CodeWriter.java | 16 +++++++++ .../com/palantir/javapoet/MethodSpec.java | 22 +------------ .../java/com/palantir/javapoet/TypeSpec.java | 33 +++++++++---------- 3 files changed, 33 insertions(+), 38 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/CodeWriter.java b/javapoet/src/main/java/com/palantir/javapoet/CodeWriter.java index 17ba7e83..9c6eac72 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/CodeWriter.java +++ b/javapoet/src/main/java/com/palantir/javapoet/CodeWriter.java @@ -241,6 +241,22 @@ public void emitParameters(Iterable parameters, boolean varargs) emit(")"); } + public void emitJavadocWithParameters(CodeBlock javadoc, Iterable parameters) throws IOException { + CodeBlock.Builder builder = javadoc.toBuilder(); + boolean emitTagNewline = true; + for (ParameterSpec parameterSpec : parameters) { + if (!parameterSpec.javadoc().isEmpty()) { + // Emit a new line before @param section only if the method javadoc is present. + if (emitTagNewline && !javadoc.isEmpty()) { + builder.add("\n"); + } + emitTagNewline = false; + builder.add("@param $L $L", parameterSpec.name(), parameterSpec.javadoc()); + } + } + emitJavadoc(builder.build()); + } + public void popTypeVariables(List typeVariables) { typeVariables.forEach(typeVariable -> currentTypeVariables.remove(typeVariable.name())); } diff --git a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java index b728ec7c..0bcdc8b1 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java @@ -138,7 +138,7 @@ private boolean lastParameterIsArray(List parameters) { } void emit(CodeWriter codeWriter, String enclosingName, Set implicitModifiers) throws IOException { - codeWriter.emitJavadoc(javadocWithParameters()); + codeWriter.emitJavadocWithParameters(javadoc, parameters); codeWriter.emitAnnotations(annotations, false); codeWriter.emitModifiers(modifiers, implicitModifiers); @@ -192,26 +192,6 @@ void emit(CodeWriter codeWriter, String enclosingName, Set implicitMod codeWriter.popTypeVariables(typeVariables); } - private CodeBlock javadocWithParameters() { - return makeJavadocWithParameters(javadoc, parameters); - } - - static CodeBlock makeJavadocWithParameters(CodeBlock javadoc, Iterable parameters) { - CodeBlock.Builder builder = javadoc.toBuilder(); - boolean emitTagNewline = true; - for (ParameterSpec parameterSpec : parameters) { - if (!parameterSpec.javadoc().isEmpty()) { - // Emit a new line before @param section only if the method javadoc is present. - if (emitTagNewline && !javadoc.isEmpty()) { - builder.add("\n"); - } - emitTagNewline = false; - builder.add("@param $L $L", parameterSpec.name(), parameterSpec.javadoc()); - } - } - return builder.build(); - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index 65e20b13..2ab4c6ab 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -295,10 +295,11 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier // Push an empty type (specifically without nested types) for type-resolution. codeWriter.pushType(new TypeSpec(this)); - codeWriter.emitJavadoc( - kind == Kind.RECORD && recordConstructor != null - ? MethodSpec.makeJavadocWithParameters(javadoc, recordConstructor.parameters()) - : javadoc); + if (kind == Kind.RECORD && recordConstructor != null) { + codeWriter.emitJavadocWithParameters(javadoc, recordConstructor.parameters()); + } else { + codeWriter.emitJavadoc(javadoc); + } codeWriter.emitAnnotations(annotations, false); codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers)); if (kind == Kind.ANNOTATION) { @@ -728,11 +729,11 @@ public Builder addSuperinterface(TypeMirror superinterface, boolean avoidNestedT return this; } - public Builder recordConstructor(MethodSpec methodSpec) { + public Builder recordConstructor(MethodSpec recordConstructor) { if (kind != Kind.RECORD) { - throw new UnsupportedOperationException(kind + " can't have compact constructors"); + throw new UnsupportedOperationException(kind + " can't have record constructor"); } - recordConstructor = methodSpec; + this.recordConstructor = recordConstructor; return this; } @@ -958,15 +959,9 @@ public TypeSpec build() { } } - if (kind == Kind.RECORD) { - checkState(!modifiers.contains(Modifier.ABSTRACT), "abstract record"); - } - - if (recordConstructor != null && !recordConstructor.parameters().isEmpty()) { - checkState(kind == Kind.RECORD, "%s is not record", this.name); + if (recordConstructor != null) { for (ParameterSpec recordComponent : recordConstructor.parameters()) { - checkState(recordComponent != null, "recordComponents contain null"); - checkState(recordComponent.modifiers().isEmpty(), "recordComponents has modifier"); + checkArgument(recordComponent.modifiers().isEmpty(), "record components must not have modifiers"); } } @@ -1005,7 +1000,6 @@ public TypeSpec build() { fieldSpec.name(), check); } - if (kind == Kind.RECORD) { checkState( fieldSpec.modifiers().contains(Modifier.STATIC), @@ -1081,7 +1075,12 @@ public TypeSpec build() { kind.implicitTypeModifiers); } - boolean isAbstract = modifiers.contains(Modifier.ABSTRACT) || (kind != Kind.CLASS && kind != Kind.RECORD); + boolean isAbstract = + switch (kind) { + case CLASS -> modifiers.contains(Modifier.ABSTRACT); + case RECORD, ENUM, ANNOTATION -> false; + case INTERFACE -> true; + }; for (MethodSpec methodSpec : methodSpecs) { checkArgument( isAbstract || !methodSpec.modifiers().contains(Modifier.ABSTRACT), From eefad33317e24df22a5835e5910dedd158751825 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Mon, 3 Jun 2024 11:50:28 +0100 Subject: [PATCH 15/19] lint --- javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index 2ab4c6ab..6e987032 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -1077,9 +1077,8 @@ public TypeSpec build() { boolean isAbstract = switch (kind) { - case CLASS -> modifiers.contains(Modifier.ABSTRACT); - case RECORD, ENUM, ANNOTATION -> false; - case INTERFACE -> true; + case CLASS, RECORD -> modifiers.contains(Modifier.ABSTRACT); + case ENUM, ANNOTATION, INTERFACE -> true; }; for (MethodSpec methodSpec : methodSpecs) { checkArgument( From 589a4127299e4584da8833850218a8137e538b86 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Thu, 20 Jun 2024 21:39:15 +0100 Subject: [PATCH 16/19] comments --- .../com/palantir/javapoet/MethodSpec.java | 4 ---- .../java/com/palantir/javapoet/TypeSpec.java | 13 +++++------- .../com/palantir/javapoet/JavaFileTest.java | 21 ++----------------- .../com/palantir/javapoet/TypeSpecTest.java | 13 ++++++++++++ 4 files changed, 20 insertions(+), 31 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java index 0bcdc8b1..21306d57 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/MethodSpec.java @@ -128,10 +128,6 @@ public boolean isConstructor() { return name.equals(CONSTRUCTOR); } - public boolean isCompactConstructor() { - return compactConstructor; - } - private boolean lastParameterIsArray(List parameters) { return !parameters.isEmpty() && TypeName.asArray(parameters.get(parameters.size() - 1).type()) != null; diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index 6e987032..dde6f7a3 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -295,7 +295,7 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier // Push an empty type (specifically without nested types) for type-resolution. codeWriter.pushType(new TypeSpec(this)); - if (kind == Kind.RECORD && recordConstructor != null) { + if (recordConstructor != null) { codeWriter.emitJavadocWithParameters(javadoc, recordConstructor.parameters()); } else { codeWriter.emitJavadoc(javadoc); @@ -305,7 +305,7 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier if (kind == Kind.ANNOTATION) { codeWriter.emit("$L $L", "@interface", name); } else { - codeWriter.emit("$L $L", kind.name().toLowerCase(Locale.US), name); + codeWriter.emit("$L $L", kind.name().toLowerCase(Locale.ROOT), name); } codeWriter.emitTypeVariables(typeVariables); @@ -313,7 +313,6 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier if (recordConstructor != null) { codeWriter.emitParameters(recordConstructor.parameters(), recordConstructor.varargs()); } else { - // If there is no constructor then emit an empty parameter list. codeWriter.emitParameters(List.of(), false); } } @@ -438,10 +437,7 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier } // Compact constructor. - if (recordConstructor != null - && recordConstructor.code() != null - && !recordConstructor.code().isEmpty() - && recordConstructor.isCompactConstructor()) { + if (recordConstructor != null && !recordConstructor.code().isEmpty()) { if (!firstMember) { codeWriter.emit("\n"); } @@ -529,6 +525,7 @@ public String toString() { @SuppressWarnings("ImmutableEnumChecker") public enum Kind { CLASS(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()), + RECORD(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()), INTERFACE( @@ -575,6 +572,7 @@ public static final class Builder { private final Kind kind; private final String name; private final CodeBlock anonymousTypeArguments; + private MethodSpec recordConstructor; private final CodeBlock.Builder javadoc = CodeBlock.builder(); private TypeName superclass = ClassName.OBJECT; @@ -592,7 +590,6 @@ public static final class Builder { private final List typeSpecs = new ArrayList<>(); private final List originatingElements = new ArrayList<>(); private final Set alwaysQualifiedNames = new LinkedHashSet<>(); - private MethodSpec recordConstructor; private Builder(Kind kind, String name, CodeBlock anonymousTypeArguments) { checkArgument(name == null || SourceVersion.isName(name), "not a valid name: %s", name); diff --git a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java index d4138660..e88cf42b 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java @@ -388,23 +388,6 @@ class Taco { """); } - @Test - public void recordNoField() { - String source = JavaFile.builder( - "com.palantir.tacos", TypeSpec.recordBuilder("Taco").build()) - .skipJavaLangImports(true) - .build() - .toString(); - assertThat(source) - .isEqualTo( - """ - package com.palantir.tacos; - - record Taco() { - } - """); - } - @Test public void recordOneFieldWithGeneric() { String source = JavaFile.builder( @@ -496,7 +479,7 @@ public void secondaryConstructorRecord() { .build()) .addMethod(MethodSpec.constructorBuilder() .addParameter(TypeName.INT, "number") - .addCode("this.name = $T.toString(number);", ClassName.get(Integer.class)) + .addCode("this($T.toString(number));", ClassName.get(Integer.class)) .build()) .build()) .skipJavaLangImports(true) @@ -509,7 +492,7 @@ public void secondaryConstructorRecord() { record Taco(String name) { Taco(int number) { - this.name = Integer.toString(number); + this(Integer.toString(number)); } } """); diff --git a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java index 511c319c..f61c5f9b 100644 --- a/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java +++ b/javapoet/src/test/java/com/palantir/javapoet/TypeSpecTest.java @@ -965,6 +965,19 @@ record Taco(@Deprecated String id) { """); } + @Test + public void recordNoField() { + TypeSpec typeSpec = TypeSpec.recordBuilder("Taco").build(); + assertThat(toString(typeSpec)) + .isEqualTo( + """ + package com.palantir.tacos; + + record Taco() { + } + """); + } + @Test public void nestedClasses() { ClassName taco = ClassName.get(tacosPackage, "Combo", "Taco"); From 40e7b547a08b30b3915f85cb8c65a523f0972cb6 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Thu, 20 Jun 2024 21:46:03 +0100 Subject: [PATCH 17/19] Add keyword to kind enum --- .../java/com/palantir/javapoet/TypeSpec.java | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index dde6f7a3..07fb93c8 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -302,11 +302,7 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier } codeWriter.emitAnnotations(annotations, false); codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers)); - if (kind == Kind.ANNOTATION) { - codeWriter.emit("$L $L", "@interface", name); - } else { - codeWriter.emit("$L $L", kind.name().toLowerCase(Locale.ROOT), name); - } + codeWriter.emit("$L $L", kind.keyword, name); codeWriter.emitTypeVariables(typeVariables); if (kind == Kind.RECORD) { @@ -524,42 +520,53 @@ public String toString() { @SuppressWarnings("ImmutableEnumChecker") public enum Kind { - CLASS(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()), + CLASS(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), "class"), - RECORD(Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet()), + RECORD( + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet(), + Collections.emptySet(), + "record"), INTERFACE( Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)), Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)), Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)), - Util.immutableSet(Collections.singletonList(Modifier.STATIC))), + Util.immutableSet(Collections.singletonList(Modifier.STATIC)), + "interface"), ENUM( Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), - Collections.singleton(Modifier.STATIC)), + Collections.singleton(Modifier.STATIC), + "enum"), ANNOTATION( Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)), Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)), Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)), - Util.immutableSet(Collections.singletonList(Modifier.STATIC))); + Util.immutableSet(Collections.singletonList(Modifier.STATIC)), + "@annotation"); private final Set implicitFieldModifiers; private final Set implicitMethodModifiers; private final Set implicitTypeModifiers; private final Set asMemberModifiers; + private final String keyword; Kind( Set implicitFieldModifiers, Set implicitMethodModifiers, Set implicitTypeModifiers, - Set asMemberModifiers) { + Set asMemberModifiers, + String keyword) { this.implicitFieldModifiers = implicitFieldModifiers; this.implicitMethodModifiers = implicitMethodModifiers; this.implicitTypeModifiers = implicitTypeModifiers; this.asMemberModifiers = asMemberModifiers; + this.keyword = keyword; } @Override From fb5503abf0d76130b2f7fc32c1e86e5423c2ce15 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Wed, 26 Jun 2024 13:58:40 +0100 Subject: [PATCH 18/19] fix --- javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index 07fb93c8..6b420e0e 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -548,7 +548,7 @@ public enum Kind { Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)), Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)), Util.immutableSet(Collections.singletonList(Modifier.STATIC)), - "@annotation"); + "@interface"); private final Set implicitFieldModifiers; private final Set implicitMethodModifiers; From 9f0f4d4207935384967fa9fe2491400e099474d3 Mon Sep 17 00:00:00 2001 From: Xander Bailey Date: Tue, 23 Jul 2024 22:03:32 +0100 Subject: [PATCH 19/19] last comment --- javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java index 6b420e0e..227403b9 100644 --- a/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java +++ b/javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java @@ -33,7 +33,6 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.Set; import javax.lang.model.SourceVersion; @@ -569,10 +568,6 @@ public enum Kind { this.keyword = keyword; } - @Override - public String toString() { - return name().toLowerCase(Locale.ROOT); - } } public static final class Builder {