/**
* Copyright (c) 2012 Cloudsmith 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:
* Cloudsmith
*
*/
package org.cloudsmith.xtext.textflow;
import java.io.IOException;
import java.util.List;
import org.cloudsmith.xtext.dommodel.formatter.context.IFormattingContext;
import com.google.inject.Inject;
/**
* An abstract implementation of an ITextFlow.
*
*/
public abstract class AbstractTextFlow implements ITextFlow {
protected int indent;
protected final String lineSeparator;
protected final int indentSize;
protected final char indentChar;
protected int wrapIndentSize;
protected int preferredMaxWidth;
/**
* Copy constructor
*
* @param original
*/
protected AbstractTextFlow(AbstractTextFlow original) {
this.indent = original.indent;
this.lineSeparator = original.lineSeparator;
this.indentChar = original.indentChar;
this.indentSize = original.indentSize;
this.wrapIndentSize = original.wrapIndentSize;
this.preferredMaxWidth = original.preferredMaxWidth;
}
@Inject
protected AbstractTextFlow(IFormattingContext formattingContext) {
this.preferredMaxWidth = formattingContext.getPreferredMaxWidth();
this.wrapIndentSize = formattingContext.getWrapIndentSize();
lineSeparator = formattingContext.getLineSeparatorInformation().getLineSeparator();
String indentationString = formattingContext.getIndentationInformation().getIndentString();
indentSize = indentationString.length();
indentChar = indentSize == 0
? ' '
: indentationString.charAt(0);
if(indentSize > 0) {
for(int i = 0; i < indentSize; i++)
if(indentationString.charAt(i) != indentChar)
throw new IllegalStateException("Indentation string must consist of the same character");
}
}
@Override
public Appendable append(char c) throws IOException {
return appendText(String.valueOf(c));
}
@Override
public Appendable append(CharSequence csq) throws IOException {
return appendText(csq);
}
@Override
public Appendable append(CharSequence csq, int start, int end) throws IOException {
return appendText(csq.subSequence(start, end));
}
@Override
public ITextFlow appendBreak() {
return appendBreaks(1);
}
@Override
public ITextFlow appendBreaks(int count) {
appendBreaks(count, false);
return this;
}
@Override
public abstract ITextFlow appendBreaks(int count, boolean verbatim);
@Override
public ITextFlow appendSpace() {
return appendSpaces(1);
}
@Override
public abstract ITextFlow appendSpaces(int count);
/**
* Breaks lines into separate lines and calls a sequence of {@link #doText(String)} and {@link #oneBreak(int)}.
* A derived class should not override this method, and instead override {@link #doText(String)} to perform the emit of
* the actual text, or {@link #processTextSequence(CharSequence)} to do processing before emit (wrapping, etc).
*/
@Override
public ITextFlow appendText(CharSequence s) {
return appendText(s, false);
}
@Override
public ITextFlow appendText(CharSequence s, boolean verbatim) {
processEmbeddedLinebreaks(s, verbatim);
return this;
}
@Override
public ITextFlow changeIndentation(int count) {
if(count == 0)
return this;
indent += count * indentSize;
indent = Math.max(0, indent);
return this;
}
protected abstract void doText(CharSequence s, boolean verbatim);
@Override
public abstract ITextFlow ensureBreaks(int count);
@Override
public int getIndentation() {
return indent / indentSize;
}
@Override
public int getIndentSize() {
return indentSize;
}
protected String getLineSeparator() {
return lineSeparator;
}
@Override
public int getPreferredMaxWidth() {
return preferredMaxWidth;
}
@Override
public int getWrapIndentation() {
return wrapIndentSize;
}
/**
* Process (and output) given sequence for embedded line breaks (must be counted and subject to indentation).
* Results in a series of calls to {@link #processTextSequence(CharSequence)} and (if there are
* any embedded breaks) {@link #appendBreaks(1)}.
*
* @param s
*/
protected void processEmbeddedLinebreaks(CharSequence s, boolean verbatim) {
List<CharSequence> lines = CharSequences.split(s, lineSeparator);
int sz = lines.size();
for(int i = 0; i < sz - 1; i++) {
processTextSequence(lines.get(i), verbatim);
appendBreaks(1, verbatim);
}
// last line (may be terminated with line separator)
if(sz > 0) {
CharSequence line = lines.get(sz - 1);
processTextSequence(line, verbatim);
if(CharSequences.endsWith(line, lineSeparator))
appendBreaks(1, verbatim);
}
}
/**
* This default implementation does nothing but call {@link #doText(CharSequence)} - a derived implementation
* may buffer, perform auto line wrapping etc.
*
* @param s
*/
protected void processTextSequence(CharSequence s, boolean verbatim) {
doText(s, verbatim);
}
@Override
public ITextFlow setIndentation(int count) {
indent = Math.max(0, count * indentSize);
return this;
}
@Override
public void setPreferredMaxWidth(int preferredMaxWidth) {
this.preferredMaxWidth = preferredMaxWidth;
};
public void setWrapIndentation(int count) {
wrapIndentSize = count;
}
}