/** * 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 org.cloudsmith.xtext.dommodel.formatter.context.IFormattingContext; import org.eclipse.xtext.util.Exceptions; import com.google.inject.Inject; /** * <p> * An implementation of {@link ITextFlow.WithText} that appends its content to an {@link Appendable} (by default an * internal {@link StringBuilder} instance). * </p> * * <p> * An {@link IOException} thrown by an {@link Appendable} is rethrown as a runtime {@link WrappedException}. * </p> * */ public class TextFlow extends MeasuredTextFlow implements ITextFlow.WithText { private Appendable builder; private boolean lastWasBreak; private int length; private final static String oneSpace = " "; public TextFlow(Appendable output, IFormattingContext formattingContext) { super(formattingContext); length = 0; if(output == null) this.builder = new StringBuilder(); else this.builder = output; this.lastWasBreak = false; } @Inject public TextFlow(IFormattingContext formattingContext) { this(null, formattingContext); } @Override public ITextFlow appendBreaks(int count, boolean verbatim) { super.appendBreaks(count, verbatim); if(count <= 0) return this; internal_append(count == 1 ? getLineSeparator() : new CharSequences.RepeatingString(getLineSeparator(), count)); lastWasBreak = true; return this; } @Override public ITextFlow appendSpaces(int count) { super.appendSpaces(count); if(count <= 0) return this; emit(count == 1 ? oneSpace : new CharSequences.Spaces(count)); return this; } /** * This implementation emits the text to the {@link Appendable} given to the constructor (or an internal buffer if * unspecified). Keeps metrics up to date. */ @Override protected void doText(CharSequence s, boolean verbatim) { super.doText(s, verbatim); emit(s); } private void emit(CharSequence s) { if(s == null || s.length() == 0) return; // do not change state on empty output if(lastWasBreak) { lastWasBreak = false; internal_append(getPendingIndent() == 0 ? "" : new CharSequences.Fixed(indentChar, getPendingIndent())); } internal_append(s); } @Override public CharSequence getText() { CharSequence a = (builder instanceof CharSequence) ? (CharSequence) builder : builder.toString(); // include the not yet fully processed text by adding indent and current run CharSequence b = getCurrentRun(); if(lastWasBreak && b.length() > 0) a = CharSequences.concatenate(a, new CharSequences.Fixed(indentChar, getPendingIndent())); return CharSequences.concatenate(a, getCurrentRun()); } private void internal_append(CharSequence s) { // keep track of how much was appended length += s.length(); try { builder.append(s); } catch(IOException e) { Exceptions.throwUncheckedException(e); } } @Override public void setIndentFirstLine(boolean flag) { super.setIndentFirstLine(flag); // if not empty, just remembers the flag if(!isEmpty()) return; lastWasBreak = flag; // fake a break, or clear } @Override public int size() { return length; } }