diff --git a/src/main/java/com/squareup/javapoet/MethodSpec.java b/src/main/java/com/squareup/javapoet/MethodSpec.java index 67722c774..0b2d258b1 100644 --- a/src/main/java/com/squareup/javapoet/MethodSpec.java +++ b/src/main/java/com/squareup/javapoet/MethodSpec.java @@ -80,7 +80,8 @@ private boolean lastParameterIsArray(List parameters) { && TypeName.asArray((parameters.get(parameters.size() - 1).type)) != null; } - void emit(CodeWriter codeWriter, String enclosingName, Set implicitModifiers) + void emit(CodeWriter codeWriter, String enclosingName, Set implicitModifiers, + boolean compactConstructor) throws IOException { codeWriter.emitJavadoc(javadocWithParameters()); codeWriter.emitAnnotations(annotations, false); @@ -91,22 +92,18 @@ 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); - } + if (isConstructor()) { + codeWriter.emit("$L", enclosingName); + } else { + codeWriter.emit("$T $L", returnType, name); + } - 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; + emitParameters(codeWriter, parameters, varargs); } - codeWriter.emit(")"); - if (defaultValue != null && !defaultValue.isEmpty()) { codeWriter.emit(" default "); codeWriter.emit(defaultValue); @@ -141,6 +138,11 @@ void emit(CodeWriter codeWriter, String enclosingName, Set implicitMod } 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) { @@ -154,6 +156,22 @@ private CodeBlock javadocWithParameters() { return builder.build(); } + 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(")"); + } + public boolean hasModifier(Modifier modifier) { return modifiers.contains(modifier); } @@ -177,7 +195,7 @@ public boolean isConstructor() { 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 AssertionError(); diff --git a/src/main/java/com/squareup/javapoet/TypeSpec.java b/src/main/java/com/squareup/javapoet/TypeSpec.java index 6cd2ea636..7214b6d30 100644 --- a/src/main/java/com/squareup/javapoet/TypeSpec.java +++ b/src/main/java/com/squareup/javapoet/TypeSpec.java @@ -55,10 +55,13 @@ public final class TypeSpec { public final List typeVariables; public final TypeName superclass; public final List superinterfaces; + public final List recordComponents; + public final boolean varargs; public final Map enumConstants; public final List fieldSpecs; public final CodeBlock staticBlock; public final CodeBlock initializerBlock; + public final MethodSpec compactConstructor; public final List methodSpecs; public final List typeSpecs; final Set nestedTypesSimpleNames; @@ -75,10 +78,13 @@ private TypeSpec(Builder builder) { this.typeVariables = Util.immutableList(builder.typeVariables); this.superclass = builder.superclass; this.superinterfaces = Util.immutableList(builder.superinterfaces); + this.recordComponents = Util.immutableList(builder.recordComponents); + this.varargs = builder.varargs; this.enumConstants = Util.immutableMap(builder.enumConstants); this.fieldSpecs = Util.immutableList(builder.fieldSpecs); this.staticBlock = builder.staticBlock.build(); this.initializerBlock = builder.initializerBlock.build(); + this.compactConstructor = builder.compactConstructor; this.methodSpecs = Util.immutableList(builder.methodSpecs); this.typeSpecs = Util.immutableList(builder.typeSpecs); this.alwaysQualifiedNames = Util.immutableSet(builder.alwaysQualifiedNames); @@ -109,10 +115,13 @@ private TypeSpec(TypeSpec type) { this.typeVariables = Collections.emptyList(); this.superclass = null; this.superinterfaces = Collections.emptyList(); + this.recordComponents = Collections.emptyList(); + this.varargs = false; this.enumConstants = Collections.emptyMap(); this.fieldSpecs = Collections.emptyList(); this.staticBlock = type.staticBlock; this.initializerBlock = type.initializerBlock; + this.compactConstructor = null; this.methodSpecs = Collections.emptyList(); this.typeSpecs = Collections.emptyList(); this.originatingElements = Collections.emptyList(); @@ -148,6 +157,14 @@ public static Builder enumBuilder(ClassName className) { return enumBuilder(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 anonymousClassBuilder(String typeArgumentsFormat, Object... args) { return anonymousClassBuilder(CodeBlock.of(typeArgumentsFormat, args)); } @@ -172,7 +189,10 @@ public Builder toBuilder() { builder.typeVariables.addAll(typeVariables); builder.superclass = superclass; builder.superinterfaces.addAll(superinterfaces); + builder.recordComponents.addAll(recordComponents); + builder.varargs = varargs; builder.enumConstants.putAll(enumConstants); + builder.compactConstructor = compactConstructor; builder.fieldSpecs.addAll(fieldSpecs); builder.methodSpecs.addAll(methodSpecs); builder.typeSpecs.addAll(typeSpecs); @@ -183,81 +203,100 @@ 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.name().toLowerCase(Locale.US), 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 { - 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 (!extendsTypes.isEmpty()) { + codeWriter.emit(" extends"); + boolean firstType = true; + for (TypeName type : extendsTypes) { + 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; - } + 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.popType(); + + codeWriter.emit(" {\n"); + } - codeWriter.emit(" {\n"); + return true; + } + + 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); @@ -309,11 +348,18 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier 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); + methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers, false); firstMember = false; } @@ -321,7 +367,7 @@ void emit(CodeWriter codeWriter, String enumName, Set implicitModifier for (MethodSpec methodSpec : methodSpecs) { if (methodSpec.isConstructor()) continue; if (!firstMember) codeWriter.emit("\n"); - methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers); + methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers, false); firstMember = false; } @@ -390,7 +436,13 @@ 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.singleton(Modifier.STATIC)); private final Set implicitFieldModifiers; private final Set implicitMethodModifiers; @@ -417,8 +469,11 @@ public static final class Builder { private TypeName superclass = ClassName.OBJECT; private final CodeBlock.Builder staticBlock = CodeBlock.builder(); private final CodeBlock.Builder initializerBlock = CodeBlock.builder(); + private MethodSpec compactConstructor; public final Map enumConstants = new LinkedHashMap<>(); + public final List recordComponents = new ArrayList<>(); + public boolean varargs; public final List annotations = new ArrayList<>(); public final List modifiers = new ArrayList<>(); public final List typeVariables = new ArrayList<>(); @@ -579,6 +634,58 @@ public Builder addSuperinterface(TypeMirror superinterface, 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 addEnumConstant(String name) { return addEnumConstant(name, anonymousClassBuilder("").build()); } @@ -763,6 +870,10 @@ public TypeSpec build() { for (Modifier modifier : modifiers) { checkArgument(modifier != null, "modifiers contain null"); } + + if (kind == Kind.RECORD) { + checkState(!modifiers.contains(Modifier.ABSTRACT), "abstract record"); + } } for (TypeName superinterface : superinterfaces) { @@ -784,6 +895,15 @@ public TypeSpec build() { checkArgument(SourceVersion.isName(name), "not a valid enum constant: %s", name); } + 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 (FieldSpec fieldSpec : fieldSpecs) { if (kind == Kind.INTERFACE || kind == Kind.ANNOTATION) { requireExactlyOneOf(fieldSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE); @@ -791,6 +911,11 @@ public TypeSpec build() { checkState(fieldSpec.modifiers.containsAll(check), "%s %s.%s requires modifiers %s", kind, name, 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) { @@ -818,6 +943,10 @@ public TypeSpec build() { checkState(!methodSpec.hasModifier(Modifier.DEFAULT), "%s %s.%s cannot be default", kind, name, methodSpec.name); } + if (kind == Kind.RECORD) { + checkState(!methodSpec.hasModifier(Modifier.NATIVE), "%s %s.%s cannot be native", + kind, name, methodSpec.name); + } } for (TypeSpec typeSpec : typeSpecs) { @@ -826,7 +955,8 @@ 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.hasModifier(Modifier.ABSTRACT), "non-abstract type %s cannot declare abstract method %s", name, methodSpec.name); diff --git a/src/test/java/com/squareup/javapoet/TypeSpecTest.java b/src/test/java/com/squareup/javapoet/TypeSpecTest.java index 759b99d2d..a7c41cc46 100644 --- a/src/test/java/com/squareup/javapoet/TypeSpecTest.java +++ b/src/test/java/com/squareup/javapoet/TypeSpecTest.java @@ -347,6 +347,116 @@ private TypeElement getElement(Class clazz) { } } + @Test + public void basicRecord() throws Exception { + ParameterSpec keyComponent = ParameterSpec.builder(ClassName.get(String.class), "key").build(); + ParameterSpec valueComponent = ParameterSpec.builder(ClassName.get(String.class), "value").build(); + TypeSpec rec = TypeSpec.recordBuilder("KeyValue") + .addModifiers(Modifier.PUBLIC) + .addRecordComponent(keyComponent) + .addRecordComponent(valueComponent) + .compactConstructor(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addCode( + CodeBlock.builder() + .beginControlFlow("if ($N.indexOf(':') != -1)", keyComponent) + .addStatement("throw new $T()", ClassName.get(IllegalArgumentException.class)) + .endControlFlow() + .build()) + .build()) + .addSuperinterface(Cloneable.class) + .build(); + + assertThat(toString(rec)).isEqualTo("" + + "package com.squareup.tacos;\n" + + "\n" + + "import java.lang.Cloneable;\n" + + "import java.lang.IllegalArgumentException;\n" + + "import java.lang.String;\n" + + "\n" + + "public record KeyValue(String key, String value) implements Cloneable {\n" + + " public KeyValue {\n" + + " if (key.indexOf(':') != -1) {\n" + + " throw new IllegalArgumentException();\n" + + " }\n" + + " }\n" + + "}\n"); + } + + @Test + public void emptyRecord() throws Exception { + TypeSpec rec = TypeSpec.recordBuilder("Empty").build(); + + assertThat(toString(rec)).isEqualTo("" + + "package com.squareup.tacos;\n" + + "\n" + + "record Empty() {\n" + + "}\n"); + } + + @Test + public void compactConstructorOnlyRecord() throws Exception { + TypeSpec rec = TypeSpec.recordBuilder("Empty") + .addModifiers(Modifier.PUBLIC) + .compactConstructor(MethodSpec.constructorBuilder() + .addModifiers(Modifier.PUBLIC) + .addCode("$T.out.println($S);", ClassName.get(System.class), "Hello!") + .build()) + .build(); + + assertThat(toString(rec)).isEqualTo("" + + "package com.squareup.tacos;\n" + + "\n" + + "import java.lang.System;\n" + + "\n" + + "public record Empty() {\n" + + " public Empty {\n" + + " System.out.println(\"Hello!\");\n" + + " }\n" + + "}\n"); + } + + @Test + public void secondaryConstructorRecord() throws Exception { + TypeSpec rec = TypeSpec.recordBuilder("Person") + .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(); + + assertThat(toString(rec)).isEqualTo("" + + "package com.squareup.tacos;\n" + + "\n" + + "import java.lang.Integer;\n" + + "import java.lang.String;\n" + + "\n" + + "record Person(String name) {\n" + + " Person(int number) {\n" + + " this.name = Integer.toString(number);\n" + + " }\n" + + "}\n"); + } + + @Test + public void varargsRecord() throws Exception { + TypeSpec rec = TypeSpec.recordBuilder("Vararg") + .addModifiers(Modifier.PUBLIC) + .addRecordComponent(ParameterSpec.builder(TypeName.INT, "val").build()) + .addRecordComponent(ParameterSpec.builder(ArrayTypeName.of(ClassName.get(String.class)), "keys").build()) + .varargs() + .build(); + + assertThat(toString(rec)).isEqualTo("" + + "package com.squareup.tacos;\n" + + "\n" + + "import java.lang.String;\n" + + "\n" + + "public record Vararg(int val, String... keys) {\n" + + "}\n"); + } + @Test public void enumWithSubclassing() throws Exception { TypeSpec roshambo = TypeSpec.enumBuilder("Roshambo") .addModifiers(Modifier.PUBLIC) @@ -1226,6 +1336,46 @@ public void interfacePrivateMethods() { + "}\n"); } + @Test + public void recordComponentJavadoc() throws Exception { + TypeSpec taco = TypeSpec.recordBuilder("Taco") + .addJavadoc("Wants a cat to be a tacocat.\n") + .addRecordComponent(ParameterSpec.builder(ClassName.get(String.class), "shell") + .addJavadoc("the outer level of tortilla of the taco\n") + .build()) + .addRecordComponent(ParameterSpec.builder(boolean.class, "soft") + .addJavadoc("true for a soft flour tortilla, or false for a crunchy corn tortilla\n") + .build()) + .compactConstructor(MethodSpec.constructorBuilder() + .addJavadoc("Makes a taco without a cat :(.\n") + .addParameter(ParameterSpec.builder(ClassName.get(String.class), "shell") + .addJavadoc("the outer level of tortilla of the taco\n") + .build()) + .build()) + .build(); + // Ensures that the parameter javadoc inclusion works + assertThat(toString(taco)).isEqualTo("" + + "package com.squareup.tacos;\n" + + "\n" + + "import java.lang.String;\n" + + "\n" + + "/**\n" + + " * Wants a cat to be a tacocat.\n" + + " *\n" + + " * @param shell the outer level of tortilla of the taco\n" + + " * @param soft true for a soft flour tortilla, or false for a crunchy corn tortilla\n" + + " */\n" + + "record Taco(String shell, boolean soft) {\n" + + " /**\n" + + " * Makes a taco without a cat :(.\n" + + " *\n" + + " * @param shell the outer level of tortilla of the taco\n" + + " */\n" + + " Taco {\n" + + " }\n" + + "}\n"); + } + @Test public void annotationsInAnnotations() throws Exception { ClassName beef = ClassName.get(tacosPackage, "Beef"); ClassName chicken = ClassName.get(tacosPackage, "Chicken"); @@ -1482,6 +1632,10 @@ public void interfacePrivateMethods() { .addModifiers(Modifier.STATIC) .addEnumConstant("SALSA") .build()) + .addType(TypeSpec.recordBuilder("Veggie") + .addModifiers(Modifier.STATIC) + .addRecordComponent(ParameterSpec.builder(TypeName.INT, "name").build()) + .build()) .build(); assertThat(toString(taco)).isEqualTo("" + "package com.squareup.tacos;\n" @@ -1496,6 +1650,9 @@ public void interfacePrivateMethods() { + " enum Topping {\n" + " SALSA\n" + " }\n" + + "\n" + + " record Veggie(int name) {\n" + + " }\n" + "}\n"); } @@ -2585,6 +2742,17 @@ public void modifyTypes() { assertThat(builder.build().typeSpecs).isEmpty(); } + @Test + public void modifyRecordComponents() { + ParameterSpec shellComponent = ParameterSpec.builder(ClassName.get(String.class), "shell").build(); + TypeSpec.Builder builder = TypeSpec.recordBuilder("Taco") + .addRecordComponent(shellComponent) + .addRecordComponent(ParameterSpec.builder(TypeName.DOUBLE, "price").build()); + + builder.recordComponents.removeIf(p -> p.name.equals("price")); + assertThat(builder.build().recordComponents).containsExactly(shellComponent); + } + @Test public void modifyEnumConstants() { TypeSpec constantType = TypeSpec.anonymousClassBuilder("").build();