Skip to content

Commit

Permalink
Add record support
Browse files Browse the repository at this point in the history
  • Loading branch information
xbailey committed Apr 30, 2024
1 parent d0bf75b commit 2282fea
Show file tree
Hide file tree
Showing 2 changed files with 211 additions and 9 deletions.
58 changes: 49 additions & 9 deletions javapoet/src/main/java/com/palantir/javapoet/TypeSpec.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -299,6 +307,11 @@ void emit(CodeWriter codeWriter, String enumName, Set<Modifier> 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()
Expand Down Expand Up @@ -393,15 +406,18 @@ void emit(CodeWriter codeWriter, String enumName, Set<Modifier> 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.
Expand Down Expand Up @@ -459,6 +475,28 @@ void emit(CodeWriter codeWriter, String enumName, Set<Modifier> 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<FieldSpec> 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) {
Expand Down Expand Up @@ -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<Modifier> implicitFieldModifiers;
private final Set<Modifier> implicitMethodModifiers;
Expand Down
162 changes: 162 additions & 0 deletions javapoet/src/test/java/com/palantir/javapoet/JavaFileTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down

0 comments on commit 2282fea

Please sign in to comment.