| /** |
| * <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(); |
| } |
| } |