KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > Diff


1 // Diff -- text file difference utility.
2
// See full docu-comment at beginning of Diff class.
3

4 // $Id: Diff.java 1257 2003-12-11 13:36:55Z leo $
5

6 import java.io.*;
7
8 /** This is the info kept per-file. */
9 class fileInfo {
10
11     static final int MAXLINECOUNT = 20000;
12
13     DataInputStream file; /* File handle that is open for read. */
14     public int maxLine; /* After input done, # lines in file. */
15     node symbol[]; /* The symtab handle of each line. */
16     int other[]; /* Map of line# to line# in other file */
17                                 /* ( -1 means don't-know ). */
18                 /* Allocated AFTER the lines are read. */
19
20     /**
21      * Normal constructor with one filename; file is opened and saved.
22      */

23     fileInfo( String JavaDoc filename ) {
24         symbol = new node [ MAXLINECOUNT+2 ];
25         other = null; // allocated later!
26
try {
27             file = new DataInputStream(
28                 new FileInputStream( filename));
29         } catch (IOException e) {
30               System.err.println("Diff can't read file " +
31                 filename );
32               System.err.println("Error Exception was:" + e );
33               System.exit(1);
34         }
35     }
36     // This is done late, to be same size as # lines in input file.
37
void alloc() {
38         other = new int[symbol.length + 2];
39     }
40 };
41
42 /**
43  * diff Text file difference utility.
44  * ---- Copyright 1987, 1989 by Donald C. Lindsay,
45  * School of Computer Science, Carnegie Mellon University.
46  * Copyright 1982 by Symbionics.
47  * Use without fee is permitted when not for direct commercial
48  * advantage, and when credit to the source is given. Other uses
49  * require specific permission.
50  *
51  * Converted from C to Java by Ian F. Darwin, ian@darwinsys.com, January, 1997.
52  * Copyright 1997, Ian F. Darwin.
53  *
54  * Conversion is NOT FULLY TESTED.
55  *
56  * USAGE: diff oldfile newfile
57  *
58  * This program assumes that "oldfile" and "newfile" are text files.
59  * The program writes to stdout a description of the changes which would
60  * transform "oldfile" into "newfile".
61  *
62  * The printout is in the form of commands, each followed by a block of
63  * text. The text is delimited by the commands, which are:
64  *
65  * DELETE AT n
66  * ..deleted lines
67  *
68  * INSERT BEFORE n
69  * ..inserted lines
70  *
71  * n MOVED TO BEFORE n
72  * ..moved lines
73  *
74  * n CHANGED FROM
75  * ..old lines
76  * CHANGED TO
77  * ..newer lines
78  *
79  * The line numbers all refer to the lines of the oldfile, as they are
80  * numbered before any commands are applied.
81  * The text lines are printed as-is, without indentation or prefixing. The
82  * commands are printed in upper case, with a prefix of ">>>>", so that
83  * they will stand out. Other schemes may be preferred.
84  * Files which contain more than MAXLINECOUNT lines cannot be processed.
85  * This can be fixed by changing "symbol" to a Vector.
86  * The algorithm is taken from Communications of the ACM, Apr78 (21, 4, 264-),
87  * "A Technique for Isolating Differences Between Files."
88  * Ignoring I/O, and ignoring the symbol table, it should take O(N) time.
89  * This implementation takes fixed space, plus O(U) space for the symbol
90  * table (where U is the number of unique lines). Methods exist to change
91  * the fixed space to O(N) space.
92  * Note that this is not the only interesting file-difference algorithm. In
93  * general, different algorithms draw different conclusions about the
94  * changes that have been made to the oldfile. This algorithm is sometimes
95  * "more right", particularly since it does not consider a block move to be
96  * an insertion and a (separate) deletion. However, on some files it will be
97  * "less right". This is a consequence of the fact that files may contain
98  * many identical lines (particularly if they are program source). Each
99  * algorithm resolves the ambiguity in its own way, and the resolution
100  * is never guaranteed to be "right". However, it is often excellent.
101  * This program is intended to be pedagogic. Specifically, this program was
102  * the basis of the Literate Programming column which appeared in the
103  * Communications of the ACM (CACM), in the June 1989 issue (32, 6,
104  * 740-755).
105  * By "pedagogic", I do not mean that the program is gracefully worded, or
106  * that it showcases language features or its algorithm. I also do not mean
107  * that it is highly accessible to beginners, or that it is intended to be
108  * read in full, or in a particular order. Rather, this program is an
109  * example of one professional's style of keeping things organized and
110  * maintainable.
111  * The program would be better if the "print" variables were wrapped into
112  * a struct. In general, grouping related variables in this way improves
113  * documentation, and adds the ability to pass the group in argument lists.
114  * This program is a de-engineered version of a program which uses less
115  * memory and less time. The article points out that the "symbol" arrays
116  * can be implemented as arrays of pointers to arrays, with dynamic
117  * allocation of the subarrays. (In C, macros are very useful for hiding
118  * the two-level accesses.) In Java, a Vector would be used. This allows an
119  * extremely large value for MAXLINECOUNT, without dedicating fixed arrays.
120  * (The "other" array can be allocated after the input phase, when the exact
121  * sizes are known.) The only slow piece of code is the "strcmp" in the tree
122  * descent: it can be speeded up by keeping a hash in the tree node, and
123  * only using "strcmp" when two hashes happen to be equal.
124  *
125  * Change Log
126  * ----------
127  * 1Jan97 Ian F. Darwin: first working rewrite in Java, based entirely on
128  * D.C.Lindsay's reasonable C version.
129  * Changed comments from /***************** to /**, shortened, added
130  * whitespace, used tabs more, etc.
131  * 6jul89 D.C.Lindsay, CMU: fixed portability bug. Thanks, Gregg Wonderly.
132  * Just changed "char ch" to "int ch".
133  * Also added comment about way to improve code.
134  * 10jun89 D.C.Lindsay, CMU: posted version created.
135  * Copyright notice changed to ACM style, and Dept. is now School.
136  * ACM article referenced in docn.
137  * 26sep87 D.C.Lindsay, CMU: publication version created.
138  * Condensed all 1982/83 change log entries.
139  * Removed all command line options, and supporting code. This
140  * simplified the input code (no case reduction etc). It also
141  * simplified the symbol table, which was capable of remembering
142  * offsets into files (instead of strings), and trusting (!) hash
143  * values to be unique.
144  * Removed dynamic allocation of arrays: now fixed static arrays.
145  * Removed speed optimizations in symtab package.
146  * Removed string compression/decompression code.
147  * Recoded to Unix standards from old Lattice/MSDOS standards.
148  * (This affected only the #include's and the IO.)
149  * Some renaming of variables, and rewording of comments.
150  * 1982/83 D.C.Lindsay, Symbionics: created.
151  *
152  * @author Ian F. Darwin, Java version
153  * @version Java version 0.9, 1997
154  * @author D. C. Lindsay, C version (1982-1987)
155  *
156  */

157 public class Diff {
158
159     /** block len > any possible real block len */
160     final int UNREAL=Integer.MAX_VALUE;
161
162     /** Keeps track of information about file1 and file2 */
163     fileInfo oldinfo, newinfo;
164
165     /** blocklen is the info about found blocks. It will be set to 0, except
166      * at the line#s where blocks start in the old file. At these places it
167      * will be set to the # of lines in the block. During printout ,
168      * this # will be reset to -1 if the block is printed as a MOVE block
169      * (because the printout phase will encounter the block twice, but
170      * must only print it once.)
171      * The array declarations are to MAXLINECOUNT+2 so that we can have two
172      * extra lines (pseudolines) at line# 0 and line# MAXLINECOUNT+1
173      * (or less).
174      */

175     int blocklen[];
176
177     /**
178      * main - entry point when used standalone.
179      * NOTE: no routines return error codes or throw any local
180      * exceptions. Instead, any routine may complain
181      * to stderr and then exit with error to the system.
182      */

183     public static void main(String JavaDoc argstrings[])
184     {
185         if ( argstrings.length != 2 ) {
186           System.err.println("Usage: diff oldfile newfile" );
187           System.exit(1);
188         }
189         Diff d = new Diff();
190         d.doDiff(argstrings[0], argstrings[1]);
191         return;
192     }
193
194     /** Construct a Diff object. */
195     Diff() {
196     }
197
198     /** Do one file comparison. Called with both filenames. */
199     public void doDiff(String JavaDoc oldFile, String JavaDoc newFile) {
200         println( ">>>> Difference of file \"" + oldFile +
201             "\" and file \"" + newFile + "\".\n");
202         oldinfo = new fileInfo(oldFile);
203         newinfo = new fileInfo(newFile);
204         /* we don't process until we know both files really do exist. */
205         try {
206             inputscan( oldinfo );
207             inputscan( newinfo );
208         } catch (IOException e) {
209             System.err.println("Read error: " + e);
210         }
211
212         /* Now that we've read all the lines, allocate some arrays.
213          */

214         blocklen = new int[ (oldinfo.maxLine>newinfo.maxLine?
215             oldinfo.maxLine : newinfo.maxLine) + 2 ];
216         oldinfo.alloc();
217         newinfo.alloc();
218
219         /* Now do the work, and print the results. */
220         transform();
221         printout();
222     }
223
224     /**
225      * inputscan Reads the file specified by pinfo.file.
226      * --------- Places the lines of that file in the symbol table.
227      * Sets pinfo.maxLine to the number of lines found.
228      */

229     void inputscan( fileInfo pinfo ) throws IOException
230     {
231          String JavaDoc linebuffer;
232
233          pinfo.maxLine = 0;
234          while ((linebuffer = pinfo.file.readLine()) != null) {
235                storeline( linebuffer, pinfo );
236          }
237     }
238
239     /**
240      * storeline Places line into symbol table.
241      * --------- Expects pinfo.maxLine initted: increments.
242      * Places symbol table handle in pinfo.ymbol.
243      * Expects pinfo is either oldinfo or newinfo.
244      */

245     void storeline( String JavaDoc linebuffer, fileInfo pinfo )
246     {
247          int linenum = ++pinfo.maxLine; /* note, no line zero */
248          if ( linenum > fileInfo.MAXLINECOUNT ) {
249           System.err.println( "MAXLINECOUNT exceeded, must stop." );
250           System.exit(1);
251          }
252          pinfo.symbol[ linenum ] =
253           node.addSymbol( linebuffer, pinfo == oldinfo, linenum );
254     }
255
256     /*
257      * transform
258      * Analyzes the file differences and leaves its findings in
259      * the global arrays oldinfo.other, newinfo.other, and blocklen.
260      * Expects both files in symtab.
261      * Expects valid "maxLine" and "symbol" in oldinfo and newinfo.
262      */

263     void transform()
264     {
265          int oldline, newline;
266          int oldmax = oldinfo.maxLine + 2; /* Count pseudolines at */
267          int newmax = newinfo.maxLine + 2; /* ..front and rear of file */
268
269          for (oldline=0; oldline < oldmax; oldline++ )
270             oldinfo.other[oldline]= -1;
271          for (newline=0; newline < newmax; newline++ )
272             newinfo.other[newline]= -1;
273
274          scanunique(); /* scan for lines used once in both files */
275          scanafter(); /* scan past sure-matches for non-unique blocks */
276          scanbefore(); /* scan backwards from sure-matches */
277          scanblocks(); /* find the fronts and lengths of blocks */
278     }
279
280     /*
281      * scanunique
282      * Scans for lines which are used exactly once in each file.
283      * Expects both files in symtab, and oldinfo and newinfo valid.
284      * The appropriate "other" array entries are set to the line# in
285      * the other file.
286      * Claims pseudo-lines at 0 and XXXinfo.maxLine+1 are unique.
287      */

288     void scanunique()
289     {
290          int oldline, newline;
291          node psymbol;
292
293          for( newline = 1; newline <= newinfo.maxLine; newline++ ) {
294           psymbol = newinfo.symbol[ newline ];
295           if ( psymbol.symbolIsUnique()) { // 1 use in each file
296
oldline = psymbol.linenum;
297                newinfo.other[ newline ] = oldline; // record 1-1 map
298
oldinfo.other[ oldline ] = newline;
299           }
300          }
301          newinfo.other[ 0 ] = 0;
302          oldinfo.other[ 0 ] = 0;
303          newinfo.other[ newinfo.maxLine + 1 ] = oldinfo.maxLine + 1;
304          oldinfo.other[ oldinfo.maxLine + 1 ] = newinfo.maxLine + 1;
305     }
306
307     /*
308      * scanafter
309      * Expects both files in symtab, and oldinfo and newinfo valid.
310      * Expects the "other" arrays contain positive #s to indicate
311      * lines that are unique in both files.
312      * For each such pair of places, scans past in each file.
313      * Contiguous groups of lines that match non-uniquely are
314      * taken to be good-enough matches, and so marked in "other".
315      * Assumes each other[0] is 0.
316      */

317     void scanafter()
318     {
319          int oldline, newline;
320
321          for( newline = 0; newline <= newinfo.maxLine; newline++ ) {
322           oldline = newinfo.other[ newline ];
323           if ( oldline >= 0 ) { /* is unique in old & new */
324                for(;;) { /* scan after there in both files */
325                 if ( ++oldline > oldinfo.maxLine ) break;
326                 if ( oldinfo.other[ oldline ] >= 0 ) break;
327                 if ( ++newline > newinfo.maxLine ) break;
328                 if ( newinfo.other[ newline ] >= 0 ) break;
329
330                 /* oldline & newline exist, and
331                 aren't already matched */

332
333                 if ( newinfo.symbol[ newline ] !=
334                 oldinfo.symbol[ oldline ] ) break; // not same
335

336                 newinfo.other[newline] = oldline; // record a match
337
oldinfo.other[oldline] = newline;
338                }
339           }
340          }
341     }
342
343     /**
344      * scanbefore
345      * As scanafter, except scans towards file fronts.
346      * Assumes the off-end lines have been marked as a match.
347      */

348     void scanbefore()
349     {
350          int oldline, newline;
351
352          for( newline = newinfo.maxLine + 1; newline > 0; newline-- ) {
353           oldline = newinfo.other[ newline ];
354           if ( oldline >= 0 ) { /* unique in each */
355                for(;;) {
356                 if ( --oldline <= 0 ) break;
357                 if ( oldinfo.other[ oldline ] >= 0 ) break;
358                 if ( --newline <= 0 ) break;
359                 if ( newinfo.other[ newline ] >= 0 ) break;
360      
361                 /* oldline and newline exist,
362                 and aren't marked yet */

363
364                 if ( newinfo.symbol[ newline ] !=
365                 oldinfo.symbol[ oldline ] ) break; // not same
366

367                 newinfo.other[newline] = oldline; // record a match
368
oldinfo.other[oldline] = newline;
369                }
370           }
371          }
372     }
373
374     /**
375      * scanblocks - Finds the beginnings and lengths of blocks of matches.
376      * Sets the blocklen array (see definition).
377      * Expects oldinfo valid.
378      */

379     void scanblocks()
380     {
381          int oldline, newline;
382          int oldfront = 0; // line# of front of a block in old, or 0
383
int newlast = -1; // newline's value during prev. iteration
384

385          for( oldline = 1; oldline <= oldinfo.maxLine; oldline++ )
386                blocklen[ oldline ] = 0;
387          blocklen[ oldinfo.maxLine + 1 ] = UNREAL; // starts a mythical blk
388

389          for( oldline = 1; oldline <= oldinfo.maxLine; oldline++ ) {
390           newline = oldinfo.other[ oldline ];
391           if ( newline < 0 ) oldfront = 0; /* no match: not in block */
392           else{ /* match. */
393                if ( oldfront == 0 ) oldfront = oldline;
394                if ( newline != (newlast+1)) oldfront = oldline;
395                ++blocklen[ oldfront ];
396           }
397           newlast = newline;
398          }
399     }
400
401     /* The following are global to printout's subsidiary routines */
402     // enum{ idle, delete, insert, movenew, moveold,
403
// same, change } printstatus;
404
public static final int
405         idle = 0, delete = 1, insert = 2, movenew = 3, moveold = 4,
406         same = 5, change = 6;
407     int printstatus;
408     boolean anyprinted;
409     int printoldline, printnewline; // line numbers in old & new file
410

411     /**
412      * printout - Prints summary to stdout.
413      * Expects all data structures have been filled out.
414      */

415     void printout()
416     {
417          printstatus = idle;
418          anyprinted = false;
419          for( printoldline = printnewline = 1; ; ) {
420           if ( printoldline > oldinfo.maxLine ) { newconsume(); break;}
421           if ( printnewline > newinfo.maxLine ) { oldconsume(); break;}
422           if ( newinfo.other[ printnewline ] < 0 ) {
423                if ( oldinfo.other[ printoldline ] < 0 )
424                 showchange();
425                else
426                 showinsert();
427           }
428           else if ( oldinfo.other[ printoldline ] < 0 )
429             showdelete();
430           else if ( blocklen[ printoldline ] < 0 )
431             skipold();
432           else if ( oldinfo.other[ printoldline ] == printnewline )
433             showsame();
434           else
435             showmove();
436          }
437          if ( anyprinted == true ) println( ">>>> End of differences." );
438          else println( ">>>> Files are identical." );
439     }
440
441     /*
442      * newconsume Part of printout. Have run out of old file.
443      * Print the rest of the new file, as inserts and/or moves.
444      */

445     void newconsume()
446     {
447          for(;;) {
448           if ( printnewline > newinfo.maxLine )
449             break; /* end of file */
450           if ( newinfo.other[ printnewline ] < 0 ) showinsert();
451           else showmove();
452          }
453     }
454
455     /**
456      * oldconsume Part of printout. Have run out of new file.
457      * Process the rest of the old file, printing any
458      * parts which were deletes or moves.
459      */

460     void oldconsume()
461     {
462          for(;;) {
463           if ( printoldline > oldinfo.maxLine )
464             break; /* end of file */
465           printnewline = oldinfo.other[ printoldline ];
466           if ( printnewline < 0 ) showdelete();
467           else if ( blocklen[ printoldline ] < 0 ) skipold();
468           else showmove();
469          }
470     }
471
472     /**
473      * showdelete Part of printout.
474      * Expects printoldline is at a deletion.
475      */

476     void showdelete()
477     {
478         if ( printstatus != delete )
479             println( ">>>> DELETE AT " + printoldline);
480         printstatus = delete;
481         oldinfo.symbol[ printoldline ].showSymbol();
482         anyprinted = true;
483         printoldline++;
484     }
485
486     /*
487      * showinsert Part of printout.
488      * Expects printnewline is at an insertion.
489      */

490     void showinsert()
491     {
492          if ( printstatus == change ) println( ">>>> CHANGED TO" );
493          else if ( printstatus != insert )
494           println( ">>>> INSERT BEFORE " + printoldline );
495          printstatus = insert;
496          newinfo.symbol[ printnewline ].showSymbol();
497          anyprinted = true;
498          printnewline++;
499     }
500
501     /**
502      * showchange Part of printout.
503      * Expects printnewline is an insertion.
504      * Expects printoldline is a deletion.
505      */

506     void showchange()
507     {
508          if ( printstatus != change )
509           println( ">>>> " + printoldline + " CHANGED FROM");
510          printstatus = change;
511          oldinfo.symbol[ printoldline ].showSymbol();
512          anyprinted = true;
513          printoldline++;
514     }
515
516     /**
517      * skipold Part of printout.
518      * Expects printoldline at start of an old block that has
519      * already been announced as a move.
520      * Skips over the old block.
521      */

522     void skipold()
523     {
524          printstatus = idle;
525          for(;;) {
526           if ( ++printoldline > oldinfo.maxLine )
527             break; /* end of file */
528           if ( oldinfo.other[ printoldline ] < 0 )
529             break; /* end of block */
530           if ( blocklen[ printoldline ]!=0)
531             break; /* start of another */
532          }
533     }
534
535     /**
536      * skipnew Part of printout.
537      * Expects printnewline is at start of a new block that has
538      * already been announced as a move.
539      * Skips over the new block.
540      */

541     void skipnew()
542     {
543          int oldline;
544          printstatus = idle;
545          for(;;) {
546           if ( ++printnewline > newinfo.maxLine )
547             break; /* end of file */
548           oldline = newinfo.other[ printnewline ];
549           if ( oldline < 0 )
550             break; /* end of block */
551           if ( blocklen[ oldline ] != 0)
552             break; /* start of another */
553          }
554     }
555
556     /**
557      * showsame Part of printout.
558      * Expects printnewline and printoldline at start of
559      * two blocks that aren't to be displayed.
560      */

561     void showsame()
562     {
563          int count;
564          printstatus = idle;
565          if ( newinfo.other[ printnewline ] != printoldline ) {
566           System.err.println("BUG IN LINE REFERENCING");
567           System.exit(1);
568          }
569          count = blocklen[ printoldline ];
570          printoldline += count;
571          printnewline += count;
572     }
573
574     /**
575      * showmove Part of printout.
576      * Expects printoldline, printnewline at start of
577      * two different blocks ( a move was done).
578      */

579     void showmove()
580     {
581          int oldblock = blocklen[ printoldline ];
582          int newother = newinfo.other[ printnewline ];
583          int newblock = blocklen[ newother ];
584
585          if ( newblock < 0 ) skipnew(); // already printed.
586
else if ( oldblock >= newblock ) { // assume new's blk moved.
587
blocklen[newother] = -1; // stamp block as "printed".
588
println( ">>>> " + newother +
589             " THRU " + (newother + newblock - 1) +
590             " MOVED TO BEFORE " + printoldline );
591           for( ; newblock > 0; newblock--, printnewline++ )
592                newinfo.symbol[ printnewline ].showSymbol();
593           anyprinted = true;
594           printstatus = idle;
595
596          } else /* assume old's block moved */
597           skipold(); /* target line# not known, display later */
598     }
599
600     /** Convenience wrapper for println */
601     public void println(String JavaDoc s) {
602         System.out.println(s);
603     }
604 }; // end of main class!
605

606 /**
607  * Class "node". The symbol table routines in this class all
608  * understand the symbol table format, which is a binary tree.
609  * The methods are: addSymbol, symbolIsUnique, showSymbol.
610  */

611 class node{ /* the tree is made up of these nodes */
612     node pleft, pright;
613     int linenum;
614
615     static final int freshnode = 0,
616     oldonce = 1, newonce = 2, bothonce = 3, other = 4;
617
618     int /* enum linestates */ linestate;
619     String JavaDoc line;
620
621     static node panchor = null; /* symtab is a tree hung from this */
622
623     /**
624      * Construct a new symbol table node and fill in its fields.
625      * @param string A line of the text file
626      */

627     node( String JavaDoc pline)
628     {
629          pleft = pright = null;
630          linestate = freshnode;
631          /* linenum field is not always valid */
632          line = pline;
633     }
634
635     /**
636      * matchsymbol Searches tree for a match to the line.
637      * @param String pline, a line of text
638      * If node's linestate == freshnode, then created the node.
639      */

640     static node matchsymbol( String JavaDoc pline )
641     {
642          int comparison;
643          node pnode = panchor;
644          if ( panchor == null ) return panchor = new node( pline);
645          for(;;) {
646           comparison = pnode.line.compareTo(pline);
647           if ( comparison == 0 ) return pnode; /* found */
648
649           if ( comparison < 0 ) {
650                if ( pnode.pleft == null ) {
651                 pnode.pleft = new node( pline);
652                 return pnode.pleft;
653                }
654                pnode = pnode.pleft;
655           }
656           if ( comparison > 0 ) {
657                if ( pnode.pright == null ) {
658                 pnode.pright = new node( pline);
659                 return pnode.pright;
660                }
661                pnode = pnode.pright;
662           }
663          }
664          /* NOTE: There are return stmts, so control does not get here. */
665     }
666
667     /**
668      * addSymbol(String pline) - Saves line into the symbol table.
669      * Returns a handle to the symtab entry for that unique line.
670      * If inoldfile nonzero, then linenum is remembered.
671      */

672     static node addSymbol( String JavaDoc pline, boolean inoldfile, int linenum )
673     {
674         node pnode;
675         pnode = matchsymbol( pline ); /* find the node in the tree */
676         if ( pnode.linestate == freshnode ) {
677             pnode.linestate = inoldfile ? oldonce : newonce;
678         } else {
679           if (( pnode.linestate == oldonce && !inoldfile ) ||
680               ( pnode.linestate == newonce && inoldfile ))
681                pnode.linestate = bothonce;
682           else pnode.linestate = other;
683         }
684         if (inoldfile) pnode.linenum = linenum;
685         return pnode;
686     }
687
688     /**
689      * symbolIsUnique Arg is a ptr previously returned by addSymbol.
690      * -------------- Returns true if the line was added to the
691      * symbol table exactly once with inoldfile true,
692      * and exactly once with inoldfile false.
693      */

694     boolean symbolIsUnique()
695     {
696         return (linestate == bothonce );
697     }
698
699     /**
700      * showSymbol Prints the line to stdout.
701      */

702     void showSymbol()
703     {
704         System.out.println(line);
705     }
706 }
707
Popular Tags