From 4613d7ffc81652dd8623083c2601b71609c5da30 Mon Sep 17 00:00:00 2001 From: Michael Dowling Date: Wed, 4 Sep 2019 14:33:55 -0700 Subject: [PATCH] Add method to get non-trait shapes This commit adds a method to ModelTransformer to get all shapes from a model that are not only used as part of a trait or definition of a trait. This makes it easier for code generators to get a shape index that contains only shapes that they typically want to generate. A convenience method was added to PluginContext to make this more apparent when implementing SmithyBuild plugins. The result is also cached since it can be an expensive thing to compute. --- .../amazon/smithy/build/PluginContext.java | 27 +++++++++++++++++ .../smithy/build/PluginContextTest.java | 19 ++++++++++++ .../model/transform/ModelTransformer.java | 29 +++++++++++++++++++ .../model/transform/ModelTransformerTest.java | 14 +++++++++ 4 files changed, 89 insertions(+) diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/PluginContext.java b/smithy-build/src/main/java/software/amazon/smithy/build/PluginContext.java index 87385426ca5..4b052595ec8 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/PluginContext.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/PluginContext.java @@ -28,7 +28,9 @@ import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ShapeIndex; import software.amazon.smithy.model.shapes.ToShapeId; +import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.model.validation.ValidationEvent; import software.amazon.smithy.utils.SetUtils; import software.amazon.smithy.utils.SmithyBuilder; @@ -46,6 +48,7 @@ public final class PluginContext { private final FileManifest fileManifest; private final ClassLoader pluginClassLoader; private final Set sources; + private ShapeIndex nonTraitsIndex; private PluginContext(Builder builder) { model = SmithyBuilder.requiredState("model", builder.model); @@ -144,6 +147,30 @@ public Optional getPluginClassLoader() { return Optional.ofNullable(pluginClassLoader); } + /** + * Gets all shapes from a model as a {@code ShapeIndex} where shapes that + * define traits or shapes that are only used as part of a trait + * definition have been removed. + * + *

This is typically functionality used by code generators when + * generating data structures from a model. It's useful because it only + * provides shapes that are used to describe data structures rather than + * shapes used to describe metadata about the data structures. + * + *

Note: this method just calls {@link ModelTransformer#getNonTraitShapes}. + * It's added to {@code PluginContext} to make it more easily available + * to code generators. + * + * @return Returns a ShapeIndex containing matching shapes. + */ + public synchronized ShapeIndex getNonTraitShapes() { + if (nonTraitsIndex == null) { + nonTraitsIndex = ModelTransformer.create().getNonTraitShapes(model); + } + + return nonTraitsIndex; + } + /** * Gets the source models, or models that are considered the subject * of the build. diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/PluginContextTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/PluginContextTest.java index bb8d7b2c987..80f0aaf85f1 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/PluginContextTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/PluginContextTest.java @@ -8,6 +8,8 @@ import org.junit.jupiter.api.Test; import software.amazon.smithy.build.model.ProjectionConfig; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ShapeIndex; +import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.utils.ListUtils; public class PluginContextTest { @@ -42,4 +44,21 @@ public void hasSources() { assertThat(context.getSources(), contains(Paths.get("/foo/baz"))); } + + @Test + public void createsNonTraitShapeIndex() { + Model model = Model.assembler() + .addImport(getClass().getResource("simple-model.json")) + .assemble() + .unwrap(); + ShapeIndex scrubbed = ModelTransformer.create().getNonTraitShapes(model); + PluginContext context = PluginContext.builder() + .fileManifest(new MockManifest()) + .model(model) + .sources(ListUtils.of(Paths.get("/foo/baz"))) + .build(); + + assertThat(context.getNonTraitShapes(), equalTo(scrubbed)); + assertThat(context.getNonTraitShapes(), equalTo(scrubbed)); // trigger loading from cache + } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java index 2e9d6e04c4d..c81b8b49ac9 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java @@ -18,6 +18,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.ServiceLoader; import java.util.function.BiFunction; import java.util.function.BiPredicate; @@ -28,6 +29,7 @@ import software.amazon.smithy.model.neighbor.UnreferencedTraitDefinitions; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeIndex; import software.amazon.smithy.model.traits.Trait; import software.amazon.smithy.model.traits.TraitDefinition; import software.amazon.smithy.utils.FunctionalUtils; @@ -335,4 +337,31 @@ public Model removeUnreferencedTraitDefinitions(Model model, Predicate ke public Model scrubTraitDefinitions(Model model) { return new ScrubTraitDefinitions().transform(this, model); } + + /** + * Gets all shapes from a model as a {@code ShapeIndex} where shapes that + * define traits or shapes that are only used as part of a trait + * definition have been removed. + * + * @param model Model that contains shapes. + * @return Returns a ShapeIndex containing matching shapes. + */ + public ShapeIndex getNonTraitShapes(Model model) { + ShapeIndex currentIndex = model.getShapeIndex(); + ShapeIndex.Builder indexBuilder = ShapeIndex.builder(); + + // ScrubTraitDefinitions is used to removed traits and trait shapes. + // However, the returned model can't be returned directly because + // as traits are removed, uses of that trait are removed. Instead, + // a ShapeIndex is created by getting all shape IDs from the modified + // model, grabbing shapes from the original model, and building a new + // ShapeIndex. + scrubTraitDefinitions(model).getShapeIndex().shapes() + .map(Shape::getId) + .map(currentIndex::getShape) + .map(Optional::get) + .forEach(indexBuilder::addShape); + + return indexBuilder.build(); + } } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java index ac8450115da..f5435653ebd 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java @@ -26,6 +26,8 @@ import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.ShapeIndex; +import software.amazon.smithy.model.traits.EnumTrait; +import software.amazon.smithy.model.traits.ReadonlyTrait; public class ModelTransformerTest { @@ -46,6 +48,18 @@ public void discoversOnRemoveClassesWithSpi() { Matchers.equalTo(Optional.of(Collections.emptyList()))); } + @Test + public void removesTraitShapesButNotTraitUsage() { + ModelTransformer transformer = ModelTransformer.create(); + Model model = createTestModel(); + ShapeIndex index = transformer.getNonTraitShapes(model); + ShapeId operation = ShapeId.from("ns.foo#MyOperation"); + + assertThat(index.getShape(operation), Matchers.not(Optional.empty())); + assertThat(index.getShape(operation).get().getTrait(ReadonlyTrait.class), Matchers.not(Optional.empty())); + assertThat(index.getShape(EnumTrait.ID), Matchers.equalTo(Optional.empty())); + } + private Model createTestModel() { return Model.assembler() .addImport(ModelTransformerTest.class.getResource("test-model.json"))