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

software.amazon.smithy.model.neighbor.NeighborProvider 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.neighbor;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.IdRefTrait;
import software.amazon.smithy.utils.ListUtils;

/**
 * Provides the neighbor relationships for a given shape.
 */
@FunctionalInterface
public interface NeighborProvider {
    /**
     * Gets the neighbor relationships of a shape.
     *
     * @param shape Shape to get neighbors for.
     * @return Returns the found neighbors.
     */
    List getNeighbors(Shape shape);

    /**
     * Creates a default NeighborProvider for the given model.
     *
     * @param model Model to create a neighbor provider for.
     * @return Returns the created neighbor provider.
     */
    static NeighborProvider of(Model model) {
        return new NeighborVisitor(model);
    }

    /**
     * Creates a NeighborProvider that precomputes the neighbors of a model.
     *
     * @param model Model to create a neighbor provider for.
     * @return Returns the created neighbor provider.
     */
    static NeighborProvider precomputed(Model model) {
        return precomputed(model, of(model));
    }

    /**
     * Creates a NeighborProvider that includes {@link RelationshipType#TRAIT}
     * relationships.
     *
     * @param model Model to use to look up trait shapes.
     * @param neighborProvider Provider to wrap.
     * @return Returns the created neighbor provider.
     */
    static NeighborProvider withTraitRelationships(Model model, NeighborProvider neighborProvider) {
        return shape -> {
            List relationships = neighborProvider.getNeighbors(shape);

            // Don't copy the array unless the shape has traits.
            if (shape.getAllTraits().isEmpty()) {
                return relationships;
            }

            // The delegate might have returned an immutable list, so copy first.
            relationships = new ArrayList<>(relationships);
            for (ShapeId trait : shape.getAllTraits().keySet()) {
                Relationship traitRel = model.getShape(trait)
                        .map(target -> Relationship.create(shape, RelationshipType.TRAIT, target))
                        .orElseGet(() -> Relationship.createInvalid(shape, RelationshipType.TRAIT, trait));
                relationships.add(traitRel);
            }

            return relationships;
        };
    }

    /**
     * Creates a NeighborProvider that includes {@link RelationshipType#ID_REF}
     * relationships.
     *
     * @param model Model to use to look up shapes referenced by {@link IdRefTrait}.
     * @param neighborProvider Provider to wrap.
     * @return Returns the created neighbor provider.
     */
    static NeighborProvider withIdRefRelationships(Model model, NeighborProvider neighborProvider) {
        Map> idRefRelationships = new IdRefShapeRelationships(model).getRelationships();
        return shape -> {
            List relationships = neighborProvider.getNeighbors(shape);

            if (!idRefRelationships.containsKey(shape.getId())) {
                return relationships;
            }

            relationships = new ArrayList<>(relationships);
            relationships.addAll(idRefRelationships.get(shape.getId()));
            return relationships;
        };
    }

    /**
     * Creates a NeighborProvider that precomputes the neighbors of a model.
     *
     * @param model Model to create a neighbor provider for.
     * @param provider Provider to use when precomputing.
     * @return Returns the created neighbor provider.
     */
    static NeighborProvider precomputed(Model model, NeighborProvider provider) {
        Set shapes = model.toSet();
        Map> relationships = new HashMap<>(shapes.size());
        for (Shape shape : shapes) {
            relationships.put(shape, provider.getNeighbors(shape));
        }
        return shape -> relationships.getOrDefault(shape, ListUtils.of());
    }

    /**
     * Returns a NeighborProvider that returns relationships that point at a
     * given shape rather than relationships that the given shape points at.
     *
     * @param model Model to build reverse relationships from.
     * @return Returns the reverse neighbor provider.
     */
    static NeighborProvider reverse(Model model) {
        return reverse(model, of(model));
    }

    /**
     * Returns a NeighborProvider that returns relationships that point at a
     * given shape rather than relationships that the given shape points at.
     *
     * @param model Model to build reverse relationships from.
     * @param forwardProvider The forward directed neighbor provider to grab relationships from.
     * @return Returns the reverse neighbor provider.
     */
    static NeighborProvider reverse(Model model, NeighborProvider forwardProvider) {
        // Note: this method previously needed lots of intermediate representations
        // stored in memory to create a Map> that contains
        // only unique relationships. It was done using Stream, distinct, and groupingBy.
        // However, when trying to load ridiculously large models, that approach consumes
        // tons of heap. This approach allocates as little as possible (I think), but
        // does require creating an ArrayList copy of a Set each time neighbors are returned.
        Map> targetedFrom = new HashMap<>();

        for (Shape shape : model.toSet()) {
            for (Relationship rel : forwardProvider.getNeighbors(shape)) {
                targetedFrom.computeIfAbsent(rel.getNeighborShapeId(), id -> new HashSet<>()).add(rel);
            }
        }

        return shape -> {
            Set shapes = targetedFrom.get(shape.getId());
            return shapes == null ? Collections.emptyList() : ListUtils.copyOf(shapes);
        };
    }

    /**
     * Caches the results of calling a delegate provider.
     *
     * @param provider Provider to delegate to and cache.
     * @return Returns the thread-safe caching neighbor provider.
     */
    static NeighborProvider cached(NeighborProvider provider) {
        Map> relationships = new ConcurrentHashMap<>();
        return shape -> relationships.computeIfAbsent(shape, provider::getNeighbors);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy