KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > polyglot > util > CodeWriter


1 /**
2  * CodeWriter -- Andrew C. Myers, April 2001
3  * For use in Cornell University Computer Science 412/413
4  */

5
6 package polyglot.util;
7
8 import java.io.OutputStream JavaDoc;
9 import java.io.OutputStreamWriter JavaDoc;
10 import java.io.Writer JavaDoc;
11 import java.io.IOException JavaDoc;
12 import java.util.Vector JavaDoc;
13
14 /**
15  * A <code>CodeWriter</code> is a pretty-printing engine.
16  * It formats structured text onto an output stream <code>o</code> in the
17  * minimum number of lines, while keeping the width of the output
18  * within <code>width</code> characters if possible.
19  */

20 public class CodeWriter
21 {
22     /**
23      * Create a CodeWriter object with output stream <code>o</code>
24      * and width <code>width_</code>.
25      * <esc>requires o != null</esc>
26      * <esc>requires width_ > 0</esc>
27      */

28     public CodeWriter(OutputStream JavaDoc o, int width_) {
29         output = new OutputStreamWriter JavaDoc(o);
30         width = width_;
31         current = input = new BlockItem(null, 0);
32     }
33
34     /**
35      * Create a CodeWriter object with output <code>w</code> and
36      * width <code>width_</code>.
37      * <esc>requires w != null</esc>
38      * <esc>requires width_ > 0</esc>
39      */

40     public CodeWriter(Writer JavaDoc w, int width_) {
41         output = w;
42         width = width_;
43         current = input = new BlockItem(null, 0);
44     }
45         
46     /** Print the string <code>s</code> verbatim on the output stream.
47      * <esc>requires s != null</esc>
48      */

49     public void write(String JavaDoc s) {
50        if (s.length() > 0)
51           current.add(new StringItem(s));
52     }
53
54     /** Force a newline with no added indentation. */
55     public void newline()
56     {
57        newline(0);
58     }
59
60     /**
61      * Start a new block with a relative indentation of <code>n</code>
62      * characters.
63      * <br>
64      * A block is a formatting unit. The formatting algorithm will try
65      * to put the whole block in one line unless
66      * <ul>
67      * <li>there is a <code>newline</code> item in the block.</li>
68      * <li>the block cannot fit in one line.</li>
69      * </ul>
70      * If either of the two conditions is satisfied, the
71      * formatting algorithm will break the block into lines: every
72      * <code>allowBreak</code> will cause a line change, the first line
73      * is printed at the current cursor position <code>pos</code>,
74      * all the following lines are printed at the position
75      * <code>pos+n</code>.
76      *
77      * @param n the number of characters increased on indentation (relative
78      * to the current position) for all lines in the block.
79      *
80      * <esc>requires n >= 0</esc>
81      */

82     public void begin(int n) {
83         BlockItem b = new BlockItem(current, n);
84         current.add(b);
85         current = b;
86     }
87         
88     /**
89      * Terminate the most recent outstanding <code>begin</code>.
90      */

91     public void end() {
92         current = current.parent;
93         //@ assert current != null
94
// if (current == null) throw new RuntimeException();
95
}
96
97     /**
98      * Allow a newline. Indentation will be preserved.
99      * If no newline is inserted, a single space character is output instead.
100      *
101      * @param n the amount of increase in indentation if
102      * the newline is inserted.
103      *
104      * <esc>requires n >= 0</esc>
105      */

106     public void allowBreak(int n) {
107         current.add(new AllowBreak(n, " "));
108     }
109
110     /**
111      * Allow a newline. Indentation will be preserved.
112      *
113      * @param n the amount of increase in indentation if
114      * the newline is inserted.
115      * @param alt if no newline is inserted, the string <code>alt</code> is
116      * output instead.
117      *
118      * <esc>requires n >= 0</esc>
119      * <esc>requires alt != null</esc>
120      */

121     public void allowBreak(int n, String JavaDoc alt) {
122         current.add(new AllowBreak(n, alt));
123     }
124
125     /**
126      * Force a newline. Indentation will be preserved. This method
127      * should be used sparingly; usually a call to <code>allowBreak</code> is
128      * preferable because forcing a newline also causes all breaks
129      * in containing blocks to be broken.
130      *
131      * <esc>requires n >= 0</esc>
132      */

133     public void newline(int n) {
134         current.add(new Newline(n));
135     }
136
137     /**
138      * Send out the current batch of text to be formatted. All
139      * outstanding <code>begin</code>'s are closed and the current
140      * indentation level is reset to 0. Returns true if formatting
141      * was completely successful (the margins were obeyed).
142      */

143     public boolean flush() throws IOException JavaDoc {
144     boolean success = true;
145     try {
146         Item.format(input,0, 0, width, width, true, true);
147     } catch (Overrun o) { success = false; }
148         input.sendOutput(output, 0, 0);
149         output.flush();
150         input.free();
151         current = input = new BlockItem(null, 0);
152     return success;
153     }
154     /**
155      * Return a readable representation of all the structured input
156      * given to the CodeWriter since the last flush.
157      */

158     public String JavaDoc toString() {
159     return input.toString();
160     }
161
162     BlockItem input;
163     BlockItem current;
164
165     Writer JavaDoc output;
166     int width;
167     public static final boolean debug = false;
168     public static final boolean precompute = false;
169
170     //@ invariant current != null
171
//@ invariant input != null
172
//@ invariant output != null
173
//@ invariant width > 0
174
}
175
176 /**
177  * An <code>Overrun</code> represents a formatting that failed because the right
178  * margin was exceeded by at least <code>amount</code> chars.
179  */

180 class Overrun extends Exception JavaDoc
181 {
182     int amount;
183
184     private Overrun() { }
185     private /*@ non_null */ static Overrun overrun = new Overrun();
186
187     //@ ensures \result != null
188
static Overrun overrun(int amount) {
189     if (CodeWriter.debug) System.err.println("-- Overrun: " + amount);
190         overrun.amount = amount;
191         return overrun;
192     }
193 }
194
195 /**
196  * An <code>Item</code> is a piece of input handed to the formatter. It
197  * contains a reference to a possibly empty list of items that follow it.
198  */

199 abstract class Item
200 {
201     Item next;
202
203     protected Item() { next = null; }
204
205     /**
206      * Try to format this item and subsequent items. The current cursor
207      * position is <code>pos</code>, left and right margins are as
208      * specified. Returns the final position, which must be <code>&lt;
209      * fin</code>. If breaks may be broken, <code>can_break</code> is
210      * set. Return the new cursor position (which may overrun rmargin,
211      * fin, or both, and set any contained breaks accordingly. (It is
212      * important that formatN not necessarily convert overruns in its
213      * final position into exceptions. This allows the calling routine
214      * to distinguish between 'internal' overruns and ones that it can
215      * tack on a conservative estimate of how much formatting the rest
216      * of the list will make the overrun go up by. Also, it simplifies
217      * the coding of formatN.)
218      *
219      * Requires: rmargin &lt; lmargin, pos &lt;= rmargin.
220      * <esc>requires lmargin < rmargin</esc>
221      * <xxx>requires pos <= rmargin</xxx>
222      * <esc>requires lmargin >= 0</esc>
223      */

224
225     abstract int formatN(int lmargin, int pos, int rmargin, int fin,
226                  boolean can_break, boolean nofail) throws Overrun;
227     /**
228      * Send the output associated with this item to <code>o</code>, using the
229      * current break settings.
230      * <esc>requires o != null</esc>
231      * <esc>requires lmargin >= 0</esc>
232      * <esc>requires pos >= 0</esc>
233      */

234     abstract int sendOutput(Writer JavaDoc o, int lmargin, int pos)
235       throws IOException JavaDoc;
236
237     /** Free references to any other items. */
238     void free() {
239         if( next != null) {
240             next.free();
241             next = null;
242         }
243     }
244
245     /**
246      * Try to format a whole sequence of items in the manner of formatN.
247      * The initial position may be an overrun (this is the only way
248      * that overruns are checked!) <code>it</code> may be also null,
249      * signifying an empty list.
250      *
251      * <esc>requires lmargin < rmargin</esc>
252      * <xxx>requires pos <= rmargin</xxx>
253      * <esc>requires lmargin >= 0</esc>
254      */

255     static int format(Item it, int lmargin, int pos, int rmargin, int fin,
256                   boolean can_break, boolean nofail) throws Overrun {
257     if (CodeWriter.debug) {
258         System.err.println("Format: " + it + "\n lmargin = " +
259         lmargin + " pos = " + pos + " fin = " + fin +
260         (can_break ? " can break" : " no breaks") +
261         (nofail ? " [nofail]" : ""));
262     }
263     if (!nofail && pos > rmargin) { // overrun
264
if (CodeWriter.precompute) {
265         if (CodeWriter.debug) {
266             System.err.println("MinWidth of " + it + " = " +
267                     getMinWidth(it));
268             System.err.println("MinPosWidth of " + it + " = " +
269                     getMinPosWidth(it));
270             System.err.println("MinIndent of " + it + " = " +
271                     getMinIndent(it));
272         }
273         int amount = pos + getMinPosWidth(it) - rmargin;
274         int amount1 = lmargin + getMinWidth(it) - rmargin;
275         if (amount1 > amount) amount = amount1;
276         int amount2 = lmargin + getMinIndent(it) - fin;
277         if (amount2 > amount) amount = amount2;
278         throw Overrun.overrun(amount);
279         } else {
280         throw Overrun.overrun(pos - rmargin);
281         }
282     }
283     if (it == null) { // no items to format. Check against final position.
284
if (!nofail && pos > fin) throw Overrun.overrun(pos - fin);
285         return pos;
286     }
287     return it.formatN(lmargin, pos, rmargin, fin, can_break, nofail);
288     }
289   
290 /*
291     The following fields keep track of the tightest formatting that is
292     possible with an item and its following items, if all breaks are
293     broken. Formatting is measured relative to both "lmargin" and to
294     "pos".
295
296    lmargin
297    | pos
298    | |
299    | xxxxx
300    xxxxxxxx
301    xxxxxx
302    <------>
303    min_width (at least min_pos_width)
304      <--->
305      min_pos_width
306    <---->
307    min_indent (at most min_width)
308 */

309
310     //@ invariant min_pos_width <= min_width
311
//@ invariant min_indent <= min_width
312

313     /** Minimum lmargin-rhs width. */
314     int min_width = -1;
315     
316     /** Minimum lmargin-final offset */
317     int min_indent = -1;
318
319     /** Minimum pos-rhs width (i.e., min width up to first break) */
320     int min_pos_width = -1;
321
322     /** Are there any breaks in this items and following items? */
323     boolean contains_brks;
324     boolean cb_init = false;
325
326     static int max(int i, int j) {
327     if (i > j) return i;
328     return j;
329     }
330
331     static int getMinWidth(Item it) {
332     if (it == null) return 0;
333     if (it.min_width >= 0) return it.min_width;
334     int p1 = it.selfMinWidth();
335     int p2 = it.selfMinIndent();
336     int p3 = getMinPosWidth(it.next) + p2;
337     int p4 = getMinWidth(it.next);
338     return (it.min_width = max(max(p1, p3), p4));
339     }
340
341     static int getMinPosWidth(Item it) {
342     if (it == null) return 0;
343     if (it.min_pos_width >= 0) return it.min_pos_width;
344     int p1 = it.selfMinPosWidth();
345     if (it.next == null ||
346         it.selfContainsBreaks()) return p1;
347     return p1 + getMinPosWidth(it.next);
348     }
349
350     static int getMinIndent(Item it) {
351     if (it == null) return 0;
352     if (it.min_indent >= 0) return it.min_indent;
353     int p1 = it.selfMinIndent();
354     if (it.next == null) return p1;
355     if (containsBreaks(it.next))
356         return getMinIndent(it.next);
357     return p1 + getMinPosWidth(it.next);
358     }
359
360     static boolean containsBreaks(Item it) {
361     if (it == null) return false;
362     if (it.cb_init) return it.contains_brks;
363     if (it.selfContainsBreaks()) {
364         it.contains_brks = true;
365         it.cb_init = true;
366         return true;
367     }
368     if (it.next == null) return false;
369     it.contains_brks = containsBreaks(it.next);
370     it.cb_init = true;
371     return it.contains_brks;
372     }
373
374     public String JavaDoc summarize(String JavaDoc s) {
375     if (s.length() <= 60) return s;
376     return s.substring(0, 57) + "...";
377     }
378
379     public String JavaDoc toString() {
380     if (next == null) return summarize(selfToString());
381     return summarize(selfToString() + next.toString());
382     }
383
384     abstract String JavaDoc selfToString();
385     abstract int selfMinIndent();
386     abstract int selfMinWidth();
387     abstract int selfMinPosWidth();
388     abstract boolean selfContainsBreaks();
389 }
390
391 /**
392  * A BlockItem is a formatting unit containing a list of other items
393  * to be formatted.
394  */

395 class BlockItem extends Item {
396     BlockItem parent;
397     Item first;
398     Item last;
399     int indent;
400
401     //@ invariant indent >= 0
402

403     BlockItem(BlockItem parent_, int indent_) {
404         parent = parent_;
405         first = last = null;
406         indent = indent_;
407     }
408
409     /**
410      * Add a new item to the end of the block. Successive
411      * StringItems are concatenated together to limit recursion
412      * depth when formatting.
413      */

414     void add(Item it) {
415         if (first == null) {
416         first = it;
417     } else {
418         if (it instanceof StringItem && last instanceof StringItem) {
419         StringItem lasts = (StringItem)last;
420         lasts.appendString(((StringItem)it).s);
421         return;
422         } else {
423         last.next = it;
424         }
425     }
426         last = it;
427     }
428
429 int formatN(int lmargin, int pos, int rmargin, int fin, boolean can_break,
430             boolean nofail) throws Overrun {
431     if (CodeWriter.debug)
432             System.err.println("BlockItem format " + this + "\n lmargin = " +
433                 lmargin + " pos = " + pos + " fin = " + fin +
434         (can_break ? " can break" : " no breaks") +
435         (nofail ? " [nofail]" : ""));
436         // "this_fin" is a final-position bound for the formatting of
437
// the contained list, cranked in from the right margin
438
// when subsequent items overrun.
439
int this_fin = rmargin - getMinPosWidth(next);
440         // Keep track of whether to send "nofail" down to contained
441
// list. Don't do this unless forced to.
442
boolean this_nofail = false;
443         // Keep track of whether to send "can_break" down to contained
444
// list. Don't do this unless forced to.
445
boolean this_break = false;
446     while (true) {
447         int next_pos;
448         try {
449           next_pos = format(first, pos + indent, pos, rmargin,
450                   this_fin, this_break, this_nofail && this_break);
451         } catch (Overrun o) {
452         if (!can_break) throw o;
453         if (!this_break) { this_break = true; continue; }
454         if (this_nofail) throw new Error JavaDoc("Failed with this_nofail");
455         if (nofail) { this_nofail = true; continue; }
456         throw o;
457         }
458         try {
459         return format(next, lmargin, next_pos, rmargin, fin,
460                   can_break, nofail);
461         } catch (Overrun o) {
462         if (!can_break) throw o; // no way to fix it
463
if (next instanceof AllowBreak) throw o; // not our fault
464
this_break = true;
465         if (nofail) throw new Error JavaDoc("Failed with nofail");
466         if (next_pos > this_fin) next_pos = this_fin;
467         this_fin = next_pos - o.amount;
468         if (CodeWriter.debug)
469             System.err.println(" Trying block again with fin = " +
470                        this_fin);
471         }
472     }
473     }
474
475     int sendOutput(Writer JavaDoc o, int lmargin, int pos) throws IOException JavaDoc {
476         Item it = first;
477         lmargin = pos+indent;
478         while (it != null) {
479             pos = it.sendOutput(o, lmargin, pos);
480             it = it.next;
481         }
482         return pos;
483     }
484
485     void free() {
486         super.free();
487   
488         parent = null;
489         if( first != null) {
490             first.free();
491         }
492         last = null;
493     }
494
495     int selfMinWidth() {
496     return getMinWidth(first);
497     }
498
499     int selfMinPosWidth() {
500     return getMinPosWidth(first);
501     }
502
503     int selfMinIndent() {
504     return getMinIndent(first);
505     }
506
507     boolean self_contains_brks;
508     boolean self_contains_brks_init = false;
509
510     boolean selfContainsBreaks() {
511     if (self_contains_brks_init) {
512         return self_contains_brks;
513     } else {
514         return (self_contains_brks = containsBreaks(first));
515     }
516     }
517     String JavaDoc selfToString() {
518     if (indent == 0) return "[" + first + "]";
519     else return "[" + indent + first + "]";
520     }
521 }
522
523 class StringItem extends Item {
524     String JavaDoc s;
525     //@ invariant s != null
526

527     //@ requires s_ != null
528
StringItem(String JavaDoc s_) { s = s_; }
529         
530     int formatN(int lmargin, int pos, int rmargin, int fin, boolean can_break,
531         boolean nofail) throws Overrun {
532     return format(next, lmargin, pos + s.length(), rmargin, fin,
533               can_break, nofail);
534     }
535     int sendOutput(Writer JavaDoc o, int lm, int pos) throws IOException JavaDoc {
536         o.write(s);
537         return pos + s.length();
538     }
539     void appendString(String JavaDoc s) { this.s = this.s + s; }
540     boolean selfContainsBreaks() { return false; }
541     int selfMinIndent() { return s.length(); }
542     int selfMinWidth() { return s.length(); }
543     int selfMinPosWidth() { return s.length(); }
544     String JavaDoc selfToString() {
545     java.io.StringWriter JavaDoc sw = new java.io.StringWriter JavaDoc();
546     for (int i = 0; i < s.length(); i++) {
547         char c = s.charAt(i);
548         if (c == ' ') sw.write("\\ ");
549         else sw.write(c);
550     }
551     return sw.toString();
552     }
553 }
554
555 class AllowBreak extends Item
556 {
557     int indent;
558     boolean broken = true;
559     String JavaDoc alt;
560         
561     //@ invariant indent >= 0
562
//@ invariant alt != null
563

564     //@ requires n_ >= 0
565
//@ requires alt_ != null
566
AllowBreak(int n_, String JavaDoc alt_) { indent = n_; alt = alt_; }
567         
568     int formatN(int lmargin, int pos, int rmargin, int fin, boolean can_break,
569             boolean nofail) throws Overrun {
570         if (can_break) { pos = lmargin + indent; broken = true; }
571         else { pos += alt.length(); broken = false; }
572     return format(next, lmargin, pos, rmargin, fin, can_break, nofail);
573     }
574         
575     int sendOutput(Writer JavaDoc o, int lmargin, int pos)
576         throws IOException JavaDoc {
577         if (broken) {
578             o.write("\r\n");
579             for (int i = 0; i < lmargin + indent; i++) o.write(" ");
580             return lmargin + indent;
581         } else {
582         o.write(alt);
583             return pos + alt.length();
584         }
585     }
586     int selfMinIndent() { return indent; }
587     int selfMinPosWidth() { return 0; }
588     int selfMinWidth() { return indent; }
589     boolean selfContainsBreaks() { return true; }
590
591     String JavaDoc selfToString() {
592     if (indent == 0) return " ";
593     else return "^" + indent; }
594 }
595
596 class Newline extends AllowBreak
597 {
598     //@ requires n_ >= 0
599
Newline(int n_) { super(n_, ""); }
600         
601     int formatN(int lmargin, int pos, int rmargin, int fin, boolean can_break,
602         boolean nofail) throws Overrun {
603         broken = true;
604     if (!can_break) throw Overrun.overrun(1);
605     return format(next, lmargin, lmargin + indent, rmargin, fin,
606             can_break, nofail);
607     }
608         
609     int sendOutput(Writer JavaDoc o, int lmargin, int pos)
610             throws IOException JavaDoc {
611         o.write("\r\n");
612         for (int i = 0; i < lmargin + indent; i++) o.write(" ");
613         return lmargin + indent;
614     }
615 }
616
Popular Tags