content: 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) {
                  // 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);
          }
      }
  
  }