diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/ShapeGenerationOrder.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/ShapeGenerationOrder.java new file mode 100644 index 00000000000..23bb7abc81f --- /dev/null +++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/ShapeGenerationOrder.java @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.codegen.core; + +/** + * Shapes order for code generation. + * + *

CodegenDirector order the shapes appropriately before feeding them to the code generators. See {@link + * software.amazon.smithy.codegen.core.directed.CodegenDirector#shapeGenerationOrder(ShapeGenerationOrder)} + */ +public enum ShapeGenerationOrder { + /** + * Shapes ordered in reverse-topological order. Also see {@link TopologicalIndex} + */ + TOPOLOGICAL, + + /** + * Shapes ordered alphabetically by their names. + */ + ALPHABETICAL, + + /** + * Shapes without order. + */ + NONE +} diff --git a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java index b70bdecea68..f6c994bca56 100644 --- a/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java +++ b/smithy-codegen-core/src/main/java/software/amazon/smithy/codegen/core/directed/CodegenDirector.java @@ -16,6 +16,7 @@ package software.amazon.smithy.codegen.core.directed; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.ServiceLoader; @@ -26,6 +27,7 @@ import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.codegen.core.CodegenContext; import software.amazon.smithy.codegen.core.ImportContainer; +import software.amazon.smithy.codegen.core.ShapeGenerationOrder; import software.amazon.smithy.codegen.core.SmithyIntegration; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.codegen.core.SymbolWriter; @@ -75,6 +77,7 @@ public final class CodegenDirector< private Supplier> integrationFinder; private DirectedCodegen directedCodegen; private final List> transforms = new ArrayList<>(); + private ShapeGenerationOrder shapeGenerationOrder = ShapeGenerationOrder.TOPOLOGICAL; /** * Simplifies a Smithy model for code generation of a single service. @@ -250,6 +253,18 @@ public void changeStringEnumsToEnumShapes(boolean synthesizeEnumNames) { }); } + /** + * Sets the shapes order for code generation. + * + *

CodegenDirector order the shapes appropriately before passing them to the code generators. + * The default order is topological, and can be overridden with this method + * + * @param order the order to use for the shape generation process. + */ + public void shapeGenerationOrder(ShapeGenerationOrder order) { + this.shapeGenerationOrder = order; + } + /** * Sorts all members of the model prior to codegen. * @@ -328,6 +343,7 @@ private void validateState() { SmithyBuilder.requiredState("settings", settings); SmithyBuilder.requiredState("fileManifest", fileManifest); SmithyBuilder.requiredState("directedCodegen", directedCodegen); + SmithyBuilder.requiredState("shapeGenerationOrder", shapeGenerationOrder); // Use a default integration finder implementation. if (integrationFinder == null) { @@ -390,16 +406,36 @@ private void registerInterceptors(C context, List integrations) { } private void generateShapesInService(C context, ServiceShape serviceShape) { - LOGGER.fine(() -> "Generating shapes for " + directedCodegen.getClass().getName()); + LOGGER.fine(() -> String.format("Generating shapes for %s in %s order", + directedCodegen.getClass().getName(), this.shapeGenerationOrder.name())); Set shapes = new Walker(context.model()).walkShapes(serviceShape); - TopologicalIndex topologicalIndex = TopologicalIndex.of(context.model()); ShapeGenerator generator = new ShapeGenerator<>(context, serviceShape, directedCodegen); - for (Shape shape : topologicalIndex.getOrderedShapes()) { - if (shapes.contains(shape)) { - shape.accept(generator); - } + List orderedShapes = new ArrayList<>(); + + switch (this.shapeGenerationOrder) { + case ALPHABETICAL: + orderedShapes.addAll(shapes); + orderedShapes.sort(Comparator.comparing(s -> s.getId().getName(serviceShape))); + break; + case NONE: + orderedShapes.addAll(shapes); + break; + case TOPOLOGICAL: + default: + TopologicalIndex topologicalIndex = TopologicalIndex.of(context.model()); + for (Shape shape : topologicalIndex.getOrderedShapes()) { + if (shapes.contains(shape)) { + orderedShapes.add(shape); + } + } + for (Shape shape : topologicalIndex.getRecursiveShapes()) { + if (shapes.contains(shape)) { + orderedShapes.add(shape); + } + } } - for (Shape shape : topologicalIndex.getRecursiveShapes()) { + + for (Shape shape : orderedShapes) { if (shapes.contains(shape)) { shape.accept(generator); } diff --git a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java index 79dc47eea1b..8c7f07273a9 100644 --- a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java +++ b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.Test; import software.amazon.smithy.build.FileManifest; import software.amazon.smithy.build.MockManifest; +import software.amazon.smithy.codegen.core.ShapeGenerationOrder; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.codegen.core.WriterDelegator; @@ -227,7 +228,7 @@ public void performsCodegenWithStringEnumsChangedToEnumShapes() { } @Test - public void sortsShapes() { + public void sortsShapesWithDefaultTopologicalOrder() { TestDirected testDirected = new TestDirected(); CodegenDirector runner = new CodegenDirector<>(); @@ -260,4 +261,72 @@ public void sortsShapes() { ShapeId.from("smithy.example#Foo") )); } + + @Test + public void testShapesGenerationWithAlphabeticalOrder() { + TestDirected testDirected = new TestDirected(); + CodegenDirector runner + = new CodegenDirector<>(); + FileManifest manifest = new MockManifest(); + Model model = Model.assembler() + .addImport(getClass().getResource("needs-sorting.smithy")) + .assemble() + .unwrap(); + + runner.settings(new TestSettings()); + runner.directedCodegen(testDirected); + runner.fileManifest(manifest); + runner.service(ShapeId.from("smithy.example#Foo")); + runner.model(model); + runner.integrationClass(TestIntegration.class); + runner.performDefaultCodegenTransforms(); + runner.shapeGenerationOrder(ShapeGenerationOrder.ALPHABETICAL); + runner.run(); + + assertThat(testDirected.generatedShapes, contains( + ShapeId.from("smithy.example#A"), + ShapeId.from("smithy.example#B"), + ShapeId.from("smithy.example#C"), + ShapeId.from("smithy.example#D"), + ShapeId.from("smithy.example#FooOperationInput"), + ShapeId.from("smithy.example#FooOperationOutput"), + ShapeId.from("smithy.example#RecursiveA"), + ShapeId.from("smithy.example#RecursiveB"), + ShapeId.from("smithy.example#Foo") + )); + } + + @Test + public void testShapesGenerationWithoutOrder() { + TestDirected testDirected = new TestDirected(); + CodegenDirector runner + = new CodegenDirector<>(); + FileManifest manifest = new MockManifest(); + Model model = Model.assembler() + .addImport(getClass().getResource("needs-sorting.smithy")) + .assemble() + .unwrap(); + + runner.settings(new TestSettings()); + runner.directedCodegen(testDirected); + runner.fileManifest(manifest); + runner.service(ShapeId.from("smithy.example#Foo")); + runner.model(model); + runner.integrationClass(TestIntegration.class); + runner.performDefaultCodegenTransforms(); + runner.shapeGenerationOrder(ShapeGenerationOrder.NONE); + runner.run(); + + assertThat(testDirected.generatedShapes, contains( + ShapeId.from("smithy.example#FooOperationOutput"), + ShapeId.from("smithy.example#A"), + ShapeId.from("smithy.example#B"), + ShapeId.from("smithy.example#C"), + ShapeId.from("smithy.example#D"), + ShapeId.from("smithy.example#FooOperationInput"), + ShapeId.from("smithy.example#RecursiveA"), + ShapeId.from("smithy.example#RecursiveB"), + ShapeId.from("smithy.example#Foo") + )); + } }