001: // You can redistribute this software and/or modify it under the terms of
002: // the Infozone Software License version 2 published by the Infozone Group
003: // (http://www.infozone-group.org).
004: //
005: // Copyright (C) @year@ by The Infozone Group. All rights reserved.
006: //
007: // $Id: JavaCodeOutput.java,v 1.1 2002/05/10 08:59:12 per_nyfelt Exp $
008:
009: package org.infozone.tools.janalyzer;
010:
011: import koala.dynamicjava.tree.*;
012:
013: import java.util.List;
014: import java.util.ListIterator;
015: import java.io.File;
016: import java.io.FileReader;
017: import java.io.BufferedReader;
018: import java.io.FileNotFoundException;
019: import java.io.IOException;
020: import java.io.PrintStream;
021: import java.io.FileOutputStream;
022:
023: import java.util.Vector;
024: import java.util.Arrays;
025: import java.util.Collections;
026:
027: import org.apache.regexp.RE;
028: import org.apache.regexp.RESyntaxException;
029:
030: /**
031: * This class prints the lines of code and the affiliated comments of the output from
032: * the JavaCodeAnalyzer class.
033: * It makes line wrapping with splitting levels, correct indentation and comment creation. .
034: * This all is done after the
035: * <a href=http://xml.apache.org/source.html>Apache Source Conventions</a>
036: * from SUN and therefore the ASF..
037: *
038: * <pre>
039: *
040: * TODO:
041: * - trailling comments
042: * - more documentation
043: * - doc comment cration for classes, constructors, fields, methods
044: * BUGS
045: * - comment items in "" are extracted to real comments
046: * </pre>
047: *
048: * @version $Revision: 1.1 $ $Date: 2002/05/10 08:59:12 $
049: * @author <a href="http://www.softwarebuero.de">SMB</a>
050: * @see org.infozone.janalyzer.JavaCodeAnalyzer
051: */
052: public final class JavaCodeOutput extends java.lang.Object {
053:
054: //
055: // constants
056: //
057:
058: /** The maximum line length JCC 4.1 */
059: private int LINE_LENGTH;
060: /** Constants for line wrapping nice level */
061: private final static int LOW_SPLIT_LEVEL = 10;
062: private final static int MIDDLE_SPLIT_LEVEL = 25;
063: private final static int HIGH_SPLIT_LEVEL = 50;
064: private final static int OBLIGATION_SPLIT_LEVEL = 99;
065: /** One indentation */
066: private final static String ONE_INDENT = " ";
067: /** Indentation for automatically line wrapping */
068: private final static String WRAP_INDENT = " ";
069: /** documentation comment start */
070: // for correct highlighting :-)=)
071: private final static String DOC_COMMENT_START = "/" + "**";
072: /** block comment start */
073: // for correct highlighting :-)=)
074: private final static String BLOCK_COMMENT_START = "/" + "*";
075: /** line comment start */
076: // for correct highlighting :-)=)
077: private final static String LINE_COMMENT_START = "/" + "/";
078: /** block and documentation comment line start */
079: private final static String BLOCK_COMMENT_LINE = "*";
080: /** block and documentation comment end */
081: private final static String BLOCK_COMMENT_END = "*" + "/";
082: /** lines to start a created doc comment */
083: private final static String[] DOC_COMMENT_START_LINES = { DOC_COMMENT_START };
084: /** lines to end a created doc comment */
085: private final static String[] DOC_COMMENT_END_LINES = { BLOCK_COMMENT_END };
086:
087: /**
088: * A split character for priority expression splitting in line wrapping.
089: * All existing split characters must be masked by double split character.
090: * After this character follows the splitting nice level as integer.
091: * - 1 means better no splitting
092: * - 9 or greater means lets split
093: * Example: §1§ or §25§
094: *
095: * @see #println(String aLine)
096: */
097: private final static String SPLIT_CHARACTER = "§";
098: /** Should lineNumbers be printed? */
099: private final static boolean PRINT_LINE_NUMBERS = true;
100:
101: //
102: // data
103: //
104:
105: /**
106: * The nice level for splitting on wrapping lines.
107: * @see #SPLIT_CHARACTER
108: * @see #println(String aLine)
109: */
110: private int splitLevel = LOW_SPLIT_LEVEL + 1;
111: /**
112: * @see #println(String aLine)
113: */
114: private boolean wrapIndentAdded = false;
115: /** The complete indentationString. */
116: private String indent = "";
117:
118: /** The start for printing a comment.It should be the end of the previous node. */
119: private int commentStart = 0;
120: /** The endline for printing a comment.It should be the line of the actual node. */
121: private int commentEnd = 0;
122:
123: /** to output the resulting lines */
124: private PrintStream outStream = new PrintStream(System.out);
125:
126: /**
127: * The container for textlines of the original file.
128: */
129: Vector origLines = new Vector();
130:
131: public JavaCodeOutput(File fileIn, String filenameOut,
132: String lineLength) {
133: try {
134: BufferedReader br = new BufferedReader(new FileReader(
135: fileIn));
136: while (br.ready()) {
137: origLines.add(br.readLine());
138: }
139: br.close();
140: } catch (FileNotFoundException e) {
141: printEx("File " + fileIn.getName() + " not found!", e);
142: } catch (IOException e) {
143: printEx(" ", e);
144: }
145: try {
146: if (filenameOut != null) {
147: File file = new File(filenameOut);
148: FileOutputStream fos = new FileOutputStream(file);
149: outStream = new PrintStream(fos);
150: }
151: } catch (FileNotFoundException e) {
152: printEx("File " + filenameOut + " not found!", e);
153: } catch (IOException e) {
154: printEx(" ", e);
155: }
156: try {
157: if (lineLength != null) {
158: LINE_LENGTH = Integer.parseInt(lineLength);
159: } else {
160: LINE_LENGTH = 80;
161: }
162: } catch (NumberFormatException e) {
163: printEx("Line length " + lineLength
164: + " is not a valid number!", e);
165: }
166: }
167:
168: //
169: // indentation section
170: //
171:
172: /**
173: * Increase the indentation String by oneIndent.
174: */
175: public void increaseIndent() {
176: indent += ONE_INDENT;
177: }
178:
179: /**
180: * Decrease the indentation String by the length of the OE_INDENT String.
181: */
182: protected void decreaseIndent() {
183: if (indent.length() > 2) {
184: indent = indent.substring(0, indent.length()
185: - ONE_INDENT.length());
186: }
187: }
188:
189: /**
190: * Add to the indentation String the WRAP_INDENT,
191: * because on automatic line wrapping a other indentation string must used.
192: * Only the method println use it.
193: */
194: private void addWrapIndent() {
195: if (!wrapIndentAdded) {
196: wrapIndentAdded = true;
197: indent += WRAP_INDENT;
198: }
199: }
200:
201: /**
202: * Delete the indentation String by the length of the WRAP_INDENT String
203: * if it added before.
204: */
205: private void deleteWrapIndent() {
206: if (wrapIndentAdded) {
207: wrapIndentAdded = false;
208: indent = indent.substring(0, indent.length()
209: - WRAP_INDENT.length());
210: }
211: }
212:
213: //
214: // line numbering for debugging only
215: //
216:
217: /**
218: * Formatting of the line number on the begin of each outline if it desired.
219: *
220: * The format should be replaced java.text.MessageFormat or someone else.
221: * @return The number of the line formatted of three characters at the moment.
222: */
223: private String getLineNumber() {
224: return "";
225: }
226:
227: //
228: // line wrapping section
229: //
230:
231: /**
232: * If the split level String exits in the original source it must be masked
233: */
234: public String mask(String aText) {
235: try {
236: RE regexp = new RE(SPLIT_CHARACTER);
237: aText = regexp.subst(aText, SPLIT_CHARACTER
238: + SPLIT_CHARACTER);
239: } catch (RESyntaxException e) {
240: printEx("false regexp in mask", e);
241: }
242: return aText;
243: }
244:
245: /**
246: * Splitting levels were used by the JavaCodeAnalyzer to set marker into the lines of
247: * code, were line wrapping is recommended.
248: * Miscellaneous split level should give a priority were line wrapping is good or bad.
249: * A high level means a good place to wrap the line.
250: *
251: * @return The splitting level with splitting characters.
252: */
253: public String getSplitLevel() {
254: return SPLIT_CHARACTER + splitLevel;
255: }
256:
257: /**
258: * @return The low splitting level with splitting characters.
259: */
260: public String getLowSplitLevel() {
261: return SPLIT_CHARACTER + LOW_SPLIT_LEVEL;
262: }
263:
264: /**
265: * @return The middle splitting level with splitting characters.
266: */
267: public String getMiddleSplitLevel() {
268: return SPLIT_CHARACTER + MIDDLE_SPLIT_LEVEL;
269: }
270:
271: /**
272: * @return The high splitting level with splitting characters.
273: */
274: public String getHighSplitLevel() {
275: return SPLIT_CHARACTER + HIGH_SPLIT_LEVEL;
276: }
277:
278: /**
279: * If it present in a line and the line is longer then LINE_LENGTH
280: * then line must wrapped on this place.
281: * Useful for correct indentation of e.g. ternary expressions.
282: *
283: * @return The obligation splitting level with splitting characters.
284: */
285: public String getObligateSplitLevel() {
286: return SPLIT_CHARACTER + OBLIGATION_SPLIT_LEVEL;
287: }
288:
289: /**
290: * Increase the split level and returns it.
291: */
292: public String getNextSplitLevel() {
293: return SPLIT_CHARACTER + splitLevel++;
294: }
295:
296: public void resetSplitLevel() {
297: splitLevel = LOW_SPLIT_LEVEL + 1;
298: }
299:
300: //
301: // line wrapping
302: //
303:
304: /**
305: * This method print out the line. It wrapping lines and so on.
306: * Code Convention Chapter 4.
307: *
308: * @param aLine
309: */
310: public void println(String aLine) {
311: String output = cleanOutputLine(aLine);
312: output = getLineNumber() + indent + output;
313: //
314: if (output.length() <= LINE_LENGTH) {
315: // line ok print it out
316: outStream.println(output);
317: } else {
318: // line to long
319: // found split characters in the original line and break it there
320: // printLog("println: Line too long.");
321: // find last presence of HIGH_SPLIT_LEVEL
322: // then split_level greater then LOW_SPLIT_LEVEL
323: // then LOW_SPLIT_LEVEL
324: // obligation split level at first
325: // split on getObligateSplitLevel not on SPLIT_CHARACTER+getObligateSplitLevel!
326: int index = 0;
327: // find first presence
328: if ((index = aLine.indexOf(getObligateSplitLevel())) != -1
329: && (index == 0 || index > 0
330: && !(aLine.charAt(index - 1) == SPLIT_CHARACTER
331: .charAt(0)))) {
332: // aSplitter found
333: println(aLine.substring(0, index));
334: aLine = aLine.substring(index
335: + getObligateSplitLevel().length());
336: printDB("ObligateSplitLevel found on line in if "
337: + getLineNumber() + " index is " + index
338: + " aLine is " + aLine);
339: addWrapIndent();
340: // find next presences
341: while ((index = aLine.indexOf(getObligateSplitLevel())) != -1
342: && (index == 0 || index > 0
343: && !(aLine.charAt(index - 1) == SPLIT_CHARACTER
344: .charAt(0)))) {
345: // aSplitter found
346: println(aLine.substring(0, index));
347: aLine = aLine.substring(index
348: + getObligateSplitLevel().length());
349: printDB("ObligateSplitLevel found on line in while "
350: + getLineNumber()
351: + " index is "
352: + index
353: + " aLine is " + aLine);
354: }
355: // the last part ever
356: println(aLine);
357: deleteWrapIndent();
358: // after this for all parts a expliziet println is invoked therefore return
359: return;
360: }
361: //
362: if ((output = breakThisLine(aLine, getHighSplitLevel())) != null) {
363: if ((output = breakThisLine(output,
364: getMiddleSplitLevel())) != null) {
365: if ((output = breakThisLine(output,
366: getLowSplitLevel())) != null) {
367: outStream.println(getLineNumber() + indent
368: + cleanOutputLine(output));
369: deleteWrapIndent();
370: }
371: }
372: }
373: }
374: }
375:
376: private String breakThisLine(String aLine, String aSplitter) {
377: int index = LINE_LENGTH + 8 * aSplitter.length();
378: String output = null;
379: // string splitter found and no split_character before
380: while ((index = aLine.lastIndexOf(aSplitter, index)) != -1
381: && (index == 0 || index > 0
382: && !(aLine.charAt(index - 1) == SPLIT_CHARACTER
383: .charAt(0)))) {
384: // aSplitter found
385: output = getLineNumber() + indent
386: + cleanOutputLine(aLine.substring(0, index));
387: if (output.length() <= LINE_LENGTH) {
388: // string fit's the size
389: outStream.println(output);
390: // line contains only a splitter at end of line
391: if (index + aSplitter.length() < aLine.length()) {
392: // print the rest
393: addWrapIndent();
394: println(aLine.substring(index + aSplitter.length())
395: .trim());
396: deleteWrapIndent();
397: }
398: return null;
399: } else {
400: // line too long
401: // search next Splitter
402: --index;
403: }
404: }
405: // no suitable Splitter found
406: // now test the other
407: printLog("Line not splitable by Splitter " + aSplitter);
408: //
409: return aLine;
410: }
411:
412: /**
413: * This method removes all splitting chracters and other stuff from
414: * the output line.
415: */
416: private String cleanOutputLine(String aLine) {
417: if (aLine.indexOf(SPLIT_CHARACTER) != -1) {
418: try {
419: // first delete all unmasked combinations
420:
421: RE regexp = new RE("[^" + SPLIT_CHARACTER + "]("
422: + SPLIT_CHARACTER + "\\d\\d)");
423: RE regexptwo;
424: while (regexp.match(aLine)) {
425: regexptwo = new RE(regexp.getParen(1));
426: aLine = regexptwo.subst(aLine, "");
427: }
428:
429: // subsstitute at begin of line
430: regexp = new RE("^(" + SPLIT_CHARACTER + "\\d\\d)");
431: aLine = regexp.subst(aLine, "");
432:
433: // substitute masked to original
434: regexp = new RE(SPLIT_CHARACTER + SPLIT_CHARACTER);
435: aLine = regexp.subst(aLine, SPLIT_CHARACTER);
436: } catch (Exception e) {
437: e.printStackTrace();
438: }
439: }
440: return aLine;
441: }
442:
443: //
444: // comment section
445: //
446:
447: public void setCommentStart(int start) {
448: commentStart = start;
449: }
450:
451: public void setCommentEnd(int end) {
452: commentEnd = end;
453: }
454:
455: /**
456: * Invokes the method printOrigComment with the parameters
457: * commentStart, commentEnd.
458: */
459: public void printComment() {
460: printOrigComment(commentStart, commentEnd, 0);
461: }
462:
463: /**
464: * Insert existing comments between Node.getBeginLine To lastline..
465: * This methods could be used to create empty comments.
466: */
467: public void printImportComment(Node aNode) {
468: printComment();
469: }
470:
471: public void printPackageComment(Node aNode) {
472: printComment();
473: }
474:
475: public void printClassComment(Node aNode) {
476: printOrigComment(commentStart, commentEnd, 2);
477: }
478:
479: public void printInterfaceComment(Node aNode) {
480: printOrigComment(commentStart, commentEnd, 2);
481: }
482:
483: public void printConstructorComment(Node aNode) {
484: printOrigComment(commentStart, commentEnd, 2);
485: }
486:
487: public void printMethodComment(MethodDeclaration method) {
488: printOrigComment(commentStart, commentEnd, 2);
489: }
490:
491: public void printFieldComment(Node aNode) {
492: printComment();
493: }
494:
495: public void printVariableComment(Node aNode) {
496: printComment();
497: }
498:
499: /**
500: * Prints the comment line out.
501: * There are a println for source code and a printComment for comment.
502: */
503: private void printComment(String aCommentLine) {
504: outStream.println(getLineNumber() + indent + aCommentLine);
505: }
506:
507: //
508: // comment output section
509: //
510:
511: /**
512: * Performes a arraycopy of the existing Comments between start and end in vector origLines.
513: * @see #printComment(String[] lines)
514: *
515: */
516: private boolean printOrigComment(int startLine, int endLine,
517: int insertLineCount) {
518:
519: // trailling comments not handled actually
520: if (startLine >= endLine) {
521: // no comment and no empty line found
522: return false;
523: }
524: Vector lines = new Vector();
525:
526: boolean removeEmptyLines = false;
527: // boolean linesInserted = (insertLineCount == 0);
528:
529: for (int i = 1; i < (commentEnd - commentStart); i++) {
530: String line = (String) origLines.elementAt(commentEnd - i
531: - 1);
532:
533: if (line.trim().length() != 0) {
534: removeEmptyLines = false;
535: lines.add(line);
536: } else {
537: // first empty line before following statement found
538: // insert empty lines
539: if (insertLineCount > 0) {
540: while (insertLineCount > 0) {
541: lines.add("");
542: insertLineCount--;
543: }
544: removeEmptyLines = true;
545: }
546: if (!removeEmptyLines) {
547: lines.add("");
548: }
549: }
550: }
551:
552: while (insertLineCount > 0) {
553: lines.add("");
554: insertLineCount--;
555: }
556:
557: Collections.reverse(lines);
558:
559: String[] strings = new String[lines.size()];
560: try {
561: // copy only relevant lines from the origLines vector
562: System.arraycopy(lines.toArray(), 0, strings, 0, lines
563: .size());
564: } catch (Exception e) {
565: e.printStackTrace();
566: }
567: return printComment(strings);
568: }
569:
570: /**
571: * Perform a arraycopy of all elements in the Vector to a String[].
572: * @see #printComment(String[] lines)
573: */
574: private boolean printComment(Vector someLines) {
575: String[] lines = new String[someLines.size()];
576: try {
577: // copy only relevant lines from the origLines vector
578: System.arraycopy(someLines.toArray(), 0, lines, 0,
579: someLines.size());
580: } catch (Exception e) {
581: e.printStackTrace();
582: }
583: return printComment(lines);
584: }
585:
586: private boolean printComment(String[] lines) {
587: // actual line in Vector
588: String actLine;
589: // loop in documentation block comment
590: boolean isDocComment = false;
591: // loop in block comment
592: boolean isBlockComment = false;
593: //
594: boolean commentExist = false;
595:
596: // get lines from vector
597: for (int i = 0; i < lines.length; i++) {
598: // single line documentation comment
599: /** ... */
600: actLine = lines[i].trim();
601: if (actLine == null) {
602: actLine = "";
603: }
604: // //
605: if (actLine.startsWith(LINE_COMMENT_START)) {
606: // single line comment with // found
607: if (!isBlockComment) {
608: // is not inside a block comment
609: printComment(actLine);
610: continue;
611: }
612: }
613: // /** ... */
614: if ((actLine.startsWith(DOC_COMMENT_START) || actLine
615: .startsWith(BLOCK_COMMENT_START))
616: && actLine.endsWith(BLOCK_COMMENT_END)) {
617: // single line comment found
618: printComment(actLine);
619: commentExist = true;
620: continue;
621: }
622: // ... /* .... */ ....
623: // more detailed handling of this comment
624: if (actLine.indexOf(BLOCK_COMMENT_START) != -1
625: && actLine.indexOf(BLOCK_COMMENT_END) != -1) {
626: printComment(actLine.substring(actLine
627: .indexOf(BLOCK_COMMENT_START), actLine
628: .indexOf(BLOCK_COMMENT_END)
629: + BLOCK_COMMENT_END.length()));
630:
631: continue;
632: }
633:
634: // */
635: if (actLine.startsWith(BLOCK_COMMENT_END)) {
636: // block comment ended with */
637: isDocComment = isBlockComment = false;
638: printComment(" " + BLOCK_COMMENT_END);
639: continue;
640: }
641: // block comment ended with ..... */
642: if (actLine.indexOf(BLOCK_COMMENT_END) != -1) {
643: isDocComment = isBlockComment = false;
644:
645: int index = actLine.indexOf(BLOCK_COMMENT_END);
646: printComment(printBlockCommentLine(actLine.substring(0,
647: index)));
648: // actline contains more than the doc comment start
649: printComment(" " + BLOCK_COMMENT_END);
650: // }
651: continue;
652: }
653: // block doc comment started /** ... */
654: if (actLine.startsWith(DOC_COMMENT_START)) {
655: isDocComment = true;
656: commentExist = true;
657:
658: printComment(DOC_COMMENT_START);
659: // actline contains more than the doc comment start
660: if (actLine.length() > DOC_COMMENT_START.length()) {
661: printComment(printBlockCommentLine(actLine
662: .substring(DOC_COMMENT_START.length() - 1)));
663: }
664: continue;
665: }
666: if (actLine.startsWith(BLOCK_COMMENT_START)) {
667: // block doc comment started
668: //System.out.printComment("printComment block doc comment found");
669: isBlockComment = true;
670:
671: printComment(BLOCK_COMMENT_START);
672: // actline contains more than the doc comment start
673: if (actLine.length() > BLOCK_COMMENT_START.length()) {
674: printComment(printBlockCommentLine(actLine
675: .substring(BLOCK_COMMENT_START.length() - 1)));
676: }
677: continue;
678: }
679: // no start or end string found
680: if (isDocComment) {
681: // in a blockComment, must start with asterisk
682: printComment(printBlockCommentLine(actLine));
683: continue;
684: }
685: if (isBlockComment) {
686: // in a blockComment
687: // print as is
688: outStream.println(lines[i]);
689: continue;
690: }
691:
692: // trailing comment ..... // ......
693: if (actLine.indexOf(LINE_COMMENT_START) != -1) {
694: // && !isInQuotes(actLine, LINE_COMMENT_START) ) {
695: if (!isBlockComment) {
696: int index = actLine.indexOf(LINE_COMMENT_START);
697: printComment(actLine.substring(index));
698: }
699: continue;
700: }
701: // empty actline is only a newline
702: if (actLine.length() < 1) {
703: printComment("");
704: }
705: }
706: // is isBlock or DocComment true, block isn't determined
707:
708: // no start or end string in the loop found
709: if (isDocComment || isBlockComment) {
710: printComment(" " + BLOCK_COMMENT_END);
711: printLog("Blockcomment not determinated!");
712: }
713: return commentExist;
714: }
715:
716: /**
717: * Useful for printComment to encapsulate some functionality.
718: * If the line doesn't start with * it would be precede
719: *
720: * @see #printComment(int, int).
721: */
722: private String printBlockCommentLine(String aLine) {
723: if (aLine.startsWith(BLOCK_COMMENT_LINE)) {
724: return " " + aLine;
725: } else {
726: return " " + BLOCK_COMMENT_LINE + " " + aLine;
727: }
728: }
729:
730: /**
731: * This method logs warnings on code violations.
732: * @param aLine
733: */
734: private void printLog(String aLine) {
735: }
736:
737: /**
738: * This method print debug messages.
739: * @param aLine
740: */
741: private void printDB(String aLine) {
742: }
743:
744: /**
745: * This method print debug messages.
746: * @param aLine
747: */
748: private void printEx(String aLine, Exception e) {
749: System.err.println(getClass() + ": " + e);
750: }
751: }
|