/**
* Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Puppet Labs
*/
package com.puppetlabs.xtext.serializer;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import com.puppetlabs.xtext.dommodel.DomModelUtils;
import com.puppetlabs.xtext.dommodel.IDomNode;
import com.puppetlabs.xtext.dommodel.formatter.IDomModelFormatter;
import com.puppetlabs.xtext.dommodel.formatter.comments.ICommentConfiguration;
import com.puppetlabs.xtext.dommodel.formatter.context.IFormattingContextFactory;
import com.puppetlabs.xtext.dommodel.formatter.context.IFormattingContextFactory.FormattingOption;
import com.puppetlabs.xtext.resource.ResourceAccessScope;
import com.puppetlabs.xtext.serializer.acceptor.DomModelSequenceAdapter;
import com.puppetlabs.xtext.serializer.acceptor.IHiddenTokenSequencer2;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.xtext.formatting.ILineSeparatorInformation;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.parsetree.reconstr.ICommentAssociater;
import org.eclipse.xtext.parsetree.reconstr.IHiddenTokenHelper;
import org.eclipse.xtext.parsetree.reconstr.ITokenStream;
import org.eclipse.xtext.resource.SaveOptions;
import org.eclipse.xtext.serializer.acceptor.ISemanticSequenceAcceptor;
import org.eclipse.xtext.serializer.acceptor.ISequenceAcceptor;
import org.eclipse.xtext.serializer.acceptor.ISyntacticSequenceAcceptor;
import org.eclipse.xtext.serializer.acceptor.TokenStreamSequenceAdapter;
import org.eclipse.xtext.serializer.diagnostic.ISerializationDiagnostic;
import org.eclipse.xtext.serializer.impl.Serializer;
import org.eclipse.xtext.serializer.sequencer.IHiddenTokenSequencer;
import org.eclipse.xtext.serializer.sequencer.ISemanticSequencer;
import org.eclipse.xtext.serializer.sequencer.ISyntacticSequencer;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.ReplaceRegion;
import org.eclipse.xtext.util.TextRegion;
import org.eclipse.xtext.validation.IConcreteSyntaxValidator;
import com.google.inject.Inject;
/**
* Extends Serializer and modifies the API (ITokenStream based formatting/output not supported).
* Use Serializer methods that does not take ITokenStream as an argument.
* TODO: This is a temporary solution.
*
*/
public class DomBasedSerializer extends Serializer {
protected static class StringBuilderWriter extends Writer {
private final StringBuilder builder;
public StringBuilderWriter() {
builder = new StringBuilder();
}
public StringBuilderWriter(int initialCapacity) {
builder = new StringBuilder(initialCapacity);
}
public StringBuilderWriter(StringBuilder appendToBuilder) {
builder = appendToBuilder;
}
@Override
public Writer append(char value) {
builder.append(value);
return this;
}
@Override
public Writer append(CharSequence value) {
builder.append(value);
return this;
}
@Override
public Writer append(CharSequence value, int start, int end) {
builder.append(value, start, end);
return this;
}
@Override
public void close() throws IOException {
// does nothing
}
@Override
public void flush() throws IOException {
// does nothing
}
StringBuilder getBuilder() {
return builder;
}
@Override
public String toString() {
return builder.toString();
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException {
builder.append(cbuf, off, len);
}
@Override
public void write(String value) {
builder.append(value);
}
}
@Inject
IDomModelFormatter domFormatter;
@Inject
IFormattingContextFactory formattingContextFactory;
@Inject
IHiddenTokenHelper hiddenTokenHelper;
@Inject
ILineSeparatorInformation lineSeparatorInformation;
@Inject
ICommentConfiguration<?> commentConfiguration;
@Inject
private ResourceAccessScope resourceScope;
@Inject
ICommentAssociater commentAssociater;
@Inject
CommentAssociator ppCommentAssociator;
private FormattingOption formatting(SaveOptions options) {
return options.isFormatting()
? FormattingOption.Format
: FormattingOption.PreserveWhitespace;
}
/**
* NOTE: This overridden method is required to initialize the DomModelSequences.
* The base implementation checks if the tokens parameter is a specific adapter, and
* then initializes it. This method does the same but for DomModelSequenceAdapter.
*
*/
@Override
protected void serialize(EObject semanticObject, EObject context, ISequenceAcceptor tokens,
ISerializationDiagnostic.Acceptor errors) {
serialize(semanticObject, context, tokens, errors, null);
}
protected void serialize(EObject semanticObject, EObject context, ISequenceAcceptor tokens,
ISerializationDiagnostic.Acceptor errors, ICommentReconcilement commentReconciliator) {
ISemanticSequencer semantic = semanticSequencerProvider.get();
ISyntacticSequencer syntactic = syntacticSequencerProvider.get();
IHiddenTokenSequencer hidden = hiddenTokenSequencerProvider.get();
semantic.init((ISemanticSequenceAcceptor) syntactic, errors);
syntactic.init(context, semanticObject, (ISyntacticSequenceAcceptor) hidden, errors);
if(hidden instanceof IHiddenTokenSequencer2)
((IHiddenTokenSequencer2) hidden).init(context, semanticObject, tokens, errors, commentReconciliator);
else
hidden.init(context, semanticObject, tokens, errors);
// NOTE: This is not so nice
if(tokens instanceof TokenStreamSequenceAdapter)
((TokenStreamSequenceAdapter) tokens).init(context);
else if(tokens instanceof DomModelSequenceAdapter)
((DomModelSequenceAdapter) tokens).init(context, semanticObject);
semantic.createSequence(context, semanticObject);
}
/**
* @throws UnsupportedOperationException
*/
@Override
protected void serialize(EObject obj, ITokenStream tokenStream, SaveOptions options) throws IOException {
throw new UnsupportedOperationException("Use new API");
}
@Override
public String serialize(EObject obj, SaveOptions options) {
return serialize(obj, options, null);
}
public String serialize(EObject obj, SaveOptions options, ITextRegion regionToFormat) {
// TODO: Faster to use a non synchronizing implementation such as StringBuilderWriter from apache commons io
Writer writer = new StringBuilderWriter();
try {
serialize(obj, writer, options, regionToFormat);
}
catch(IOException e) {
throw new RuntimeException(e);
}
return writer.toString();
}
@Override
public void serialize(EObject obj, Writer writer, SaveOptions options) throws IOException {
serialize(obj, writer, options, null /* all text */);
}
public void serialize(EObject obj, Writer writer, SaveOptions options, ITextRegion regionToFormat)
throws IOException {
// FROM SUPER VERSION (without the ITextRegion)
// use the CSV as long as there are cases where is provides better messages than the serializer itself.
if(options.isValidating()) {
List<Diagnostic> diagnostics = new ArrayList<Diagnostic>();
validator.validateRecursive(
obj, new IConcreteSyntaxValidator.DiagnosticListAcceptor(diagnostics), new HashMap<Object, Object>());
if(!diagnostics.isEmpty())
throw new IConcreteSyntaxValidator.InvalidConcreteSyntaxException(
"These errors need to be fixed before the model can be serialized.", diagnostics);
}
// END FROM SUPER VERSION
// Uses DomModelSequencer and new formatter interface
ISerializationDiagnostic.Acceptor errors = ISerializationDiagnostic.EXCEPTION_THROWING_ACCEPTOR;
DomModelSequenceAdapter acceptor = new DomModelSequenceAdapter(
hiddenTokenHelper, commentConfiguration, lineSeparatorInformation, errors);
EObject context = getContext(obj);
serialize(obj, context, acceptor, errors);
ReplaceRegion r = domFormatter.format(
acceptor.getDomModel(), regionToFormat, formattingContextFactory.create(obj, formatting(options)), errors);
writer.append(r.getText());
}
@Override
public ReplaceRegion serializeReplacement(EObject obj, SaveOptions options) {
ICompositeNode node = NodeModelUtils.findActualNodeFor(obj);
if(node == null) {
throw new IllegalStateException("Cannot replace an obj that has no associated node");
}
ICommentReconcilement commentReconciliator = ppCommentAssociator.associateComments(node);
boolean alreadyEnteredResourceScope = false;
try {
if(resourceScope.get().getResourceURI() == null) {
alreadyEnteredResourceScope = true;
resourceScope.enter(obj.eResource());
}
// Serialize everything to DOM
IDomNode root = serializeToDom(obj.eResource().getContents().get(0), true, commentReconciliator);
// Find the node for the modified object, we need to format the region it occupies.
IDomNode replacementNode = DomModelUtils.findNodeForSemanticObject(root, obj);
TextRegion regionToFormat = new TextRegion(replacementNode.getOffset(), replacementNode.getLength());
// Override temporarily to have formatting turned on
// GAH - this is just ridiculous internal DSL junk to set a single boolean
options = SaveOptions.newBuilder().format().getOptions();
ReplaceRegion r = domFormatter.format(
root, regionToFormat,
formattingContextFactory.create(obj.eResource().getContents().get(0), formatting(options)));
// String text = serialize(obj.eContainer(), options, new TextRegion(node.getOffset(), node.getLength()));
return new ReplaceRegion(node.getTotalOffset(), node.getTotalLength(), r.getText());
}
finally {
if(alreadyEnteredResourceScope)
resourceScope.exit();
}
}
/**
* Serialize and return the resulting DOM. This is the same as calling {@link #serializeToDom(EObject, boolean, ICommentReconcilement)} with a
* null ICommentReconciliator.
*
* @param obj
* @param preserveWhitespace
* @return
*/
public IDomNode serializeToDom(EObject obj, boolean preserveWhitespace) {
return serializeToDom(obj, preserveWhitespace, null);
}
/**
* Serialize and return the resulting DOM.
*
* @param obj
* @param preserveWhitespace
* @return
*/
public IDomNode serializeToDom(EObject obj, boolean preserveWhitespace, ICommentReconcilement commentReconciliator) {
// Uses DomModelSequencer and new formatter interface
ISerializationDiagnostic.Acceptor errors = ISerializationDiagnostic.EXCEPTION_THROWING_ACCEPTOR;
DomModelSequenceAdapter acceptor = new DomModelSequenceAdapter(
hiddenTokenHelper, commentConfiguration, lineSeparatorInformation, errors);
EObject context = getContext(obj);
serialize(obj, context, acceptor, errors, commentReconciliator);
return acceptor.getDomModel();
}
}