/**
* CodeWriter -- Andrew C. Myers, April 2001
* For use in Cornell University Computer Science CS 412/413
*/
package ppg.util;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.io.IOException;
import java.util.Vector;
/**
* A <code>CodeWriter</code> is a pretty-printing engine.
* It formats structured text onto an output stream <code>o</code> in the
* minimum number of lines, while keeping the width of the output
* within <code>width</code> characters if possible.
*/
public class CodeWriter
{
/**
* Create a CodeWriter object with output stream <code>o</code>
* and width <code>width_</code>.
*/
public CodeWriter(OutputStream o, int width_) {
output = new OutputStreamWriter(o);
width = width_;
current = input = new Block(null, 0);
}
/**
* Create a CodeWriter object with output <code>w</code> and
* width <code>width_</code>.
*/
public CodeWriter(Writer w, int width_) {
output = w;
width = width_;
current = input = new Block(null, 0);
}
/** Print the string <code>s</code> verbatim on the output stream. */
public void write(String s) {
if (s.length() > 0)
current.add(new StringItem(s));
}
/** Force a newline with no added indentation. */
public void newline()
{
newline(0);
}
/**
* Start a new block with a relative indentation of <code>n</code>
* characters.
* <br>
* A block is a formatting unit. The formatting algorithm will try
* to put the whole block in one line unless
* <ul>
* <li>there is a <code>newline</code> item in the block.</li>
* <li>the block cannot fit in one line.</li>
* </ul>
* If either of the two conditions is satisfied, the
* formatting algorithm will break the block into lines: every
* <code>allowBreak</code> will cause a line change, the first line
* is printed at the current cursor position <code>pos</code>,
* all the following lines are printed at the position
* <code>pos+n</code>.
*
* @param n the number of characters increased on indentation (relative
* to the current position) for all lines in the block.
*/
public void begin(int n) {
Block b = new Block(current, n);
current.add(b);
current = b;
}
/**
* Terminate the most recent outstanding <code>begin</code>.
*/
public void end() {
current = current.parent;
if (current == null) throw new RuntimeException();
}
/**
* Allow a newline. Indentation will be preserved.
* If no newline is inserted, a single space character is output instead.
*
* @param n the amount of increase in indentation if
* the newline is inserted.
*/
public void allowBreak(int n) {
current.add(new AllowBreak(n, " "));
}
/**
* Allow a newline. Indentation will be preserved.
*
* @param n the amount of increase in indentation if
* the newline is inserted.
* @param alt if no newline is inserted, the string <code>alt</code> is
* output instead.
*/
public void allowBreak(int n, String alt) {
current.add(new AllowBreak(n, alt));
}
/**
* Force a newline. Indentation will be preserved. This method
* should be used sparingly; usually a call to <code>allowBreak</code> is
* preferable because forcing a newline also causes all breaks
* in containing blocks to be broken.
*
* @param n the amount of increase in indentation after the newline.
*/
public void newline(int n) {
current.add(new Newline(n));
}
/**
* Send out the current batch of text to be formatted. All
* outstanding <code>begin</code>'s are closed and the current
* indentation level is reset to 0. Returns true if formatting
* was completely successful (the margins were obeyed).
*/
public boolean flush() throws IOException {
boolean success = true;
try {
Item.format(input,0, 0, width, width, true, true);
} catch (Overrun o) { success = false; }
input.sendOutput(output, 0, 0);
output.flush();
input.free();
current = input = new Block(null, 0);
return success;
}
/**
* Return a readable representation of all the structured input
* given to the CodeWriter since the last flush.
*/
Block input;
Block current;
Writer output;
int width;
}
/**
* An <code>Overrun</code> represents a formatting that failed because the right
* margin was exceeded by at least <code>amount</code> chars.
*/
class Overrun extends Exception
{
int amount;
Overrun(int amount_) {
amount = amount_;
}
}
/**
* An <code>Item</code> is a piece of input handed to the formatter. It
* contains a reference to a possibly empty list of items that follow it.
*/
abstract class Item
{
Item next;
protected Item() { next = null; }
/**
* Try to format this item and subsequent items. The current cursor
* position is <code>pos</code>, left and right margins are as
* specified. Returns the final position, which must be <code><
* fin</code>. If breaks may be broken, <code>can_break</code> is
* set. Return the new cursor position (which may overrun rmargin,
* fin, or both, and set any contained breaks accordingly. (It is
* important that formatN not necessarily convert overruns in its
* final position into exceptions. This allows the calling routine
* to distinguish between 'internal' overruns and ones that it can
* tack on a conservative estimate of how much formatting the rest
* of the list will make the overrun go up by. Also, it simplifies
* the coding of formatN.)
*
* Requires: rmargin < lmargin, pos <= rmargin.
*/
abstract int formatN(int lmargin, int pos, int rmargin, int fin,
boolean can_break, boolean nofail) throws Overrun;
/**
* Send the output associated with this item to <code>o</code>, using the
* current break settings.
*/
abstract int sendOutput(Writer o, int lmargin, int pos)
throws IOException;
/** Make the garbage collector's job easy: free references to any
other items. */
void free() {
if( next != null) {
next.free();
next = null;
}
}
/**
* Try to format a whole sequence of items in the manner of formatN.
* The initial position may be an overrun (this is the only way
* that overruns are checked!) <code>it</code> may be also null,
* signifying an empty list.
*/
static int format(Item it, int lmargin, int pos, int rmargin, int fin,
boolean can_break, boolean nofail) throws Overrun {
if (!nofail && pos > rmargin) { // overrun
throw new Overrun(pos - rmargin);
}
if (it == null) { // no items to format. Check against final position.
if (!nofail && pos > fin) throw new Overrun(pos - fin);
return pos;
}
return it.formatN(lmargin, pos, rmargin, fin, can_break, nofail);
}
}
/**
* A Block is a formatting unit containing a list of other items
* to be formatted.
*/
class Block extends Item {
Block parent;
Item first;
Item last;
int indent;
Block(Block parent_, int indent_) {
parent = parent_;
first = last = null;
indent = indent_;
}
/**
* Add a new item to the end of the block. Successive
* StringItems are concatenated together to limit recursion
* depth when formatting.
*/
void add(Item it) {
if (first == null) {
first = it;
} else {
if (it instanceof StringItem && last instanceof StringItem) {
StringItem lasts = (StringItem)last;
lasts.appendString(((StringItem)it).s);
return;
} else {
last.next = it;
}
}
last = it;
}
int formatN(int lmargin, int pos, int rmargin, int fin, boolean can_break,
boolean nofail) throws Overrun {
// "this_fin" is a final-position bound for the formatting of
// the contained list, cranked in from the right margin
// when subsequent items overrun.
int this_fin = rmargin;
// Keep track of whether to send "nofail" down to contained
// list. Don't do this unless forced to.
boolean this_nofail = false;
// Keep track of whether to send "can_break" down to contained
// list. Don't do this unless forced to.
boolean this_break = false;
while (true) {
int next_pos;
try {
next_pos = format(first, pos + indent, pos, rmargin,
this_fin, this_break, this_nofail && this_break);
} catch (Overrun o) {
if (!can_break) throw o;
if (!this_break) { this_break = true; continue; }
if (nofail) { this_nofail = true; continue; }
throw o;
}
try {
return format(next, lmargin, next_pos, rmargin, fin,
can_break, nofail);
} catch (Overrun o) {
if (!can_break) throw o; // no way to fix it
if (next instanceof AllowBreak) throw o; // not our fault
this_break = true;
if (next_pos > this_fin) next_pos = this_fin;
this_fin = next_pos - o.amount;
}
}
}
int sendOutput(Writer o, int lmargin, int pos) throws IOException {
Item it = first;
lmargin = pos+indent;
while (it != null) {
pos = it.sendOutput(o, lmargin, pos);
it = it.next;
}
return pos;
}
void free() {
super.free();
parent = null;
if( first != null) {
first.free();
}
last = null;
}
}
class StringItem extends Item {
String s;
StringItem(String s_) { s = s_; }
int formatN(int lmargin, int pos, int rmargin, int fin, boolean can_break,
boolean nofail) throws Overrun {
return format(next, lmargin, pos + s.length(), rmargin, fin,
can_break, nofail);
}
int sendOutput(Writer o, int lm, int pos) throws IOException {
o.write(s);
return pos + s.length();
}
void appendString(String s) { this.s = this.s + s; }
}
class AllowBreak extends Item
{
int indent;
boolean broken = true;
String alt;
AllowBreak(int n_, String alt_) { indent = n_; alt = alt_; }
int formatN(int lmargin, int pos, int rmargin, int fin, boolean can_break,
boolean nofail) throws Overrun {
if (can_break) { pos = lmargin + indent; broken = true; }
else { pos += alt.length(); broken = false; }
return format(next, lmargin, pos, rmargin, fin, can_break, nofail);
}
int sendOutput(Writer o, int lmargin, int pos)
throws IOException {
if (broken) {
o.write("\r\n");
for (int i = 0; i < lmargin + indent; i++) o.write(" ");
return lmargin + indent;
} else {
o.write(alt);
return pos + alt.length();
}
}
}
class Newline extends AllowBreak
{
Newline(int n_) { super(n_, ""); }
int formatN(int lmargin, int pos, int rmargin, int fin, boolean can_break,
boolean nofail) throws Overrun {
broken = true;
if (!can_break) throw new Overrun(1);
return format(next, lmargin, lmargin + indent, rmargin, fin,
can_break, nofail);
}
int sendOutput(Writer o, int lmargin, int pos)
throws IOException {
o.write("\r\n");
for (int i = 0; i < lmargin + indent; i++) o.write(" ");
return lmargin + indent;
}
}