/*******************************************************************************
* Copyright (c) 2011 SAP AG and others.
* 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:
* SAP AG - initial API and implementation
******************************************************************************/
package com.sap.furcas.prettyprinter;
import static com.sap.furcas.prettyprinter.TextBlocksFactory.getLengthOf;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.sap.furcas.metamodel.FURCAS.TCS.ConcreteSyntax;
import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode;
import com.sap.furcas.metamodel.FURCAS.textblocks.OmittedToken;
import com.sap.furcas.prettyprinter.context.PrintContext;
/**
* The {@link PrettyPrinter} produces formatted text. It has to add spaces between elements for
* disambiguation purposes (e.g. "1 2" and not "12") and to perform the actual formatting (e.g. linebreaks,
* indentation and spaces before and after symbols as configured in the {@link ConcreteSyntax}).
*
* Due to the way a syntax is structured, the correct whitespaces/formatting between text fragments (e.g.
* 'text before' and 'text after') can only be calculated at the moment when 'text after' shall be printed.
*
* ...text before] [Whitespace/Formatting] [text after...
*
* After printing 'text before', the {@link PrettyPrinter} therefore does not directly create any
* whitespaces ({@link OmittedToken}s). Instead, it queues so called {@link FormatRequest}s.
* The number and kind of these requests depends on the {@link ConcreteSyntax} and the current pretty printing
* context.
*
* It is then the responsibility of this class to interpret these {@link FormatRequest}s and to translate
* them into {@link OmittedToken}s. These tokens can then be appended to the print result before 'text after'
* is printed.
*
* @author Stephan Erb
*
*/
public class Formatter {
public enum Type {
ADD_TAB,
ADD_NEWLINE,
ADD_CUSTOM,
/** Skippable via {@link #SKIP_SPACE} */
ADD_SPACE,
/** Skippable via {@link #SKIP_SPACE} or {@link #SKIP_OPTIONAL_SPACE} */
ADD_OPTIONAL_SPACE,
/** Unskippable */
ADD_FORCED_SPACE,
SKIP_SPACE,
SKIP_OPTIONAL_SPACE
}
/**
* @see Formatter
*/
public static class FormatRequest {
private final Type type;
private final String payload;
private final int indentationLevel;
private FormatRequest(Type type, String payload, int indentationLevel) {
this.type = type;
this.payload = payload;
this.indentationLevel = indentationLevel;
}
public static FormatRequest create(Type type) {
return new FormatRequest(type, null, 0);
}
public static FormatRequest createNewline(int indentationLevel) {
return new FormatRequest(Type.ADD_NEWLINE, null, indentationLevel);
}
public static FormatRequest createCustom(String payload) {
return new FormatRequest(Type.ADD_CUSTOM, payload, 0);
}
}
// Per default indent with four spaces
private final static String INDENTATION_PRIMITIVE = " ";
private final static int INDENTATION_PRIMITIVE_COUNT = 4;
private final TextBlocksFactory factory;
public Formatter(TextBlocksFactory factory) {
this.factory = factory;
}
public List<DocumentNode> translateToTokens(List<FormatRequest> formatRequests, PrintContext context) {
int offset = context.getNextOffset();
List<DocumentNode> formatting = new ArrayList<DocumentNode>();
OmittedToken disambiguationToken = null;
boolean skipSpace = false;
boolean skipOptionalSpace = false;
for (FormatRequest request : formatRequests) {
switch (request.type) {
case ADD_FORCED_SPACE:
formatting.add(factory.createOmittedToken(" ", offset));
break;
case ADD_SPACE:
if (skipSpace) {
continue;
}
if (disambiguationToken == null) {
disambiguationToken = factory.createOmittedToken(" ", offset);
}
case ADD_OPTIONAL_SPACE:
if (skipOptionalSpace) {
continue;
}
if (disambiguationToken == null) {
disambiguationToken = factory.createOmittedToken(" ", offset);
}
continue;
case ADD_NEWLINE:
formatting.add(factory.createOmittedToken("\n", offset));
formatting.addAll(createIndentation(request.indentationLevel,
getLengthOf(formatting, context.getNextOffset())));
break;
case ADD_TAB:
formatting.add(factory.createOmittedToken("\t", offset));
break;
case ADD_CUSTOM:
formatting.add(factory.createOmittedToken(request.payload, offset));
break;
case SKIP_SPACE:
skipSpace = true;
// fall through
case SKIP_OPTIONAL_SPACE:
skipOptionalSpace = true;
disambiguationToken = null;
continue;
}
offset = getLengthOf(formatting, context.getNextOffset());
}
if (formatting.isEmpty() && disambiguationToken != null) {
formatting.add(disambiguationToken);
}
return formatting;
}
private Collection<OmittedToken> createIndentation(int indentationLevel, int offset) {
List<OmittedToken> indentation = new ArrayList<OmittedToken>(indentationLevel);
for (int i=0; i < indentationLevel; i++) {
for (int j=0; j<INDENTATION_PRIMITIVE_COUNT; j++) {
indentation.add(factory.createOmittedToken(INDENTATION_PRIMITIVE, offset));
offset++;
}
}
return indentation;
}
}