All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.amazon.smithy.traitcodegen.generators.BuilderGenerator Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package software.amazon.smithy.traitcodegen.generators;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.util.Iterator;
import java.util.Optional;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.shapes.BigDecimalShape;
import software.amazon.smithy.model.shapes.BigIntegerShape;
import software.amazon.smithy.model.shapes.BlobShape;
import software.amazon.smithy.model.shapes.BooleanShape;
import software.amazon.smithy.model.shapes.ByteShape;
import software.amazon.smithy.model.shapes.DocumentShape;
import software.amazon.smithy.model.shapes.DoubleShape;
import software.amazon.smithy.model.shapes.FloatShape;
import software.amazon.smithy.model.shapes.IntegerShape;
import software.amazon.smithy.model.shapes.ListShape;
import software.amazon.smithy.model.shapes.LongShape;
import software.amazon.smithy.model.shapes.MapShape;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeType;
import software.amazon.smithy.model.shapes.ShapeVisitor;
import software.amazon.smithy.model.shapes.ShortShape;
import software.amazon.smithy.model.shapes.StringShape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.TimestampShape;
import software.amazon.smithy.model.shapes.UnionShape;
import software.amazon.smithy.model.traits.AbstractTraitBuilder;
import software.amazon.smithy.model.traits.DefaultTrait;
import software.amazon.smithy.model.traits.StringListTrait;
import software.amazon.smithy.model.traits.TimestampFormatTrait;
import software.amazon.smithy.model.traits.TraitDefinition;
import software.amazon.smithy.traitcodegen.SymbolProperties;
import software.amazon.smithy.traitcodegen.TraitCodegenUtils;
import software.amazon.smithy.traitcodegen.writer.TraitCodegenWriter;
import software.amazon.smithy.utils.BuilderRef;
import software.amazon.smithy.utils.SmithyBuilder;

/**
 * Generates a static builder for a Java class.
 *
 * 

In addition to the static builder class, this generator will create * {@code builder()} and {@code toBuilder()} methods for the target class. */ final class BuilderGenerator implements Runnable { private final TraitCodegenWriter writer; private final Symbol symbol; private final SymbolProvider symbolProvider; private final Shape baseShape; private final Model model; BuilderGenerator(TraitCodegenWriter writer, Symbol symbol, SymbolProvider symbolProvider, Shape baseShape, Model model) { this.writer = writer; this.symbol = symbol; this.symbolProvider = symbolProvider; this.baseShape = baseShape; this.model = model; } @Override public void run() { // Only create builder methods for aggregate types. if (baseShape.getType().getCategory().equals(ShapeType.Category.AGGREGATE)) { writeToBuilderMethod(); writeBuilderMethod(); writeBuilderClass(); } } private void writeBuilderClass() { writer.writeDocString(writer.format("Builder for {@link $T}.", symbol)); writer.writeInline("public static final class Builder $C", (Runnable) this::writeBuilderInterface); writer.indent(); baseShape.accept(new BuilderPropertyGenerator()); writer.newLine(); writer.writeWithNoFormatting("private Builder() {}").newLine(); baseShape.accept(new BuilderSetterGenerator()); writer.override(); writer.openBlock("public $T build() {", "}", symbol, () -> writer.write("return new $C;", (Runnable) this::writeBuilderReturn)); writer.dedent().write("}"); writer.newLine(); } private void writeBuilderInterface() { if (baseShape.hasTrait(TraitDefinition.class)) { if (TraitCodegenUtils.isJavaStringList(baseShape, symbolProvider)) { writer.write("extends $T.Builder<$T, Builder> {", StringListTrait.class, symbol); } else { writer.write("extends $T<$T, Builder> {", AbstractTraitBuilder.class, symbol); } } else { writer.write("implements $T<$T> {", SmithyBuilder.class, symbol); } } private void writeBuilderReturn() { // String list traits need a custom builder return if (TraitCodegenUtils.isJavaStringList(baseShape, symbolProvider)) { writer.write("$T(getValues(), getSourceLocation())", symbol); } else { writer.write("$T(this)", symbol); } } private void writeToBuilderMethod() { writer.writeDocString(writer.format("Creates a builder used to build a {@link $T}.", symbol)); writer.openBlock("public $T<$T> toBuilder() {", "}", SmithyBuilder.class, symbol, () -> { writer.writeInlineWithNoFormatting("return builder()"); writer.indent(); if (baseShape.hasTrait(TraitDefinition.class)) { writer.writeInlineWithNoFormatting(".sourceLocation(getSourceLocation())"); } if (baseShape.members().isEmpty()) { writer.writeInlineWithNoFormatting(";"); } writer.newLine(); // Set all builder properties for any members in the shape if (baseShape.isListShape()) { writer.writeWithNoFormatting(".values(getValues());"); } else { Iterator memberIterator = baseShape.members().iterator(); while (memberIterator.hasNext()) { MemberShape member = memberIterator.next(); writer.writeInline(".$1L($1L)", symbolProvider.toMemberName(member)); if (memberIterator.hasNext()) { writer.writeInlineWithNoFormatting("\n"); } else { writer.writeInlineWithNoFormatting(";\n"); } } } writer.dedent(); }); writer.newLine(); } private void writeBuilderMethod() { writer.openBlock("public static Builder builder() {", "}", () -> writer.write("return new Builder();")).newLine(); } private final class BuilderPropertyGenerator extends ShapeVisitor.Default { @Override protected Void getDefault(Shape shape) { throw new UnsupportedOperationException("Does not support shape of type: " + shape.getType()); } @Override public Void listShape(ListShape shape) { // String list shapes do not need value properties if (TraitCodegenUtils.isJavaStringList(shape, symbolProvider)) { return null; } writeValuesProperty(shape); return null; } @Override public Void mapShape(MapShape shape) { writeValuesProperty(shape); return null; } @Override public Void structureShape(StructureShape shape) { shape.members().forEach(this::writeProperty); return null; } private void writeProperty(MemberShape shape) { Optional builderRefOptional = symbolProvider.toSymbol(shape).getProperty(SymbolProperties.BUILDER_REF_INITIALIZER); if (builderRefOptional.isPresent()) { writer.write("private final $1T<$2B> $3L = $1T.$4L;", BuilderRef.class, symbolProvider.toSymbol(shape), symbolProvider.toMemberName(shape), builderRefOptional.orElseThrow(RuntimeException::new)); return; } if (shape.hasNonNullDefault()) { writer.write("private $B $L = $C;", symbolProvider.toSymbol(shape), symbolProvider.toMemberName(shape), new DefaultInitializerGenerator(writer, model, symbolProvider, shape)); } else { writer.write("private $B $L;", symbolProvider.toSymbol(shape), symbolProvider.toMemberName(shape)); } } private void writeValuesProperty(Shape shape) { Symbol collectionSymbol = symbolProvider.toSymbol(shape); writer.write("private final $1T<$2B> $3L = $1T.$4L;", BuilderRef.class, collectionSymbol, "values", collectionSymbol.expectProperty(SymbolProperties.BUILDER_REF_INITIALIZER)); } } private final class BuilderSetterGenerator extends ShapeVisitor.Default { @Override protected Void getDefault(Shape shape) { throw new UnsupportedOperationException("Does not support shape of type: " + shape.getType()); } @Override public Void listShape(ListShape shape) { // String list shapes do not need setters if (TraitCodegenUtils.isJavaStringList(shape, symbolProvider)) { return null; } shape.accept(new SetterVisitor("values")); return null; } @Override public Void mapShape(MapShape shape) { shape.accept(new SetterVisitor("values")); return null; } @Override public Void structureShape(StructureShape shape) { shape.members().forEach( memberShape -> memberShape.accept(new SetterVisitor(symbolProvider.toMemberName(memberShape)))); return null; } } private final class SetterVisitor extends ShapeVisitor.Default { private final String memberName; private SetterVisitor(String memberName) { this.memberName = memberName; } @Override protected Void getDefault(Shape shape) { writer.openBlock("public Builder $1L($2B $1L) {", "}", memberName, symbolProvider.toSymbol(shape), () -> { writer.write("this.$1L = $1L;", memberName); writer.writeWithNoFormatting("return this;"); }).newLine(); return null; } @Override public Void listShape(ListShape shape) { writer.openBlock("public Builder $1L($2B $1L) {", "}", memberName, symbolProvider.toSymbol(shape), () -> { writer.write("clear$U();", memberName); writer.write("this.$1L.get().addAll($1L);", memberName); writer.writeWithNoFormatting("return this;"); }).newLine(); // Clear all writer.openBlock("public Builder clear$U() {", "}", memberName, () -> { writer.write("$L.get().clear();", memberName); writer.writeWithNoFormatting("return this;"); }).newLine(); // Set one writer.openBlock("public Builder add$U($T value) {", "}", memberName, symbolProvider.toSymbol(shape.getMember()), () -> { writer.write("$L.get().add(value);", memberName); writer.write("return this;"); }).newLine(); // Remove one writer.openBlock("public Builder remove$U($T value) {", "}", memberName, symbolProvider.toSymbol(shape.getMember()), () -> { writer.write("$L.get().remove(value);", memberName); writer.write("return this;"); }).newLine(); return null; } @Override public Void mapShape(MapShape shape) { // Set all writer.openBlock("public Builder $1L($2B $1L) {", "}", memberName, symbolProvider.toSymbol(shape), () -> { writer.write("clear$U();", memberName); writer.write("this.$1L.get().putAll($1L);", memberName); writer.write("return this;"); }); writer.newLine(); // Clear all writer.openBlock("public Builder clear$U() {", "}", memberName, () -> { writer.write("this.$L.get().clear();", memberName); writer.write("return this;"); }).newLine(); // Set one MemberShape keyShape = shape.getKey(); MemberShape valueShape = shape.getValue(); writer.openBlock("public Builder put$U($T key, $T value) {", "}", memberName, symbolProvider.toSymbol(keyShape), symbolProvider.toSymbol(valueShape), () -> { writer.write("this.$L.get().put(key, value);", memberName); writer.write("return this;"); }).newLine(); // Remove one writer.openBlock("public Builder remove$U($T $L) {", "}", memberName, symbolProvider.toSymbol(keyShape), memberName, () -> { writer.write("this.$1L.get().remove($1L);", memberName); writer.write("return this;"); }).newLine(); return null; } @Override public Void memberShape(MemberShape shape) { return model.expectShape(shape.getTarget()).accept(this); } } /** * Adds default values to builder properties. */ private static final class DefaultInitializerGenerator extends ShapeVisitor.DataShapeVisitor implements Runnable { private final TraitCodegenWriter writer; private final Model model; private final SymbolProvider symbolProvider; private final MemberShape member; private Node defaultValue; DefaultInitializerGenerator( TraitCodegenWriter writer, Model model, SymbolProvider symbolProvider, MemberShape member ) { this.writer = writer; this.model = model; this.symbolProvider = symbolProvider; this.member = member; } @Override public void run() { if (member.hasNonNullDefault()) { this.defaultValue = member.expectTrait(DefaultTrait.class).toNode(); member.accept(this); } } @Override public Void blobShape(BlobShape blobShape) { throw new UnsupportedOperationException("Blob default value cannot be set."); } @Override public Void booleanShape(BooleanShape booleanShape) { writer.write("$L", defaultValue.expectBooleanNode().getValue()); return null; } @Override public Void listShape(ListShape listShape) { throw new UnsupportedOperationException("List default values are not set with DefaultGenerator."); } @Override public Void mapShape(MapShape mapShape) { throw new UnsupportedOperationException("Map default values are not set with DefaultGenerator."); } @Override public Void byteShape(ByteShape byteShape) { // Bytes duplicate the integer toString method writer.write("$L", defaultValue.expectNumberNode().getValue().intValue()); return null; } @Override public Void shortShape(ShortShape shortShape) { // Shorts duplicate the int toString method writer.write("$L", defaultValue.expectNumberNode().getValue().intValue()); return null; } @Override public Void integerShape(IntegerShape integerShape) { writer.write("$L", defaultValue.expectNumberNode().getValue().intValue()); return null; } @Override public Void longShape(LongShape longShape) { writer.write("$LL", defaultValue.expectNumberNode().getValue().longValue()); return null; } @Override public Void floatShape(FloatShape floatShape) { writer.write("$Lf", defaultValue.expectNumberNode().getValue().floatValue()); return null; } @Override public Void documentShape(DocumentShape documentShape) { throw new UnsupportedOperationException("Document shape defaults cannot be set."); } @Override public Void doubleShape(DoubleShape doubleShape) { writer.write("$L", defaultValue.expectNumberNode().getValue().doubleValue()); return null; } @Override public Void bigIntegerShape(BigIntegerShape bigIntegerShape) { writer.write("$T.valueOf($L)", BigInteger.class, defaultValue.expectNumberNode().getValue().intValue()); return null; } @Override public Void bigDecimalShape(BigDecimalShape bigDecimalShape) { writer.write("$T.valueOf($L)", BigDecimal.class, defaultValue.expectNumberNode().getValue().doubleValue()); return null; } @Override public Void stringShape(StringShape stringShape) { writer.write("$S", defaultValue.expectStringNode().getValue()); return null; } @Override public Void structureShape(StructureShape structureShape) { throw new UnsupportedOperationException("Structure shape defaults cannot be set."); } @Override public Void unionShape(UnionShape unionShape) { throw new UnsupportedOperationException("Union shape defaults cannot be set."); } @Override public Void memberShape(MemberShape memberShape) { return model.expectShape(memberShape.getTarget()).accept(this); } @Override public Void timestampShape(TimestampShape timestampShape) { if (member.hasTrait(TimestampFormatTrait.class)) { switch (member.expectTrait(TimestampFormatTrait.class).getFormat()) { case EPOCH_SECONDS: writer.writeInline( "$T.ofEpochSecond($LL)", Instant.class, defaultValue.expectNumberNode().getValue().longValue() ); return null; case HTTP_DATE: writer.writeInline( "$T.from($T.RFC_1123_DATE_TIME.parse($S))", Instant.class, DateTimeFormatter.class, defaultValue.expectStringNode().getValue() ); return null; default: // Fall through on default break; } } writer.write("$T.parse($S)", Instant.class, defaultValue.expectStringNode().getValue()); return null; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy