/* * Copyright (c) MuleSoft, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific * language governing permissions and limitations under the License. */ package org.raml.parser.visitor; import static org.raml.parser.rule.ValidationMessage.NON_SCALAR_KEY_MESSAGE; import static org.raml.parser.visitor.TupleType.KEY; import static org.raml.parser.visitor.TupleType.VALUE; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.util.Stack; import org.raml.parser.builder.DefaultTupleBuilder; import org.raml.parser.builder.NodeBuilder; import org.raml.parser.builder.SequenceBuilder; import org.raml.parser.builder.TupleBuilder; import org.raml.parser.loader.CompositeResourceLoader; import org.raml.parser.loader.FileResourceLoader; import org.raml.parser.loader.ResourceLoader; import org.raml.parser.resolver.DefaultTupleHandler; import org.raml.parser.tagresolver.IncludeResolver; import org.raml.parser.tagresolver.TagResolver; import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.emitter.Emitter; import org.yaml.snakeyaml.error.YAMLException; 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 org.yaml.snakeyaml.nodes.SequenceNode; import org.yaml.snakeyaml.nodes.Tag; import org.yaml.snakeyaml.resolver.Resolver; import org.yaml.snakeyaml.serializer.Serializer; public class YamlDocumentBuilder<T> implements NodeHandler { private Class<T> documentClass; private T documentObject; private Stack<NodeBuilder<?>> builderContext = new Stack<NodeBuilder<?>>(); private Stack<Object> documentContext = new Stack<Object>(); private MappingNode rootNode; private ResourceLoader resourceLoader; private TagResolver[] tagResolvers; public YamlDocumentBuilder(Class<T> documentClass, ResourceLoader resourceLoader, TagResolver... tagResolvers) { this.documentClass = documentClass; this.resourceLoader = resourceLoader; this.tagResolvers = tagResolvers; } public T build(Reader content) { Yaml yamlParser = new Yaml(); NodeVisitor nodeVisitor = new NodeVisitor(this, resourceLoader, tagResolvers); rootNode = (MappingNode) yamlParser.compose(content); preBuildProcess(); nodeVisitor.visitDocument(rootNode); postBuildProcess(); return documentObject; } protected T getDocumentObject() { return documentObject; } protected Stack<NodeBuilder<?>> getBuilderContext() { return builderContext; } protected Stack<Object> getDocumentContext() { return documentContext; } protected ResourceLoader getResourceLoader() { return resourceLoader; } protected void preBuildProcess() { } protected void postBuildProcess() { } public T build(InputStream content) { return build(new InputStreamReader(content)); } public T build(String content) { return build(new StringReader(content)); } public T build(File path) { this.resourceLoader = new CompositeResourceLoader(new FileResourceLoader(path.getParent()), this.resourceLoader ); FileReader reader = null; try { reader = new FileReader(path); } catch (FileNotFoundException e) { e.printStackTrace(); } return build(reader); } public MappingNode getRootNode() { return rootNode; } @Override public void onMappingNodeStart(MappingNode mappingNode, TupleType tupleType) { if (tupleType == KEY) { throw new YAMLException(NON_SCALAR_KEY_MESSAGE + ": " + mappingNode.getStartMark()); } NodeBuilder<?> currentBuilder = builderContext.peek(); Object parentObject = documentContext.peek(); Object object = ((TupleBuilder<?, MappingNode>) currentBuilder) .buildValue(parentObject, mappingNode); documentContext.push(object); } @Override public void onMappingNodeEnd(MappingNode mappingNode, TupleType tupleType) { if (tupleType == KEY) { throw new YAMLException(NON_SCALAR_KEY_MESSAGE + ": " + mappingNode.getStartMark()); } documentContext.pop(); } @Override @SuppressWarnings("unchecked") public void onSequenceStart(SequenceNode node, TupleType tupleType) { if (tupleType == KEY) { throw new YAMLException(NON_SCALAR_KEY_MESSAGE + ": " + node.getStartMark()); } SequenceBuilder currentBuilder = (SequenceBuilder) builderContext .peek(); Object parentObject = documentContext.peek(); Object object = ((NodeBuilder) currentBuilder).buildValue(parentObject, node); builderContext.push(currentBuilder.getItemBuilder()); documentContext.push(object); } @Override public void onSequenceEnd(SequenceNode node, TupleType tupleType) { if (tupleType == KEY) { throw new YAMLException(NON_SCALAR_KEY_MESSAGE + ": " + node.getStartMark()); } documentContext.pop(); builderContext.pop(); } @Override @SuppressWarnings("unchecked") public void onScalar(ScalarNode node, TupleType tupleType) { NodeBuilder<?> currentBuilder = builderContext.peek(); Object parentObject = documentContext.peek(); if (tupleType == VALUE) { ((NodeBuilder<ScalarNode>) currentBuilder).buildValue(parentObject, node); } else { ((TupleBuilder<ScalarNode, ?>) currentBuilder).buildKey( parentObject, node); } } @Override public void onDocumentStart(MappingNode node) { try { documentObject = documentClass.newInstance(); documentContext.push(documentObject); builderContext.push(buildDocumentBuilder()); } catch (Exception e) { throw new RuntimeException(e); } } private TupleBuilder<?, ?> buildDocumentBuilder() { DefaultTupleBuilder<Node, MappingNode> documentBuilder = new DefaultTupleBuilder<Node, MappingNode>( new DefaultTupleHandler()); documentBuilder.addBuildersFor(documentClass); return documentBuilder; } @Override public void onDocumentEnd(MappingNode node) { if (documentObject != documentContext.pop()) { throw new IllegalStateException("more zombies?!"); } } @Override public void onTupleEnd(NodeTuple nodeTuple) { builderContext.pop(); } @Override public void onTupleStart(NodeTuple nodeTuple) { TupleBuilder<?, ?> currentBuilder = (TupleBuilder<?, ?>) builderContext .peek(); if (currentBuilder != null) { NodeBuilder<?> builder = currentBuilder .getBuilderForTuple(nodeTuple); builderContext.push(builder); } else { throw new IllegalStateException("Unexpected builderContext state"); } } @Override public void onSequenceElementStart(Node sequenceNode) { } @Override public void onSequenceElementEnd(Node sequenceNode) { } @Override public void onCustomTagStart(Tag tag, Node originalValueNode, NodeTuple nodeTuple) { } @Override public void onCustomTagEnd(Tag tag, Node originalValueNode, NodeTuple nodeTuple) { } @Override public void onCustomTagError(Tag tag, Node node, String message) { if (IncludeResolver.INCLUDE_TAG.equals(tag)) { throw new RuntimeException("resource not found: " + ((ScalarNode) node).getValue()); } } public static String dumpFromAst(Node rootNode) { Writer writer = new StringWriter(); dumpFromAst(rootNode, writer); return writer.toString(); } public static void dumpFromAst(Node rootNode, Writer output) { if (rootNode == null) { throw new IllegalArgumentException("rootNode is null"); } DumperOptions dumperOptions = new DumperOptions(); Tag rootTag = dumperOptions.getExplicitRoot(); Serializer serializer = new Serializer(new Emitter(output, dumperOptions), new Resolver(), dumperOptions, rootTag); try { serializer.open(); serializer.serialize(rootNode); serializer.close(); } catch (IOException e) { throw new YAMLException(e); } } }