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

software.amazon.smithy.model.selector.AttributeSelector Maven / Gradle / Ivy

/*
 * Copyright 2020 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.selector;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
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.Trait;

/**
 * Matches shapes with a specific attribute or that matches an attribute comparator.
 */
final class AttributeSelector implements InternalSelector {

    private final List path;
    private final List expected;
    private final AttributeComparator comparator;
    private final boolean caseInsensitive;
    private final Function> optimizer;

    AttributeSelector(
            List path,
            List expected,
            AttributeComparator comparator,
            boolean caseInsensitive
    ) {
        this.path = path;
        this.caseInsensitive = caseInsensitive;
        this.comparator = comparator;

        // Create the valid values of the expected selector.
        if (expected == null) {
            this.expected = Collections.emptyList();
        } else {
            this.expected = new ArrayList<>(expected.size());
            for (String validValue : expected) {
                this.expected.add(AttributeValue.literal(validValue));
            }
        }

        // Optimization for loading shapes with a specific trait.
        // This optimization can only be applied when there's no comparator,
        // and it doesn't matter how deep into the trait the selector descends.
        if (comparator == null
                && path.size() >= 2
                && path.get(0).equals("trait")     // only match on traits
                && !path.get(1).startsWith("(")) { // don't match projections
            optimizer = model -> {
                // The trait name might be relative to the prelude, so ensure it's absolute.
                String absoluteShapeId = Trait.makeAbsoluteName(path.get(1));
                ShapeId trait = ShapeId.from(absoluteShapeId);
                return model.getShapesWithTrait(trait);
            };
        } else {
            optimizer = Model::toSet;
        }
    }

    static AttributeSelector existence(List path) {
        return new AttributeSelector(path, null, null, false);
    }

    @Override
    public Collection getStartingShapes(Model model) {
        return optimizer.apply(model);
    }

    @Override
    public Response push(Context context, Shape shape, Receiver next) {
        if (matchesAttribute(shape, context)) {
            return next.apply(context, shape);
        } else {
            return Response.CONTINUE;
        }
    }

    private boolean matchesAttribute(Shape shape, Context stack) {
        AttributeValue lhs = AttributeValue.shape(shape, stack.getVars()).getPath(path);

        if (comparator == null) {
            return lhs.isPresent();
        }

        for (AttributeValue rhs : expected) {
            if (comparator.compare(lhs, rhs, caseInsensitive)) {
                return true;
            }
        }

        return false;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy