All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.uka.ilkd.pp.Layouter Maven / Gradle / Ivy

The newest version!
//This file is part of the Java™ Pretty Printer Library (JPPlib)
//Copyright (c) 2009, Martin Giese
//All rights reserved.
//
//Redistribution and use in source and binary forms, with or without 
//modification, are permitted provided that the following conditions are met:
// 
// * Redistributions of source code must retain the above copyright 
//   notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright 
//   notice, this list of conditions and the following disclaimer in the 
//   documentation and/or other materials provided with the distribution.
// * Neither the name of the author nor the names of his contributors 
//   may be used to endorse or promote products derived from this 
//   software without specific prior written permission.
// 
//THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
//AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
//IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
//LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
//CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
//SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
//INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
//CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
//ARISING IN ANY WAY OUT OF THE USE OF THIS  SOFTWARE, EVEN IF ADVISED OF 
//THE POSSIBILITY OF SUCH DAMAGE.

package de.uka.ilkd.pp;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.List;
import java.util.StringTokenizer;

/**
 * Pretty-print information formatted using line breaks and indentation. For
 * instance, this class can be used to print
 * 
 * 
 * while (i > 0) {
 *   i--;
 *   j++;
 * }
 * 
* * instead of * *
 * while (i > 0) { i
 * --; j++;}
 * 
* * if a maximum line width of 15 characters is chosen. * *

* The formatted output is directed to a backend which might write it * to an I/O stream, append it to the text of a GUI componenet or store it in a * string. The {@link Backend} interface encapsulates the concept of backend. * Apart from handling the output, the backend is also asked for the available * line width and for the amount of space needed to print a string. This makes * it possible to include e.g. HTML markup in the output which does not take up * any space. There are two convenience implementations {@link WriterBackend} * and {@link StringBackend}, which write the output to a * {@link java.io.Writer}, resp. a {@link java.lang.String}. * *

* The layouter internally keeps track of a current indentation * level. * Think of nicely indented Java source code. Then the indentation level at any * point is the number of blank columns to be inserted at the begining of the * next line if you inserted a line break. To increase the indentation level of * parts of the text, the input to the layouter is separated into * blocks. The indentation level changes when a block is begun, and * it is reset to its previous value when a block is ended. Of course, blocks * maybe nested. * * In order to break text among several lines, the layouter needs to be told * where line breaks are allowed. A break is a position in the text * where there is either a line break (with appropriate indentation) or a number * of spaces, if enough material fits in one line. In order to handle the * indentation level properly, breaks should only occur inside blocks. There are * in fact two kinds of blocks: consistent and inconsistent * ones. In a consistent block, lines are broken either at all or at none of the * breaks. In an inconsistent block, as much material as possible is put on one * line before it is broken. * *

* Consider the program above. It should be printed either as * *

 * while (i > 0) { i--; j++; }
 * 
* * or, if there is not enough space on the line, as * *
 * while (i > 0) {
 *   i--;
 *   j++;
 * }
 * 
* * Given a Layouter object l, we could say: * *
 * l.beginC(2).print("while (i>0) {").brk(1,0)
 *  .print("i--;").brk(1,0)
 *  .print("j++;").brk(1,-2)
 *  .print("}").end();
 * 
* * The call to {@link #beginC(int)} starts a consistent block, advancing the * indentation level by 2, relative to the current position in the line. * The {@link #print(String)} methods gives some actual * text to be output. The call to {@link #brk(int,int)} inserts a break. The * first argument means that one space should be printed at this position if the * line is not broken. The second argument is an offset to be added * to the indentation level for the next line, if the line is broken. * The effect of this parameter can be seen in the call brk(1,-2). * The offset of -2 outdents the last line by 2 positions, which * aligns the closing brace with the while. * *

* If the lines in a block are broken, one sometimes wants to insert spaces up * to the current indentation level at a certain position without allowing a * line break there. This can be done using the {@link #ind(int,int)} method. * For instance, one wants to output either * *

 *   ...[Good and Bad and Ugly]...
 * 
* * or * *
 *   ...[    Good
 *       and Bad
 *       and Ugly]...
 * 
* * Note the four spaces required before Good. We do this by * opening a block which sets the indentation level to the column where the * G ends up and outdenting the lines with the and: * *
 * l.print("...[").beginC(4).ind(0, 0).print("Good").brk(1, -4).print("and ")
 * 		.print("Bad").brk(1, -4).print("and ").print("Ugly").end()
 * 		.print("]...");
 * 
* * Again, the first argument to {@link #ind(int,int)} is a number of spaces to * print if the block we are in is printed on one line. The second argument is * an offset to be added to the current indentation level to determine the * column to which we should skip. * *

* When all text has been sent to a Layouter and all blocks have been ended, the * {@link #close()} method should be closed. This sends all pending output to * the backend and invokes the {@link Backend#close()} method, which usually * closes I/O streams, etc. * *

The indentation level of a new block can be increased either relative to * the current position on the line, or relative to the surrounding block's * indentation level. Using the current position is useful e.g. to produce output * like *

 * if x>=2 then x := x-2
 *         else x := x+2
 * 
* where beginning a block in front of the then can set the * indentation level to the current position in the line without knowing how * much space the condition took up. Indenting by 3 relative to the surrounding * block's indentation level would produce *
 * if x>=2 then x := x-2
 *    else x := x+2
 * 
* no matter where in the first line the block was opened. *

* Some applications need to keep track of where certain parts of the input text * end up in the output. For this purpose, the Layouter class provides the * {@link #mark(Object)} method. * *

* The public methods of this class may be divided into two categories: A small * number of primitive methods, as described above, and a host of * convenience methods which simplify calling the primitive ones for * often-used arguments. For instance, a call to {@link #beginC()} is shorthand * for beginC(ind), where ind is the default * indentation selected when the Layouter was constructed. * *

* Most of the methods can throw an {@link UnbalancedBlocksException}, which * indicates that the sequence of method calls was illegal, i.e. more blocks * were ended than begun, the Layouter is closed before all blocks are ended, a * break occurs outside of any block, etc. * *

* The backend might throw exceptions of the type indicated by the type * parameter {@code Exc}. Such exceptions get passed through to the caller of * the Layouter. Note that since text usually gets buffered before it is sent to * the backend, exceptions thrown by calls to Layouter methods might not be * caused directly by that method call but by an earlier one getting forwarded * to the backend. * *

* The algorithm used is essentially the classical one from Derek C. Oppen: * Prettyprinting, TOPLAS volume 2 number 4, ACM, 1980, pp. 465-483, * with some minor extensions. It has the property that if the input contains * enough actual text, i.e. not just arbitrarily long sequences of calls to * beginC, mark, etc., then pretty-printing uses * constant space, and time linear in the size of the input. In fact, output * will begin before the whole input has been given, so this class can be used * to pretty-print a stream of data. * * @param * The type of exceptions that might be thrown by the backend. * * @author Martin Giese * @see Backend */ /* * Implementation note: The name of this class is actually a lie. What this * class does is calculate the space needed by blocks and portions of blocks * between breaks if they are to be printed in a single line. The actual * laying-out, that is choosing whether to break lines or not is done by a * Printer object, which in turn sends its output to the Backend. * */ public class Layouter { private static final Logger LOG; static { LOG = LoggerFactory.getLogger(Layouter.class); } private boolean finished; /** An enum type to distinguish consistent and inconsistent blocks. */ public static enum BreakConsistency {CONSISTENT,INCONSISTENT} /** An enum type to distinguish indentation relative to the current position * or relative to the surrounding block's indentation level */ public static enum IndentationBase {FROM_POS,FROM_IND} /** The backend */ private Backend back; /** The Printer used for output. */ private Printer out; /** The list of scanned tokens not yet output. */ private List stream = new java.util.LinkedList(); /** * A stack of OpenBlockTokens and BreakTokens * in stream, waiting for their size to be determined. */ private List delimStack = new java.util.LinkedList(); /* * Some Invariants: * * delimStack.isEmpty() implies stream.isEmpty() * * Any OpenBlockToken in stream is also on the demlimStack. The latest * BreakToken of any open block in the stream is also on the delim stack. * */ /** * Total size of received strings and blanks, if they were printed in one * line. The difference of this between two states says how much space would * be needed to print the intervening stuff without line breaks. */ private int totalSize = 0; /** * Total size of strings and blanks sent to the Printer out. * Subtract this from totalOutput and you get the space * needed to print what is still buffered in stream */ private int totalOutput = 0; /** * The size assigned to things which are guaranteed not to fit on a line. * For good measure, this is intitialized to twice the line width by the * constructors. */ private int largeSize; /** A default indentation value used for blocks. */ private int defaultInd; // PRIMITIVE CONSTRUCTOR ------------------------------------------- /** * Construts a newly allocated Layouter which will send output to the given * {@link Backend} and has the given default indentation. * * @param back * the Backend * @param indentation * the default indentation * */ public Layouter(Backend back, int indentation) { this.back = back; out = new Printer(back); largeSize = 2 * back.lineWidth(); this.defaultInd = indentation; } // STATIC FACTORY METHODS ---------------------------------------- /** = 80 : The line width for some of the convenience factories. */ public static final int DEFAULT_LINE_WIDTH = 80; /** * = 2 : The default indentation for some of the convenience constructors */ public static final int DEFAULT_INDENTATION = 2; /** * Factory method for a Layouter with a {@link WriterBackend}. The line * width is taken to be {@link #DEFAULT_LINE_WIDTH}, and the default * indentation {@link #DEFAULT_INDENTATION}. * * @param writer * the {@link java.io.Writer} the Backend is going to use */ public static Layouter getWriterLayouter(java.io.Writer writer) { return getWriterLayouter(writer, DEFAULT_LINE_WIDTH); } /** * Factory method for a Layouter with a {@link WriterBackend}. The default * indentation is taken from {@link #DEFAULT_INDENTATION}. * * @param writer * the {@link java.io.Writer} the Backend is going to use * @param lineWidth * the maximum lineWidth the Backend is going to use */ public static Layouter getWriterLayouter( java.io.Writer writer, int lineWidth) { return getWriterLayouter(writer, lineWidth, DEFAULT_INDENTATION); } /** * Factory method for a Layouter with a {@link WriterBackend}. * * @param writer * the {@link java.io.Writer} the Backend is going to use * @param lineWidth * the maximum lineWidth the Backend is going to use * @param indentation * the default indentation */ public static Layouter getWriterLayouter( java.io.Writer writer, int lineWidth, int indentation) { return new Layouter(new WriterBackend(writer, lineWidth), indentation); } /** * Factory method for a Layouter with a {@link StringBackend}. The line * width is taken to be {@link #DEFAULT_LINE_WIDTH}, and the default * indentation {@link #DEFAULT_INDENTATION}. * * @param sb * the {@link java.lang.StringBuilder} the Backend is going to * use */ public static Layouter getStringLayouter(StringBuilder sb) { return getStringLayouter(sb, DEFAULT_LINE_WIDTH); } /** * Factory method for a Layouter with a {@link StringBackend}. The default * indentation is taken from {@link #DEFAULT_INDENTATION}. * * @param sb * the {@link StringBuilder} the Backend is going to use * @param lineWidth * the maximum lineWidth the Backend is going to use */ public static Layouter getStringLayouter(StringBuilder sb, int lineWidth) { return getStringLayouter(sb, lineWidth, DEFAULT_INDENTATION); } /** * Factory method for a Layouter with a {@link StringBackend}. * * @param sb * the {@link StringBuilder} the Backend is going to use * @param lineWidth * the maximum lineWidth the Backend is going to use * @param indentation * the default indentation */ public static Layouter getStringLayouter(StringBuilder sb, int lineWidth, int indentation) { return new Layouter(new StringBackend(sb, lineWidth), indentation); } // PROPERTY GETTERS ------------------------------------ /** * Gets default indentation for this block * * @return default indentation */ public int getDefaultIndentation() { return defaultInd; } // PRIMITIVE STREAM OPERATIONS ------------------------------------ /** * Output text material. The string s should not contain * newline characters. If you have a string with newline characters, and * want to retain its formatting, consider using the {@link #pre(String s)} * method. The Layouter will not insert any line breaks in such a string. * * @param s * the String to print. * @return this */ public Layouter print(String s) throws Exc { LOG.trace("print: {}", s); checkNotFinished(); if (delimStack.isEmpty()) { out.print(s); totalSize += back.measure(s); totalOutput += back.measure(s); } else { enqueue(new StringToken(s)); totalSize += back.measure(s); while (totalSize - totalOutput > out.space() && !delimStack.isEmpty()) { popBottom().setInfiniteSize(); advanceLeft(); } } return this; } /** * Begin a block. Parameter cons indicates whether this is a * consistent block or an inconsistent one. In consistent blocks, breaks are * either all broken or none is broken. The indentation level is increased * by indent, either relative to the current position, * or relative to the surrounding block's indentation level, depending on * the parameter indBase. * * @param cons * true for consistent block * @param indBase * increment relative to current pos, not indentation * @param indent * increment to indentation level * @return this */ public Layouter begin(BreakConsistency cons, IndentationBase indBase, int indent) { if (LOG.isTraceEnabled()) { LOG.trace("begin: {} {} {}", cons, indBase, Integer.valueOf(indent)); } checkNotFinished(); StreamToken t = new OpenBlockToken(cons, indBase, indent); enqueue(t); push(t); return this; } private void checkNotFinished() { if (finished) { throw new IllegalStateException("Layouter is already finished"); } } /** * Ends the innermost block. * * @return this */ public Layouter end() throws Exc { LOG.trace("end"); checkNotFinished(); if (delimStack.isEmpty()) { /* then stream is also empty, so output */ out.closeBlock(); } else { enqueue(new CloseBlockToken()); StreamToken topDelim = pop(); topDelim.setEnd(); if (topDelim.isBreakToken() && !delimStack.isEmpty()) { /* This must be the matching OpenBlockToken */ StreamToken topOpen = pop(); topOpen.setEnd(); } if (delimStack.isEmpty()) { /* preserve invariant */ advanceLeft(); } } return this; } /** * Print a break. This will print width spaces if the line is * not broken at this point. If it is broken, * indentation is added to the current indentation level, plus the value of * offset. * * @param width * space to insert if not broken * @param offset * offset relative to current indentation level * @return this */ public Layouter brk(int width, int offset) throws Exc { if (LOG.isTraceEnabled()) { LOG.trace("brk: {} {}", Integer.valueOf(width), Integer.valueOf(offset)); } checkNotFinished(); if (!delimStack.isEmpty()) { StreamToken s = top(); if (s.isBreakToken()) { pop(); s.setEnd(); } } StreamToken t = new BreakToken(width, offset); enqueue(t); push(t); totalSize += width; return this; } /** * Indent relative to the indentation level if surrounding block is broken. If * the surrounding block fits on one line, insert width * spaces. Otherwise, indent to the current indentation level, plus * offset, unless that position has already been exceeded on * the current line. If that is the case, nothing is printed. No line break * is possible at this point. * * @param width * space to insert if not broken * @param offset * offset relative to current indentation level * @return this */ public Layouter ind(int width, int offset) throws Exc { if (LOG.isTraceEnabled()) { LOG.trace("ind: {} {}", Integer.valueOf(width), Integer.valueOf(offset)); } checkNotFinished(); if (delimStack.isEmpty()) { out.indent(width, offset); totalSize += width; totalOutput += width; } else { enqueue(new IndentationToken(width, offset)); totalSize += width; } return this; } /** * This leads to a call of the {@link Backend#mark(Object)} method of the * backend, when the material preceding the call to mark has * been printed to the backend, including any inserted line breaks and * indentation. The {@link Object} argument to mark is passed * through unchanged to the backend and may be used by the application to * pass information about the purpose of the mark. * * @param o * an object to be passed through to the backend. * @return this * */ public Layouter mark(Object o) throws Exc { if (LOG.isTraceEnabled()) { LOG.trace("mark: {}", o); } checkNotFinished(); if (delimStack.isEmpty()) { out.mark(o); } else { enqueue(new MarkToken(o)); } return this; } /** * Output any information currently kept in buffers. This is essentially * passed on to the backend. Note that material in blocks begun but not * ended cannot be forced to the output by this method. Finish all blocks * and call flush or {@link #close()} then. * * @return this */ public Layouter flush() throws Exc { if (LOG.isTraceEnabled()) { LOG.trace("flush"); } checkNotFinished(); out.flush(); return this; } /** * @see #finish() * @return {@code true} if the Layouter has finished * @since 0.7.1 */ public boolean isFinished() { return this.finished; } /** * Finish (if not already finished) and lose the Layouter. No more methods * should be called after this. All * blocks begun must have been ended by this point. Any pending material is * written to the backend, before the {@link Backend#close()} method of the * backend is called, which closes any open I/O streams, etc. * * @see #finish() */ public void close() throws Exc { LOG.trace("close"); if (!finished) { finish(); } out.close(); } /** * Finish the Layouter. No more methods should be called after this. All * blocks begun must have been ended by this point. Any pending material is * written to the backend. * * @since 0.7.1 */ public void finish() throws Exc { try { checkNotFinished(); if (!delimStack.isEmpty()) { throw new UnbalancedBlocksException(); } else { advanceLeft(); } } finally { this.finished = true; } } // CONVENIENCE STREAM OPERATIONS --------------------------------- /** * Begin a block. If consistent is set, breaks are either all * broken or all not broken. The indentation level is increased by * indent, relative to the current position. * * @deprecated use {@link #begin(de.uka.ilkd.pp.Layouter.BreakConsistency, de.uka.ilkd.pp.Layouter.IndentationBase, int)} * * @param consistent * true for consistent block * @param indent * increment to indentation level * @return this */ public Layouter begin(boolean consistent, int indent) { if (LOG.isTraceEnabled()) { LOG.trace( "begin: {} {}", Boolean.valueOf(consistent), Integer.valueOf(indent)); } return begin(consistent?BreakConsistency.CONSISTENT:BreakConsistency.INCONSISTENT, IndentationBase.FROM_POS, indent); } /** * Begin an inconsistent block. Increment the indentation level by this * layouter's default indentation, relative to the current position. * * @return this */ public Layouter beginI() { return begin(BreakConsistency.INCONSISTENT, IndentationBase.FROM_POS, defaultInd); } /** * Begin a consistent block. Increment the indentation level by this * layouter's default indentation, relative to the current position. * * @return this */ public Layouter beginC() { return begin(BreakConsistency.CONSISTENT, IndentationBase.FROM_POS, defaultInd); } /** * Begin an inconsistent block. Add indent to the indentation * level, relative to the current position. * * @param indent * the indentation for this block * @return this */ public Layouter beginI(int indent) { return begin(BreakConsistency.INCONSISTENT, IndentationBase.FROM_POS, indent); } /** * Begin a consistent block. Add indent to the indentation * level, relative to the current position. * * @param indent * the indentation for this block * @return this */ public Layouter beginC(int indent) { return begin(BreakConsistency.CONSISTENT, IndentationBase.FROM_POS, indent); } /** * Begin a block with default indentation. Add this Layouter's default * indentation to the indentation level, relative to the current position. * * @deprecated use {@link #begin(de.uka.ilkd.pp.Layouter.BreakConsistency, de.uka.ilkd.pp.Layouter.IndentationBase, int)} * * @param consistent * true for consistent block * @return this */ public Layouter begin(boolean consistent) { return begin(consistent,defaultInd); } /** * Begin an inconsistent block. Increment the indentation level by this * layouter's default indentation, relative to the surrounding block's * indentation level. * * @return this */ public Layouter beginIInd() { return begin(BreakConsistency.INCONSISTENT, IndentationBase.FROM_IND, defaultInd); } /** * Begin a consistent block. Increment the indentation level by this * layouter's default indentation, relative to the surrounding block's * indentation level. * * @return this */ public Layouter beginCInd() { return begin(BreakConsistency.CONSISTENT, IndentationBase.FROM_IND, defaultInd); } /** * Begin an inconsistent block. Increment the indentation level by * indent, relative to the surrounding block's indentation * level. * * @param indent * the indentation for this block * @return this */ public Layouter beginIInd(int indent) { return begin(BreakConsistency.INCONSISTENT, IndentationBase.FROM_IND, indent); } /** * Begin a consistent block. Increment the indentation level by * indent, relative to the surrounding block's indentation * level. * * @param indent * the indentation for this block * @return this */ public Layouter beginCInd(int indent) { return begin(BreakConsistency.CONSISTENT, IndentationBase.FROM_IND, indent); } /** * Print a break with zero offset. * * @param width * space to insert if not broken * @return this */ public Layouter brk(int width) throws Exc { return brk(width, 0); } /** * Print a break with zero offset and width one. * * @return this */ public Layouter brk() throws Exc { return brk(1); } /** * Print a break with zero offset and large width. As the large number of * spaces will never fit into one line, this amounts to a forced line break. * * @return this */ public Layouter nl() throws Exc { return brk(largeSize); } /** * Indent with zero offset and zero width. Just indents to the current * indentation level if the block is broken, and prints nothing otherwise. * * @return this */ public Layouter ind() throws Exc { return this.ind(0, 0); } /** * Layout prefromated text. This amounts to a (consistent) block with * indentation 0, where each line of s (separated by \n) gets * printed as a string and newlines become forced breaks. * * @param s * the pre-formatted string * @return this */ public Layouter pre(String s) throws Exc { if (LOG.isTraceEnabled()) { LOG.trace("pre: {}", s); } StringTokenizer st = new StringTokenizer(s, "\n", true); beginC(0); while (st.hasMoreTokens()) { String line = st.nextToken(); if ("\n".equals(line)) { nl(); } else { print(line); } } end(); return this; } // PRIVATE METHODS ----------------------------------------------- /* Delimiter Stack handling */ /** Push an OpenBlockToken or BreakToken onto the delimStack */ private void push(StreamToken t) { delimStack.add(t); } /** Pop the topmost Token from the delimStack */ private StreamToken pop() { try { return (StreamToken) (delimStack.remove(delimStack.size() - 1)); } catch (IndexOutOfBoundsException e) { throw new UnbalancedBlocksException(); } } /** * Remove and return the token from the bottom of the delimStack */ private StreamToken popBottom() { try { return (StreamToken) (delimStack.remove(0)); } catch (IndexOutOfBoundsException e) { throw new UnbalancedBlocksException(); } } /** Return the top of the delimStack, without popping it. */ private StreamToken top() { try { return (StreamToken) delimStack.get(delimStack.size() - 1); } catch (IndexOutOfBoundsException e) { throw new UnbalancedBlocksException(); } } /* stream handling */ /** Put a StreamToken into the stream (at the end). */ private void enqueue(StreamToken t) { stream.add(t); } /** * Send tokens from stream to out as long * as there are tokens left and their size is known. */ private void advanceLeft() throws Exc { StreamToken t; while (!stream.isEmpty() && ((t = (StreamToken) stream.get(0)).followingSizeKnown())) { t.print(); stream.remove(0); totalOutput += t.size(); } } // STREAM TOKEN CLASSES ----------------------------------------- /** * A stream token. */ private abstract class StreamToken { /** Send this token to the Printer {@link #out}. */ abstract void print() throws Exc; /** Return the size of this token if the block is not broken. */ abstract int size(); /** * Return the `section' size. For an OpenBlockToken, this is the size of * the whole block, if it is not broken. For a BreakToken, it is the * size of the material up to the next corresponding BreakToken or * CloseBlockToken. Otherwise it is the same as size(). This might only * be known after several more tokens have been read. If the value is * guaranteed to be larger than what fits on a line, some large value * might be returned instead of the precise size. */ /* This is actually called only in classes under SizeCalculatingToken, which * overrides it. But it's nicer to think of it in conjunction with * followingSizeKnown() below.*/ @SuppressWarnings("unused") int followingSize() { return size(); } /** * Returns whether the followingSize is already known. That is the case * if either a corresponding next BreakToken or CloseBlockToken has been * encountered, or if the material is known not to fit on a line. */ boolean followingSizeKnown() { return true; } /** * Indicate that the corresponding next BreakToken or CloseBlockToken * has been encountered. After this, followingSizeKnown() will return * the correct value. */ void setEnd() { throw new UnsupportedOperationException(); } /** * Indicate that the followingSize is guaranteed to be larger than the * line width, and that it can thus be set to some large value. */ void setInfiniteSize() { throw new UnsupportedOperationException(); } /** * Returns whether this is a BreakToken. It returns * false, and is overriden by BreakToken * to return true. */ boolean isBreakToken() { return false; } } /** A token corresponding to a print call. */ private class StringToken extends StreamToken { String s; StringToken(String s) { this.s = s; } void print() throws Exc { out.print(s); } int size() { return back.measure(s); } } /** A token corresponding to an ind call. */ private class IndentationToken extends StreamToken { protected int width; protected int offset; IndentationToken(int width, int offset) { this.width = width; this.offset = offset; } void print() throws Exc { out.indent(width, offset); } int size() { return width; } } /** Superclass of tokens which calculate their followingSize. */ private abstract class SizeCalculatingToken extends StreamToken { protected int begin = 0; /** negative means that end has not been set yet. */ protected int end = -1; SizeCalculatingToken() { begin = totalSize; } int followingSize() { return end - begin; } boolean followingSizeKnown() { return end >= 0; } void setEnd() { this.end = totalSize; } void setInfiniteSize() { end = begin + largeSize; } } /** A token corresponding to a brk call. */ private class BreakToken extends SizeCalculatingToken { protected int width; protected int offset; BreakToken(int width, int offset) { this.width = width; this.offset = offset; } int size() { return width; } void print() throws Exc { out.printBreak(width, offset, followingSize()); } boolean isBreakToken() { return true; } } /** A token corresponding to a begin call. */ private class OpenBlockToken extends SizeCalculatingToken { protected BreakConsistency cons; protected IndentationBase indBase; protected int indent; OpenBlockToken(BreakConsistency consistent, IndentationBase fromPos, int indent) { this.cons = consistent; this.indBase = fromPos; this.indent = indent; } int size() { return 0; } void print() throws Exc { out.openBlock(cons,indBase, indent, followingSize()); } } /** A token corresponding to an end call. */ private class CloseBlockToken extends StreamToken { CloseBlockToken() { } void print() throws Exc { out.closeBlock(); } int size() { return 0; } } /** A token corresponding to a mark call. */ private class MarkToken extends StreamToken { protected Object o; MarkToken(Object o) { this.o = o; } int size() { return 0; } void print() throws Exc { out.mark(o); } } }