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.
};
}