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

software.amazon.smithy.traitcodegen.generators.FromNodeGenerator 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.time.Instant;
import java.util.Iterator;
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.ExpectationNotMetException;
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.EnumShape;
import software.amazon.smithy.model.shapes.FloatShape;
import software.amazon.smithy.model.shapes.IntEnumShape;
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.NumberShape;
import software.amazon.smithy.model.shapes.Shape;
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.TimestampFormatTrait;
import software.amazon.smithy.traitcodegen.TraitCodegenUtils;
import software.amazon.smithy.traitcodegen.writer.TraitCodegenWriter;
import software.amazon.smithy.utils.StringUtils;

/**
 * Generates the static {@code fromNode} method to deserialize a smithy node into an instance of a Java class.
 */
final class FromNodeGenerator extends TraitVisitor implements Runnable {
    private final TraitCodegenWriter writer;
    private final Symbol symbol;
    private final Shape shape;
    private final SymbolProvider symbolProvider;
    private final Model model;

    FromNodeGenerator(TraitCodegenWriter writer, Symbol symbol, Shape shape, SymbolProvider symbolProvider,
                      Model model) {
        this.writer = writer;
        this.symbol = symbol;
        this.shape = shape;
        this.symbolProvider = symbolProvider;
        this.model = model;
    }

    @Override
    public void run() {
        shape.accept(this);
    }

    @Override
    public Void listShape(ListShape shape) {
        if (TraitCodegenUtils.isJavaStringList(shape, symbolProvider)) {
            return null;
        }

        writeFromNodeJavaDoc();
        writer.openBlock("public static $T fromNode($T node) {", "}",
                symbol, Node.class, () -> {
            writer.writeWithNoFormatting("Builder builder = builder();");
            shape.accept(new FromNodeMapperVisitor(writer, model, "node"));
            writer.writeWithNoFormatting("return builder.build();");
        });
        writer.newLine();

        return null;
    }

    @Override
    public Void mapShape(MapShape shape) {
        writeFromNodeJavaDoc();
        writer.openBlock("public static $T fromNode($T node) {", "}",
                symbol, Node.class, () -> {
                    writer.writeWithNoFormatting("Builder builder = builder();");
                    shape.accept(new FromNodeMapperVisitor(writer, model, "node"));
                    writer.writeWithNoFormatting("return builder.build();");
                });
        writer.newLine();

        return null;
    }

    @Override
    public Void documentShape(DocumentShape shape) {
        return null;
    }

    @Override
    public Void stringShape(StringShape shape) {
        return null;
    }

    @Override
    protected Void numberShape(NumberShape shape) {
        // Number shapes do not create a from node method
        return null;
    }

    @Override
    public Void structureShape(StructureShape shape) {
        writeFromNodeJavaDoc();
        writer.write("public static $T fromNode($T node) {", symbol, Node.class);
        writer.indent();
        writer.write("Builder builder = builder();");
        // If the shape has no members (i.e. is an annotation trait) then there will be no member setters, and we
        // need to terminate the line.
        writer.putContext("isEmpty", shape.members().isEmpty());
        writer.write("node.expectObjectNode()${?isEmpty};${/isEmpty}");
        writer.indent();
        Iterator memberIterator = shape.members().iterator();
        while (memberIterator.hasNext()) {
            MemberShape member = memberIterator.next();
            member.accept(new MemberGenerator(member));
            if (memberIterator.hasNext()) {
                writer.writeInlineWithNoFormatting("\n");
            } else {
                writer.writeWithNoFormatting(";\n");
            }
        }
        writer.dedent();
        writer.write("return builder.build();");
        writer.dedent().write("}");
        writer.newLine();

        return null;
    }

    @Override
    public Void timestampShape(TimestampShape shape) {
        writeFromNodeJavaDoc();
        writer.openBlock("public static $T fromNode($T node) {", "}",
                symbol, Node.class, this::writeTimestampDeser);
        writer.newLine();

        return null;
    }

    private void writeTimestampDeser() {
        // If the trait has the timestamp format trait then we only need a single
        // way to deserialize the input node. Without the timestamp format trait
        // timestamp should be able to handle both epoch seconds and date time formats.
        if (shape.hasTrait(TimestampFormatTrait.class)) {
            writer.write("return new $T($C, node.getSourceLocation());",
                    symbol,
                    (Runnable) () -> shape.accept(new FromNodeMapperVisitor(writer, model, "node")));
        } else {
            writer.openBlock("if (node.isNumberNode()) {", "}", () -> {
                writer.write("return new $T($T.ofEpochSecond(node.expectNumberNode().getValue().longValue()),",
                        symbol, Instant.class).indent();
                writer.writeWithNoFormatting("node.getSourceLocation());").dedent();
            });
            writer.write("return new $T($T.parse(node.expectStringNode().getValue()), node.getSourceLocation());",
                    symbol, Instant.class);
        }
        writer.newLine();
    }

    private void writeFromNodeJavaDoc() {
        writer.writeDocString(writer.format("Creates a {@link $1T} from a {@link Node}.\n\n"
                    + "@param node Node to create the $1T from.\n"
                    + "@return Returns the created $1T.\n"
                    + "@throws $2T if the given Node is invalid.\n",
                symbol, ExpectationNotMetException.class));
    }

    /**
     * Generates the mapping from a node member to a builder field.
     */
    private final class MemberGenerator extends ShapeVisitor.DataShapeVisitor {
        private final String memberName;
        private final String fieldName;
        private final String memberPrefix;

        private MemberGenerator(MemberShape member) {
            this.fieldName = member.getMemberName();
            this.memberName = symbolProvider.toMemberName(member);
            this.memberPrefix = (member.isRequired() && !member.hasNonNullDefault()) ? ".expect" : ".get";
        }

        @Override
        public Void memberShape(MemberShape shape) {
            return model.expectShape(shape.getTarget()).accept(this);
        }

        @Override
        public Void booleanShape(BooleanShape shape) {
            writer.writeInline(memberPrefix + "BooleanMember($S, builder::$L)", fieldName, memberName);
            return null;
        }

        @Override
        public Void listShape(ListShape shape) {
            writer.writeInline(memberPrefix + "ArrayMember($1S, n -> $3C, builder::$2L)",
                    fieldName, memberName,
                    (Runnable) () -> shape.getMember().accept(new FromNodeMapperVisitor(writer, model, "n")));
            return null;
        }

        @Override
        public Void byteShape(ByteShape shape) {
            writer.writeInline(memberPrefix + "NumberMember($S, n -> builder.$L(n.byteValue()))",
                    fieldName, memberName);
            return null;
        }

        @Override
        public Void shortShape(ShortShape shape) {
            writer.writeInline(memberPrefix + "NumberMember($S, n -> builder.$L(n.shortValue()))",
                    fieldName, memberName);
            return null;
        }

        @Override
        public Void integerShape(IntegerShape shape) {
            writer.writeInline(memberPrefix + "NumberMember($S, n -> builder.$L(n.intValue()))",
                    fieldName, memberName);
            return null;
        }

        @Override
        public Void longShape(LongShape shape) {
            writer.writeInline(memberPrefix + "NumberMember($S, n -> builder.$L(n.longValue()))",
                    fieldName, memberName);
            return null;
        }

        @Override
        public Void floatShape(FloatShape shape) {
            writer.writeInline(memberPrefix + "NumberMember($S, n -> builder.$L(n.floatValue()))",
                    fieldName, memberName);
            return null;
        }

        @Override
        public Void documentShape(DocumentShape shape) {
            writer.writeInline(memberPrefix + "Member($1S, $3T::expectObjectNode, builder::$2L)",
                    fieldName, memberName, Node.class);
            return null;
        }

        @Override
        public Void doubleShape(DoubleShape shape) {
            writer.writeInline(memberPrefix + "NumberMember($S, n -> builder.$L(n.doubleValue()))",
                    fieldName, memberName);
            return null;
        }

        @Override
        public Void bigIntegerShape(BigIntegerShape shape) {
            writer.writeInline(memberPrefix
                            + "Member($S, n -> n.expectNumberNode().asBigDecimal().get().toBigInteger(), builder::$L)",
                    fieldName, memberName);
            return null;
        }

        @Override
        public Void bigDecimalShape(BigDecimalShape shape) {
            writer.writeInline(memberPrefix
                            + "Member($S, n -> n.expectNumberNode().asBigDecimal().get(), builder::$L)",
                    fieldName, memberName);
            return null;
        }

        @Override
        public Void mapShape(MapShape shape) {
            writer.disableNewlines();
            writer.openBlock(memberPrefix
                            + "ObjectMember($S, o -> o.getMembers().forEach((k, v) -> {\n", "}))",
                    fieldName,
                    () -> writer.write("builder.put$L($C, $C);\n",
                            StringUtils.capitalize(memberName),
                            (Runnable) () -> shape.getKey().accept(new FromNodeMapperVisitor(writer, model, "k")),
                            (Runnable) () -> shape.getValue().accept(new FromNodeMapperVisitor(writer, model, "v")))
            );
            writer.enableNewlines();
            return null;
        }

        @Override
        public Void intEnumShape(IntEnumShape shape) {
            writer.writeInline(memberPrefix + "NumberMember($S, n -> builder.$L($T.from(n.intValue())))",
                    fieldName, memberName, symbolProvider.toSymbol(shape));
            return null;
        }

        @Override
        public Void stringShape(StringShape shape) {
            if (TraitCodegenUtils.isJavaString(symbolProvider.toSymbol(shape))) {
                writer.writeInline(memberPrefix + "StringMember($S, builder::$L)", fieldName, memberName);
            } else {
                writer.writeInline(memberPrefix + "Member($1S, n -> $3C, builder::$2L)",
                        fieldName, memberName,
                        (Runnable) () -> shape.accept(new FromNodeMapperVisitor(writer, model, "n"))
                );
            }
            return null;
        }

        @Override
        public Void enumShape(EnumShape shape) {
            writer.writeInline(memberPrefix + "StringMember($S, n -> builder.$L($T.from(n)))",
                    fieldName, memberName, symbolProvider.toSymbol(shape));
            return null;
        }

        @Override
        public Void structureShape(StructureShape shape) {
            writer.writeInline(memberPrefix + "Member($1S, n -> $3C, builder::$2L)",
                    fieldName, memberName,
                    (Runnable) () -> shape.accept(new FromNodeMapperVisitor(writer, model, "n"))
            );
            return null;
        }

        @Override
        public Void timestampShape(TimestampShape shape) {
            writer.writeInline(memberPrefix + "Member($1S, n -> $3C, builder::$2L)",
                    fieldName, memberName,
                    (Runnable) () -> shape.accept(new FromNodeMapperVisitor(writer, model, "n")));
            return null;
        }

        @Override
        public Void unionShape(UnionShape shape) {
            throw new UnsupportedOperationException("Shape not supported " + shape);
        }

        @Override
        public Void blobShape(BlobShape shape) {
            throw new UnsupportedOperationException("Shape not supported " + shape);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy