Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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 static java.lang.String.format;
import static software.amazon.smithy.model.validation.Severity.ERROR;
import static software.amazon.smithy.model.validation.Validator.MODEL_ERROR;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import software.amazon.smithy.model.SourceException;
import software.amazon.smithy.model.SourceLocation;
import software.amazon.smithy.model.node.Node;
import software.amazon.smithy.model.shapes.AbstractShapeBuilder;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.traits.DynamicTrait;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.model.traits.TraitFactory;
import software.amazon.smithy.model.validation.Severity;
import software.amazon.smithy.model.validation.ValidationEvent;
import software.amazon.smithy.model.validation.Validator;
final class LoaderTraitMap {
private static final Logger LOGGER = Logger.getLogger(LoaderTraitMap.class.getName());
private static final String UNRESOLVED_TRAIT_SUFFIX = ".UnresolvedTrait";
private final TraitFactory traitFactory;
private final Map> traits = new HashMap<>();
private final List events;
private final boolean allowUnknownTraits;
private final Map> unclaimed = new HashMap<>();
private final Set claimed = new HashSet<>();
LoaderTraitMap(TraitFactory traitFactory, List events, boolean allowUnknownTraits) {
this.traitFactory = traitFactory;
this.events = events;
this.allowUnknownTraits = allowUnknownTraits;
}
void applyTraitsToNonMixinsInShapeMap(LoaderShapeMap shapeMap) {
for (Map.Entry> entry : traits.entrySet()) {
ShapeId target = entry.getKey();
ShapeId root = target.withoutMember();
// Check if the actual shape (or member) is found, but grab the member-less shape from the shape map.
// Only pending shapes are checked here. If a trait was added to a built shape, then LoadOperationProcessor
// will have already converted the built shape into a pending shape.
boolean found = shapeMap.isShapePending(target);
Iterable rootShapes = found
? shapeMap.get(root)
: Collections::emptyIterator;
for (Map.Entry traitEntry : entry.getValue().entrySet()) {
ShapeId traitId = traitEntry.getKey();
Node traitNode = traitEntry.getValue();
Trait created = createTrait(target, traitId, traitNode);
validateTraitIsKnown(target, traitId, created, traitNode.getSourceLocation(), shapeMap);
if (target.hasMember()) {
// Apply the trait to a member by reaching into the members of each LoadOperation.DefineShape.
String memberName = target.getMember().get();
boolean foundMember = false;
for (LoadOperation.DefineShape shape : rootShapes) {
if (shape.hasMember(memberName)) {
foundMember = true;
applyTraitsToShape(shape.memberBuilders().get(memberName), created);
} else {
// If we didn't have the member and the member is from a mixin,
// we need to update ApplyMixin shape modifiers to apply the trait
// in case we have the target's container already in the shapeMap.
for (ShapeModifier modifier : shape.modifiers()) {
if (modifier instanceof ApplyMixin) {
((ApplyMixin) modifier).putPotentiallyIntroducedTrait(target, created);
}
}
}
}
// If the member wasn't found, then it might be a mixin member that is synthesized later.
if (!foundMember) {
unclaimed.computeIfAbsent(target.withMember(memberName), id -> new LinkedHashMap<>())
.put(traitId, created);
}
} else if (found) {
// Apply the trait to each shape contained in the shape map for the given target.
for (LoadOperation.DefineShape shape : rootShapes) {
applyTraitsToShape(shape.builder(), created);
}
} else {
unclaimed.computeIfAbsent(target, id -> new LinkedHashMap<>()).put(traitId, created);
}
}
}
}
private Trait createTrait(ShapeId target, ShapeId traitId, Node traitValue) {
try {
return traitFactory.createTrait(traitId, target, traitValue)
.orElseGet(() -> new DynamicTrait(traitId, traitValue));
} catch (SourceException e) {
String message = format("Error creating trait `%s`: ", Trait.getIdiomaticTraitName(traitId));
events.add(ValidationEvent.fromSourceException(e, message, target));
return null;
} catch (RuntimeException e) {
events.add(ValidationEvent.builder()
.id(MODEL_ERROR)
.severity(ERROR)
.shapeId(target)
.sourceLocation(traitValue)
.message(format("Error creating trait `%s`: %s",
Trait.getIdiomaticTraitName(traitId),
e.getMessage()))
.build());
return null;
}
}
private void validateTraitIsKnown(ShapeId target, ShapeId traitId, Trait trait,
SourceLocation sourceLocation, LoaderShapeMap shapeMap) {
if (!shapeMap.isRootShapeDefined(traitId) && (trait == null || !trait.isSynthetic())) {
Severity severity = allowUnknownTraits ? Severity.WARNING : Severity.ERROR;
events.add(ValidationEvent.builder()
.id(Validator.MODEL_ERROR + UNRESOLVED_TRAIT_SUFFIX)
.severity(severity)
.sourceLocation(sourceLocation)
.shapeId(target)
.message(String.format("Unable to resolve trait `%s`. If this is a custom trait, then it must be "
+ "defined before it can be used in a model.", traitId))
.build());
}
}
private void applyTraitsToShape(AbstractShapeBuilder, ?> shape, Trait trait) {
if (trait != null) {
shape.addTrait(trait);
}
}
// Traits can be applied to synthesized members inherited from mixins. Applying these traits is deferred until
// the point in which mixin members are synthesized into shapes.
Map claimTraitsForShape(ShapeId id) {
if (!unclaimed.containsKey(id)) {
return Collections.emptyMap();
}
claimed.add(id);
return unclaimed.get(id);
}
// Emit events if any traits were applied to shapes that weren't found in the model.
void emitUnclaimedTraits() {
for (Map.Entry> entry : unclaimed.entrySet()) {
if (claimed.contains(entry.getKey())) {
continue;
}
for (Map.Entry traitEntry : entry.getValue().entrySet()) {
events.add(ValidationEvent.builder()
.id(Validator.MODEL_ERROR)
.severity(Severity.ERROR)
.sourceLocation(traitEntry.getValue())
.message(String.format("Trait `%s` applied to unknown shape `%s`",
Trait.getIdiomaticTraitName(traitEntry.getKey()), entry.getKey()))
.build());
}
}
}
void add(LoadOperation.ApplyTrait operation) {
if (validateTraitVersion(operation)) {
if (isAppliedToPreludeOutsidePrelude(operation)) {
String message = String.format(
"Cannot apply `%s` to an immutable prelude shape defined in `smithy.api`.",
operation.trait);
events.add(ValidationEvent.builder()
.severity(Severity.ERROR)
.id(Validator.MODEL_ERROR)
.sourceLocation(operation)
.shapeId(operation.target)
.message(message)
.build());
} else {
Map current = traits.computeIfAbsent(operation.target, id -> new LinkedHashMap<>());
Node previous = current.get(operation.trait);
current.put(operation.trait, mergeTraits(operation.target, operation.trait, previous, operation.value));
}
}
}
private boolean validateTraitVersion(LoadOperation.ApplyTrait operation) {
ValidationEvent event = operation.version.validateVersionedTrait(
operation.target, operation.trait, operation.value);
if (event != null) {
events.add(event);
}
return true;
}
private boolean isAppliedToPreludeOutsidePrelude(LoadOperation.ApplyTrait operation) {
return !operation.namespace.equals(Prelude.NAMESPACE)
&& operation.target.getNamespace().equals(Prelude.NAMESPACE);
}
private Node mergeTraits(ShapeId target, ShapeId traitId, Node previous, Node updated) {
if (previous == null) {
return updated;
}
if (LoaderUtils.isSameLocation(previous, updated) && previous.equals(updated)) {
// The assumption here is that if the trait value is exactly the
// same and from the same location, then the same model file was
// included more than once in a way that side-steps file and URL
// de-duplication. For example, this can occur when a Model is assembled
// through a ModelAssembler using model discovery, then the Model is
// added to a subsequent ModelAssembler, and then model discovery is
// performed again using the same classpath.
LOGGER.finest(() -> String.format("Ignoring duplicate %s trait value on %s at same exact location",
traitId, target));
return previous;
}
if (previous.isArrayNode() && updated.isArrayNode()) {
// You can merge trait arrays.
return previous.expectArrayNode().merge(updated.expectArrayNode());
} else if (previous.equals(updated)) {
LOGGER.fine(() -> String.format("Ignoring duplicate %s trait value on %s", traitId, target));
return previous;
} else {
events.add(ValidationEvent.builder()
.id(Validator.MODEL_ERROR)
.severity(Severity.ERROR)
.sourceLocation(updated)
.shapeId(target)
.message(String.format("Conflicting `%s` trait found on shape `%s`. The previous trait was "
+ "defined at `%s`, and a conflicting trait was defined at `%s`.",
traitId, target, previous.getSourceLocation(), updated.getSourceLocation()))
.build());
return previous;
}
}
}