package alien4cloud.tosca.parser.impl.base; import java.util.Map; import org.elasticsearch.common.collect.Maps; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import org.yaml.snakeyaml.nodes.*; import alien4cloud.tosca.parser.*; import alien4cloud.tosca.parser.impl.ErrorCode; import lombok.AllArgsConstructor; /** * Parse a yaml sequence into a {@link Map} * * @param <T> The type of the values of the map. */ @Component @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) @AllArgsConstructor(suppressConstructorProperties = true) public class SequenceToMapParser<T> implements INodeParser<Map<String, T>> { private INodeParser<T> valueParser; /** The tosca type of the map. */ private String toscaType; /** If the sequence element mapping node is also the */ private Boolean nodeIsValue; /** If we should generate keys for duplicated elements or trigger errors. */ private Boolean allowDuplicate; @Override public Map<String, T> parse(Node node, ParsingContextExecution context) { if (node instanceof SequenceNode) { SequenceNode sequenceNode = (SequenceNode) node; Map<String, T> sequenceMap = Maps.newLinkedHashMap(); for (Node elementNode : sequenceNode.getValue()) { if (elementNode instanceof MappingNode) { MappingNode mappingNode = (MappingNode) elementNode; String key = ((ScalarNode) mappingNode.getValue().get(0).getKeyNode()).getValue(); T value; if (nodeIsValue) { value = valueParser.parse(mappingNode, context); } else { value = valueParser.parse(mappingNode.getValue().get(0).getValueNode(), context); checkMappingNodeSingleProperty(mappingNode, context); } if (allowDuplicate) { sequenceMap.put(getUniqueKey(sequenceMap, key), value); } else if (sequenceMap.containsKey(key)) { ParsingError err = new ParsingError(ParsingErrorLevel.WARNING, ErrorCode.DUPLICATED_ELEMENT_DECLARATION, "Key in the sequence must be unique.", node.getStartMark(), "The value of this tuple should be a scalar", node.getEndMark(), key); context.getParsingErrors().add(err); } else { sequenceMap.put(key, value); } } else { ParserUtils.addTypeError(node, context.getParsingErrors(), toscaType); } } return sequenceMap; } ParserUtils.addTypeError(node, context.getParsingErrors(), toscaType); return null; } private String getUniqueKey(Map<String, T> sequenceMap, String key) { int increment = 0; String uniqueKey = key; while (sequenceMap.containsKey(uniqueKey)) { uniqueKey = key + "_" + increment; increment++; } return uniqueKey; } /** * Check that the mapping node has a single property (when value is node is false other properties are ignored). * * @param mappingNode The mapping node. * @param context The parsing context. */ public void checkMappingNodeSingleProperty(MappingNode mappingNode, ParsingContextExecution context) { if (mappingNode.getValue().size() > 1) { // some properties are ignored. for (int i = 1; i < mappingNode.getValue().size(); i++) { NodeTuple entry = mappingNode.getValue().get(i); ParsingError err = new ParsingError(ParsingErrorLevel.WARNING, ErrorCode.UNRECOGNIZED_PROPERTY, "Parsing a MappingNode as a Map", entry.getKeyNode().getStartMark(), "The value of this tuple should be a scalar", entry.getValueNode().getEndMark(), ((ScalarNode) entry.getKeyNode()).getValue()); context.getParsingErrors().add(err); } } } }