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

software.amazon.smithy.model.transform.MarkAndSweep Maven / Gradle / Ivy

/*
 * Copyright 2019 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.model.transform;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.neighbor.NeighborProvider;
import software.amazon.smithy.model.neighbor.Relationship;
import software.amazon.smithy.model.neighbor.RelationshipDirection;
import software.amazon.smithy.model.neighbor.RelationshipType;
import software.amazon.smithy.model.shapes.Shape;

/**
 * Performs a garbage collection style cleanup of a model by removing
 * unreferenced shapes based on a custom {@code marker} function.
 *
 * 

The marker function is invoked at the start of each round and passed * a context object. The marker can query the context object and mark shapes * as needing to be removed. The MarkAndSweep then finds all shapes * that have targets to them but is only targeted by shapes that have been * marked for removal. These matching shapes are then marked for removal as * well, potentially freeing up other shapes to be marked for removal in the * next round. This process continues until no new shapes are marked in a * round. */ final class MarkAndSweep { private final Predicate sweepFilter; private final Consumer marker; MarkAndSweep(Consumer marker, Predicate sweepFilter) { this.marker = marker; this.sweepFilter = sweepFilter; } Set markAndSweep(Model model) { NeighborProvider reverseNeighbors = NeighborProvider.reverse(model); MarkerContext context = new MarkerContext(reverseNeighbors, model, sweepFilter); int currentSize; do { currentSize = context.getMarkedForRemoval().size(); marker.accept(context); // Find shapes that are only referenced by a shape that has been marked for removal. model.shapes().filter(shape -> !shape.isMemberShape()).forEach(shape -> { if (!context.getMarkedForRemoval().contains(shape)) { Set targetedFrom = context.getTargetedFrom(shape); if (!targetedFrom.isEmpty()) { targetedFrom.removeAll(context.getMarkedForRemoval()); if (targetedFrom.isEmpty()) { context.markShape(shape); } } } }); } while (currentSize != context.getMarkedForRemoval().size()); return context.getMarkedForRemoval(); } /** * Context object passed to the marked in each pass on the model. */ static final class MarkerContext { private final NeighborProvider reverseProvider; private final Model model; private final Set markedForRemoval = new HashSet<>(); private final Predicate sweepFilter; MarkerContext(NeighborProvider reverseProvider, Model model, Predicate sweepFilter) { this.reverseProvider = reverseProvider; this.model = model; this.sweepFilter = sweepFilter; } /** * @return Gets the model being evaluated. */ Model getModel() { return model; } /** * @return Gets the immutable set of shapes marked for removal. */ Set getMarkedForRemoval() { return Collections.unmodifiableSet(markedForRemoval); } /** * Marks a shape for removal. * * @param shape Shape to remove. */ void markShape(Shape shape) { if (sweepFilter.test(shape)) { markedForRemoval.add(shape); markedForRemoval.addAll(shape.members()); } } /** * Gets the set of shapes that refer to the given shape. * * @param shape Shape to check for relationships to. * @return Returns the shapes that reference the given shape. */ Set getTargetedFrom(Shape shape) { return findRelationshipsTo(shape).map(Relationship::getShape).collect(Collectors.toSet()); } private Stream findRelationshipsTo(Shape shape) { return reverseProvider.getNeighbors(shape).stream() // We are only interested in references to this shape from // other shapes, not references to this shape that the shape // contains (like members). .filter(rel -> { RelationshipType type = rel.getRelationshipType(); return type.getDirection() == RelationshipDirection.DIRECTED && !type.isMemberBinding(); }) // Don't allow recursive member references to exclude themselves. // This check ensures that recursive member references don't exclude // themselves from being marked by seeing if the relationship is a member // target (e.g., an aggregate shape that targets a member) .filter(rel -> rel.getRelationshipType() != RelationshipType.MEMBER_TARGET || !rel.getShape().getId().withoutMember().equals(rel.getNeighborShapeId())); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy