package common.prettyprinter; import java.util.HashMap; import java.util.LinkedList; import java.util.ListIterator; import java.util.Map; /** * Provides functionality to generate a {@link common.prettyprinter.PrettyString} * in an incremental fashion. * * @author Benedikt Meurer * @version $Id$ * * @see common.prettyprinter.PrettyString */ public final class PrettyStringBuilder { // // Constants // /** * An empty integer array, which is used for the break offsets while * constructing {@link PrettyAnnotation}s if no breaks are specified * for a given builder using the {@link #appendBreak()}. * * @see PrettyAnnotation */ private static int[] EMPTY_ARRAY = new int[0]; // // Attributes // /** * The {@link PrettyPrintable} for which this builder was allocated. * * @see #PrettyStringBuilder(PrettyPrintable, int) */ private PrettyPrintable printable; /** * The return priority of the printable according to the priority grammar. * * @see #PrettyStringBuilder(PrettyPrintable, int) * @see #getReturnPriority() */ private int returnPriority; /** * FIXME */ private LinkedList<Item> items = new LinkedList<Item>(); // // Constructor // /** * Allocates a new <code>PrettyStringBuilder</code>, which will generate * an annotation for the <code>printable</code> for the whole string * represented by the builder. * * @param printable the printable object. * @param returnPriority the return priority according to the priority * grammar used for the printables in this * builder. * * @throws NullPointerException if <code>printable</code> is <code>null</code>. */ public PrettyStringBuilder(PrettyPrintable printable, int returnPriority) { if (printable == null) { throw new NullPointerException("printable is null"); } this.printable = printable; this.returnPriority = returnPriority; } // // Accessors // /** * Gives the <code>returnPriority</code> for the pretty printer * of this builder. The <code>returnPriority</code> is specified * when constructing the builder. * * @return the <code>returnPriority</code>. */ public int getReturnPriority() { return this.returnPriority; } // // Insertion // /** * Appends a break location to the string builder. * A break marks the location as possible newline * insertion position for the presenter. */ public void appendBreak() { this.items.add(new BreakItem()); } /** * Inserts the given <code>builder</code> at the specified * <code>argumentPriority</code> at the end of our builder. * * @param builder the <code>PrettyStringBuilder</code> to insert. * @param argumentPriority the argument priority of the <code>builder</code>. * * @throws NullPointerException if <code>builder</code> is <code>null</code>. */ public void appendBuilder(PrettyStringBuilder builder, int argumentPriority) { // check if we need to add parenthesis boolean parenthesis = (builder.getReturnPriority() < argumentPriority); // add the required items if (parenthesis) this.items.add(new TextItem("(", PrettyStyle.NONE)); this.items.add(new BuilderItem(builder)); if (parenthesis) this.items.add(new TextItem(")", PrettyStyle.NONE)); } /** * Appends the given <code>constant</code> to the pretty string * builder. Constants will be highlighted when displayed to the * user. * * @param constant the constant to append. * * @see #appendKeyword(String) * @see #appendText(String) */ public void appendConstant(String constant) { this.items.add(new TextItem(constant, PrettyStyle.CONSTANT)); } /** * Appends the given <code>keyword</code> to the pretty string * builder. Keywords will be highlighted when displayed to the * user. * * @param keyword the keyword to append. * * @see #appendConstant(String) * @see #appendText(String) */ public void appendKeyword(String keyword) { this.items.add(new TextItem(keyword, PrettyStyle.KEYWORD)); } /** * Appends the given <code>text</code> to the pretty string * builder. Don't use this method for keywords, but use the * <code>appendKeyword</code> method instead. * * @param text the text to append. * * @see #appendKeyword(String) */ public void appendText(String text) { this.items.add(new TextItem(text, PrettyStyle.NONE)); } // // Conversion // /** * Converts the string builder content to a {@link PrettyString}. * The returned {@link PrettyString} will be read-only and not * updated when the state of the pretty string builder changes. * * @return the pretty string for the current builder content. */ public PrettyString toPrettyString() { // determine the final string length for the builder contents int length = determineStringLength(); // allocate a styles map PrettyStyle styles[] = new PrettyStyle[length]; // allocate the string buffer StringBuilder buffer = new StringBuilder(length); // allocate an empty annotations map Map<PrettyPrintable, PrettyAnnotation> annotations = new HashMap<PrettyPrintable, PrettyAnnotation>(); // determine the string representation and place it into the string buffer determineString(buffer, annotations, styles); return new DefaultPrettyString(buffer.toString(), annotations, styles); } // // Overridden methods // /** * Returns the string representation of the current string * builder content. This method is used for debugging * purposes. * * @return the string representation of the current content. * * @see java.lang.Object#toString() */ @Override public String toString() { return toPrettyString().toString(); } // // Internals // /** * Determines the required string size for the string representation * of the pretty string builder contents. * * @return the required string size. * * @see #determineString(StringBuilder, HashMap, boolean[]) */ private int determineStringLength() { int length = 0; for (ListIterator<Item> iterator = this.items.listIterator(); iterator.hasNext(); ) length += iterator.next().determineStringLength(); return length; } /** * Determines the string representation of the pretty string builder contents * and places the result into <code>buffer</code>. * * The <code>styles</code> map must be large enough to contain a <code>PrettyStyle</code> * entry for each character in the target string, * * @param buffer the target string buffer. * @param annotations the result annotations map. * @param styles the result <code>PrettyStyle</code> map. * * @see #determineStringLength() */ private void determineString(StringBuilder buffer, Map<PrettyPrintable, PrettyAnnotation> annotations, PrettyStyle[] styles) { // remember the start offset for the annotation constructor int startOffset = buffer.length(); // we allocate the break offset list only on-demand LinkedList<Integer> breakOffsetList = null; // process all items for this string builder for (ListIterator<Item> iterator = this.items.listIterator(); iterator.hasNext(); ) { Item item = iterator.next(); if (item instanceof BreakItem) { if (breakOffsetList == null) breakOffsetList = new LinkedList<Integer>(); breakOffsetList.add(buffer.length()); } else if (item instanceof TextItem) { String text = ((TextItem)item).content; for (int i = 0; i < text.length(); ++i) styles[buffer.length() + i ] = ((TextItem)item).style; buffer.append(text); } else if (item instanceof BuilderItem) { ((BuilderItem)item).builder.determineString(buffer, annotations, styles); } } // add the annotation for the current builder if (breakOffsetList != null) { // transform the break offset list to an integer array int[] breakOffsets = new int[breakOffsetList.size()]; for (int i = 0; i < breakOffsets.length; ++i) breakOffsets[i] = breakOffsetList.get(i); annotations.put(this.printable, new PrettyAnnotation(startOffset, buffer.length() - 1, breakOffsets)); } else { // just use the empty array for the break offsets to be more efficient annotations.put(this.printable, new PrettyAnnotation(startOffset, buffer.length() - 1, EMPTY_ARRAY)); } } // // Internal classes // private static abstract class Item { abstract int determineStringLength(); } private static class BreakItem extends Item { BreakItem() { } int determineStringLength() { return 0; } } private static class TextItem extends Item { TextItem(String content, PrettyStyle style) { this.content = content; this.style = style; } int determineStringLength() { return this.content.length(); } String content; PrettyStyle style; } private static class BuilderItem extends Item { BuilderItem(PrettyStringBuilder builder) { this.builder = builder; } int determineStringLength() { return this.builder.determineStringLength(); } PrettyStringBuilder builder; } }