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

software.amazon.smithy.model.loader.ApplyMixin Maven / Gradle / Ivy

/*
 * Copyright 2022 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.loader;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import software.amazon.smithy.model.SourceException;
import software.amazon.smithy.model.shapes.AbstractShapeBuilder;
import software.amazon.smithy.model.shapes.MemberShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.model.validation.Validator;

/**
 * Applies mixins to shapes after the mixins have been resolved.
 */
final class ApplyMixin implements ShapeModifier {
    private final ShapeId mixin;
    private List events;
    private Map> potentiallyIntroducedTraits;

    ApplyMixin(ShapeId mixin) {
        this.mixin = mixin;
    }

    @Override
    public void modifyMember(
            AbstractShapeBuilder shapeBuilder,
            MemberShape.Builder memberBuilder,
            Function> unclaimedTraits,
            Function shapeMap
    ) {
        // The target could have been set by resource based properties.
        if (memberBuilder.getTarget() != null) {
            return;
        }
        // Members inherited from mixins can have their targets elided, so here we set them
        // to the target defined in the mixin.
        Shape mixinShape = shapeMap.apply(mixin);
        if (mixinShape == null) {
            throw new SourceException("Cannot apply mixin to " + memberBuilder.getId() + ": " + mixin + " not found",
                                      memberBuilder);
        }

        String name = memberBuilder.getId().getMember().get();
        mixinShape.getMember(name).ifPresent(mixinMember -> memberBuilder.target(mixinMember.getTarget()));
    }

    @Override
    public void modifyShape(
            AbstractShapeBuilder builder,
            Map memberBuilders,
            Function> unclaimedTraits,
            Function shapeMap
    ) {
        Shape mixinShape = shapeMap.apply(mixin);
        if (mixinShape == null) {
            throw new SourceException("Cannot apply mixin to " + builder.getId() + ": " + mixin + " not found",
                                      builder);
        }

        for (MemberShape member : mixinShape.members()) {
            ShapeId targetId = builder.getId().withMember(member.getMemberName());
            // Claim traits from the trait maps that were applied to synthesized shapes.
            Map introducedTraits = new LinkedHashMap<>(unclaimedTraits.apply(targetId));
            if (potentiallyIntroducedTraits != null && potentiallyIntroducedTraits.containsKey(targetId)) {
                introducedTraits.putAll(potentiallyIntroducedTraits.get(targetId));
            }
            String memberName = member.getMemberName();
            MemberShape introducedMember = null;
            Optional previouslyAdded = builder.getMember(memberName);
            if (previouslyAdded.isPresent()) {
                // The member was previously introduced, check if it inherits from another mixin to
                // avoid overwriting it.
                MemberShape previous = previouslyAdded.get();
                if (!previous.getMixins().isEmpty()) {
                    MemberShape.Builder previouslyAddedBuilder = previous.toBuilder();
                    introducedMember = previouslyAddedBuilder
                            .addMixin(member)
                            .build();
                    if (!previous.getTarget().equals(member.getTarget())) {
                        mixinMemberConflict(previouslyAddedBuilder, member);
                    }
                }
            }

            if (introducedMember == null) {
                if (memberBuilders.containsKey(member.getMemberName())) {
                    MemberShape.Builder original = memberBuilders.get(member.getMemberName());
                    introducedMember = original.addMixin(member).build();
                    if (!introducedMember.getTarget().equals(member.getTarget())) {
                        mixinMemberConflict(original, member);
                    }
                } else if (!introducedTraits.isEmpty()) {
                    // Build local member copies before adding mixins if traits
                    // were introduced to inherited mixin members.
                    introducedMember = MemberShape.builder()
                            .id(targetId)
                            .target(member.getTarget())
                            .source(member.getSourceLocation())
                            .addTraits(introducedTraits.values())
                            .addMixin(member)
                            .build();
                }
            }

            if (introducedMember != null) {
                builder.addMember(introducedMember);
            }
        }

        builder.addMixin(mixinShape);
    }

    private void mixinMemberConflict(MemberShape.Builder conflict, MemberShape other) {
        if (events == null) {
            events = new ArrayList<>();
        }
        events.add(ValidationEvent.builder()
                .severity(Severity.ERROR)
                .id(Validator.MODEL_ERROR)
                .shapeId(conflict.getId())
                .sourceLocation(conflict.getSourceLocation())
                .message("Member conflicts with an inherited mixin member: `" + other.getId() + "`")
                .build());
    }

    @Override
    public List getEvents() {
        return events == null ? Collections.emptyList() : events;
    }

    void putPotentiallyIntroducedTrait(ShapeId target, Trait trait) {
        if (potentiallyIntroducedTraits == null) {
            potentiallyIntroducedTraits = new HashMap<>();
        }

        Map shapeUnclaimedTraits = potentiallyIntroducedTraits.computeIfAbsent(target,
                id -> new LinkedHashMap<>());
        shapeUnclaimedTraits.put(trait.toShapeId(), trait);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy