blob: a411a1ebb997f25fed8e9154637b7357b574dd7c [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2018 Willink Transformations and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* E.D.Willink - Initial API and implementation
*
* </copyright>
*/
package org.eclipse.qvtd.text.utilities;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.xmi.XMLHelper;
import org.eclipse.emf.ecore.xmi.impl.XMILoadImpl;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.qvtd.text.StringNode;
import org.eclipse.qvtd.text.TextModelFactory;
/**
* TextModelResourceImpl saves a model that conforms to http://www.eclipse.org/qvt/2018/Text as a simple String.
*
* This allows the Text technology space to be manipulated using Model techology space tooling.
*
* TextModelResourceImpl loads from an arbitrary source String, creating a model that conform to
* http://www.eclipse.org/qvt/2018/Text inferring indentation and hierarchy from leading whitespace and
* new lines. No inference of separators is made.
*/
public class TextModelResourceImpl extends ResourceImpl
{
public static class TextModelLoadImpl extends XMILoadImpl
{
public TextModelLoadImpl(XMLHelper helper) {
super(helper);
}
}
public static class TextModelSave
{ // FIXME Bug 534614 line-wrap to respect LINE_WIDTH save option
protected final @NonNull StringBuilder s = new StringBuilder();
protected final @NonNull Stack<@NonNull String> indentStack = new Stack<>();
private boolean indentPending = true; // AT beginning of first line, a first indent is pending
public TextModelSave() {
indentStack.push("");
}
protected void append(@NonNull String text) {
int iSize = text.length();
for (int i = 0; i < iSize; i++) {
char c = text.charAt(i);
if (!indentPending) {
s.append(c);
if (c == '\n') {
indentPending = true;
}
}
else if (c != '\n') {
s.append(indentStack.peek());
indentPending = false;
s.append(c);
}
else {
s.append(c);
indentPending = true;
}
}
}
@Override
public @NonNull String toString() {
return s.toString();
}
public void traverse(List<? extends EObject> contents) {
for (EObject eObject : contents) {
if (eObject instanceof StringNode) {
traverseNode((StringNode)eObject);
}
}
}
protected void traverseNode(@NonNull StringNode node) {
String childIndent = indentStack.peek() + node.getIndent();
indentStack.push(childIndent);
append(node.getText());
List<StringNode> children = node.getChildren();
if (children.size() > 0) {
append(node.getPrefix());
String separator = null;
for (StringNode child : children) {
if (separator != null) {
append(separator);
}
else {
separator = node.getSeparator();
}
traverseNode(child);
}
append(node.getSuffix());
}
append(node.getEndText());
indentStack.pop();
}
}
public TextModelResourceImpl(URI uri) {
super(uri);
}
@Override
public void doLoad(InputStream inputStream, Map<?, ?> options) throws IOException {
Reader reader = new InputStreamReader(inputStream);
doLoad(reader, options);
}
public void doLoad(Reader reader, Map<?, ?> options) throws IOException {
BufferedReader lineReader = new BufferedReader(reader);
Stack<@NonNull StringNode> treeNodes = new Stack<>();
Stack<@NonNull String> indentations = new Stack<>();
StringNode rootNode = TextModelFactory.eINSTANCE.createStringNode();
rootNode.setSeparator("\n");
rootNode.setSuffix("\n");
getContents().add(rootNode);
treeNodes.push(rootNode);
indentations.push(rootNode.getIndent());
for (String line; (line = lineReader.readLine()) != null; ) {
String indentation = indentations.peek();
while (!line.startsWith(indentation)) {
treeNodes.pop();
indentations.pop();
indentation = indentations.peek();
}
// treeNodes.peek() has shorter/same indentation
int outerLineStart = indentation.length();
int innerLineStart = outerLineStart;
while (innerLineStart < line.length()) {
char c = line.charAt(innerLineStart);
if (!Character.isWhitespace(c)) {
break;
}
innerLineStart++;
}
StringNode node = TextModelFactory.eINSTANCE.createStringNode();
node.setText(line.substring(innerLineStart));
if ((outerLineStart == innerLineStart) && (treeNodes.size() > 1)){ // Same indentation - add a sibling
StringNode sibling = treeNodes.pop();
node.setIndent(sibling.getIndent());
}
else { // Extra indentation - add a child
String extraIndentation = line.substring(outerLineStart, innerLineStart);
node.setIndent(extraIndentation);
indentations.push(indentation + extraIndentation);
}
StringNode parent = treeNodes.peek();
if (parent != rootNode) {
parent.setSeparator("\n");
parent.setPrefix("\n");
}
parent.getChildren().add(node);
treeNodes.push(node);
}
}
@Override
protected void doSave(OutputStream outputStream, Map<?, ?> options) throws IOException {
OutputStreamWriter writer = new OutputStreamWriter(outputStream);
doSave(writer, options);
}
protected void doSave(Writer writer, Map<?, ?> options) throws IOException {
TextModelSave saveHelper = new TextModelSave();
saveHelper.traverse(getContents());
writer.append(saveHelper.toString());
writer.flush();
writer.close();
}
}