content: package alien4cloud.tosca.parser;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.nio.file.Files;
  import java.nio.file.Path;
  
  import org.yaml.snakeyaml.composer.Composer;
  import org.yaml.snakeyaml.error.Mark;
  import org.yaml.snakeyaml.error.MarkedYAMLException;
  import org.yaml.snakeyaml.nodes.Node;
  import org.yaml.snakeyaml.parser.ParserImpl;
  import org.yaml.snakeyaml.reader.StreamReader;
  import org.yaml.snakeyaml.reader.UnicodeReader;
  import org.yaml.snakeyaml.resolver.Resolver;
  
  import alien4cloud.tosca.parser.impl.ErrorCode;
  import alien4cloud.tosca.parser.impl.base.TypeNodeParser;
  import lombok.extern.slf4j.Slf4j;
  
  /**
   * Parser to process Yaml files.
   * 
   * @author luc boutier
   *
   * @param <T> The object instance in which to parse the object.
   */
  @Slf4j
  public abstract class YamlParser<T> {
  
      /**
       * Parse a yaml file to create a new T instance.
       * 
       * @param yamlPath Path of the yaml file.
       * @return A parsing result that contains the parsing errors as well as the created instance.
       * @throws ParsingException In case there is a blocking issue while parsing the definition.
       */
      public ParsingResult<T> parseFile(Path yamlPath) throws ParsingException {
          return parseFile(yamlPath, null);
      }
  
      /**
       * Parse a yaml file into the given T instance.
       * 
       * @param yamlPath Path of the yaml file.
       * @param instance The instance to parse.
       * @return A parsing result that contains the parsing errors as well as the created instance.
       * @throws ParsingException In case there is a blocking issue while parsing the definition.
       */
      public ParsingResult<T> parseFile(Path yamlPath, T instance) throws ParsingException {
          InputStream inputStream = null;
  
          try {
              inputStream = Files.newInputStream(yamlPath);
              return parseFile(yamlPath.toString(), yamlPath.getFileName().toString(), inputStream, instance);
          } catch (IOException e) {
              throw new ParsingException(yamlPath.getFileName().toString(),
                      new ParsingError(ErrorCode.MISSING_FILE, "File not found in archive.", null, null, null, yamlPath.toString()));
          } finally {
              if (inputStream != null) {
                  try {
                      inputStream.close();
                  } catch (IOException e) {
                      log.error("Failed to close input stream after parsing.", e);
                  }
              }
          }
      }
  
      /**
       * Parse a yaml file into the given T instance.
       *
       * @param yamlStream Input stream that contains the yaml.
       * @param instance The instance to parse.
       * @return A parsing result that contains the parsing errors as well as the created instance.
       * @throws ParsingException In case there is a blocking issue while parsing the definition.
       */
      public ParsingResult<T> parseFile(String filePath, String fileName, InputStream yamlStream, T instance) throws ParsingException {
          StreamReader sreader = new StreamReader(new UnicodeReader(yamlStream));
          Composer composer = new Composer(new ParserImpl(sreader), new Resolver());
          Node rootNode = null;
          try {
              rootNode = composer.getSingleNode();
              if (rootNode == null) {
                  throw new ParsingException(fileName, new ParsingError(ErrorCode.SYNTAX_ERROR, "Empty file.", new Mark("root", 0, 0, 0, null, 0),
                          "No yaml content found in file.", new Mark("root", 0, 0, 0, null, 0), filePath));
              }
          } catch (MarkedYAMLException exception) {
              throw new ParsingException(fileName, new ParsingError(ErrorCode.INVALID_YAML, exception));
          }
  
          try {
              return doParsing(fileName, rootNode, instance);
          } catch (ParsingException e) {
              e.setFileName(fileName);
              throw e;
          }
      }
  
      private ParsingResult<T> doParsing(String fileName, Node rootNode, T instance) throws ParsingException {
          boolean createContext = !ParsingContextExecution.exist();
          try {
              if (createContext) { // parser can reuse an existing context if provided.
                  ParsingContextExecution.init();
              }
              ParsingContextExecution.setFileName(fileName);
  
              ParsingContextExecution fake = new ParsingContextExecution();
  
              INodeParser<T> nodeParser = getParser(rootNode, fake);
  
              T parsedObject;
              if (nodeParser instanceof TypeNodeParser) {
                  parsedObject = ((TypeNodeParser<T>) nodeParser).parse(rootNode, fake, instance);
              } else {
                  // let's start the parsing using the version related parsers
                  parsedObject = nodeParser.parse(rootNode, fake);
              }
  
              postParsing(parsedObject);
  
              return new ParsingResult<T>(parsedObject, ParsingContextExecution.getParsingContext());
  
          } finally {
              if (createContext) {
                  ParsingContextExecution.destroy();
              }
          }
      }
  
      /**
       * Allow to find the parser to use based on the root node.
       * 
       * @param rootNode The root node from which to get a parser implementation.
       * @param context The parsing context.
       * @return The parser to use.
       */
      protected abstract INodeParser<T> getParser(Node rootNode, ParsingContextExecution context) throws ParsingException;
  
      /**
       * Perform any required post-processing validations.
       *
       * @param result The object resulting of the parsing.
       */
      protected void postParsing(T result) {
          // no post parsing by default.
      };
  }
  