package alien4cloud.tosca.parser.impl.base;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.NodeTuple;
import org.yaml.snakeyaml.nodes.ScalarNode;
import com.google.common.collect.Maps;
import alien4cloud.tosca.parser.*;
import alien4cloud.tosca.parser.impl.ErrorCode;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Getter
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class TypeNodeParser<T> extends AbstractTypeNodeParser implements INodeParser<T> {
private final Class<T> type;
private final Map<String, MappingTarget> yamlToObjectMapping;
private final Map<Integer, MappingTarget> yamlOrderedToObjectMapping;
public TypeNodeParser(Class<T> type, String toscaType) {
super(toscaType);
this.type = type;
yamlToObjectMapping = Maps.newLinkedHashMap();
yamlOrderedToObjectMapping = Maps.newLinkedHashMap();
}
@Override
public T parse(Node node, ParsingContextExecution context) {
return parse(node, context, null);
}
/**
* Do the node parsing using the given instance rather than creating a new one.
*
* @param node The node to parse.
* @param context The parsing context.
* @param instance The instance in which to parse the node (or null to create a new instance).
* @return The given instance or a new one if none was provided.
*/
public T parse(Node node, ParsingContextExecution context, T instance) {
if (node instanceof MappingNode) {
return doParse((MappingNode) node, context, instance);
} else if (node instanceof ScalarNode) {
String scalarValue = ((ScalarNode) node).getValue();
if (scalarValue == null || scalarValue.trim().isEmpty()) {
// node is just not defined, return null.
return null;
} else {
// try to use instance default constructor based on string if any
Constructor<T> constructor;
try {
constructor = type.getConstructor(String.class);
} catch (NoSuchMethodException e) {
// scalar value is not allowed to parse the node.
ParserUtils.addTypeError(node, context.getParsingErrors(), getToscaType());
return null;
}
try {
return constructor.newInstance(scalarValue);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
log.error("Error while parsing Yaml, scalar value is not valid.", e);
context.getParsingErrors().add(new ParsingError(ErrorCode.SYNTAX_ERROR, "Invalid scalar value.", node.getStartMark(),
"Tosca type cannot be expressed with the given scalar value.", node.getEndMark(), getToscaType()));
return null;
}
}
}
ParserUtils.addTypeError(node, context.getParsingErrors(), getToscaType());
return null;
}
private T doParse(MappingNode node, ParsingContextExecution context, T instance) {
try {
if (instance == null) {
instance = type.newInstance();
}
BeanWrapper instanceWrapper = new BeanWrapperImpl(instance);
if (context.getRoot() == null) {
context.setRoot(instanceWrapper);
}
for (int i = 0; i < node.getValue().size(); i++) {
// lets proceed with node mapping.
mapTuple(instanceWrapper, node.getValue().get(i), i, context);
}
return instance;
} catch (InstantiationException | IllegalAccessException e) {
throw new ParsingTechnicalException("Unexpected error while parsing tosca definition.", e);
}
}
private void mapTuple(BeanWrapper instance, NodeTuple nodeTuple, int nodeTupleIndex, ParsingContextExecution context) {
String key = ParserUtils.getScalar(nodeTuple.getKeyNode(), context);
if (key == null) {
return;
}
// get the field that matches the tuple based on property index in the object definition.
MappingTarget target = yamlOrderedToObjectMapping.get(nodeTupleIndex);
// get the field that matches the given key.
if (target == null) {
target = yamlToObjectMapping.get(key);
}
if (target == null) {
context.getParsingErrors().add(new ParsingError(ParsingErrorLevel.WARNING, ErrorCode.UNRECOGNIZED_PROPERTY, "Ignored field during import",
nodeTuple.getKeyNode().getStartMark(), "tosca key is not recognized", nodeTuple.getValueNode().getEndMark(), key));
} else {
// set the value to the required path
BeanWrapper targetBean = target.isRootPath() ? context.getRoot() : instance;
parseAndSetValue(targetBean, key, nodeTuple.getValueNode(), context, target);
}
}
}