001: package org.incava.analysis;
002:
003: import java.io.*;
004: import java.util.*;
005: import net.sourceforge.pmd.ast.Token;
006: import org.incava.lang.StringExt;
007: import org.incava.util.ANSI;
008:
009: /**
010: * Reports errors in multiple lines, displaying the contextual code, and
011: * denoting the code to which a violation applies.
012: */
013: public class ContextReport extends Report {
014: /**
015: * The number of spaces a tab is equivalent to.
016: */
017: public static int tabWidth = 4;
018:
019: /**
020: * The end-of-line character/sequence for this OS.
021: */
022: private final static String EOLN = System
023: .getProperty("line.separator");
024:
025: /**
026: * The reader associated with the source code, which is used for reproducing
027: * the code associated with a violation.
028: */
029: private Reader sourceReader;
030:
031: /**
032: * Whether the file name has been written yet.
033: */
034: private boolean wroteFileName = false;
035:
036: /**
037: * The contents, separated by new lines, which are included at the end of
038: * each string.
039: */
040: private String[] contents;
041:
042: /**
043: * Creates a context report for the given writer.
044: *
045: * @param writer The writer associated with this report.
046: */
047: public ContextReport(Writer writer) {
048: super (writer);
049: }
050:
051: /**
052: * Creates a context report for the given output stream.
053: *
054: * @param os The output stream associated with this report.
055: */
056: public ContextReport(OutputStream os) {
057: super (os);
058: }
059:
060: /**
061: * Creates a context report for the given writer, and a string source.
062: *
063: * @param writer The writer associated with this report.
064: * @param source The source code to which this report applies.
065: */
066: public ContextReport(Writer writer, String source) {
067: super (writer, source);
068: }
069:
070: /**
071: * Creates a context report for the given writer, and a file source.
072: *
073: * @param writer The writer associated with this report.
074: * @param file The file, containing source code, to which this report applies.
075: */
076: public ContextReport(Writer writer, File file) {
077: super (writer, file);
078: }
079:
080: /**
081: * Creates a context report for the given output stream, and string source.
082: *
083: * @param os The output stream associated with this report.
084: * @param source The source code to which this report applies.
085: */
086: public ContextReport(OutputStream os, String source) {
087: super (os, source);
088: }
089:
090: /**
091: * Creates a context report for the given output stream, and file.
092: *
093: * @param os The output stream associated with this report.
094: * @param file The file, containing source code, to which this report applies.
095: */
096: public ContextReport(OutputStream os, File file) {
097: super (os, file);
098: }
099:
100: /**
101: * Associates the given file with the list of violations, including that are
102: * adding to this report later, i.e., prior to <code>flush</code>.
103: *
104: * @param file The file associated with the set of violations.
105: */
106: public void reset(File file) {
107: super .reset(file);
108: wroteFileName = false;
109: contents = null;
110:
111: try {
112: sourceReader = new FileReader(file);
113: } catch (IOException ioe) {
114: tr.Ace.log("error reading file: " + file);
115: }
116: }
117:
118: /**
119: * Associates the given string source with the list of violations, including
120: * that are adding to this report later, i.e., prior to <code>flush</code>.
121: *
122: * @param source The source code associated with the set of violations.
123: */
124: public void reset(String source) {
125: super .reset(source);
126: wroteFileName = false;
127: contents = null;
128:
129: sourceReader = new StringReader(source);
130: }
131:
132: /**
133: * Returns a string representing the given violation, consistent with the
134: * format of the Report subclass.
135: *
136: * @param violation The violation to represent as a string.
137: */
138: protected String toString(Violation violation) {
139: StringBuffer buf = new StringBuffer();
140: if (!wroteFileName) {
141: buf.append("In " + ANSI.BOLD + ANSI.REVERSE + fileName
142: + ANSI.RESET + ":" + EOLN + EOLN);
143: wroteFileName = true;
144: }
145:
146: if (contents == null) {
147: tr.Ace.log("opening reader for " + sourceReader);
148:
149: try {
150: List cont = new ArrayList();
151: BufferedReader br = new BufferedReader(sourceReader);
152:
153: String line = br.readLine();
154: while (line != null) {
155: cont.add(line);
156: line = br.readLine();
157: }
158:
159: contents = (String[]) cont.toArray(new String[0]);
160:
161: for (int i = 0; i < contents.length; ++i) {
162: tr.Ace.log("contents[" + i + "]: " + contents[i]);
163: }
164: } catch (IOException ioe) {
165: tr.Ace.log("error reading source: " + ioe);
166: }
167: }
168:
169: tr.Ace.log("writing: " + violation);
170:
171: int beginLine = violation.getBeginLine() - 1;
172: int endLine = violation.getEndLine() - 1;
173: int beginColumn = violation.getBeginColumn() - 1;
174: int endColumn = violation.getEndColumn() - 1;
175:
176: if (beginLine == endLine) {
177: writeLine(buf, beginLine);
178: underline(buf, beginLine, beginColumn, endColumn);
179: } else {
180: markToEndOfLine(buf, beginLine, beginColumn);
181: for (int lnum = beginLine; lnum <= endLine; ++lnum) {
182: writeLine(buf, lnum);
183: }
184: markToStartPosition(buf, endLine, endColumn);
185: }
186:
187: buf.append("*** " + violation.getMessage() + EOLN);
188: buf.append(EOLN);
189:
190: return buf.toString();
191: }
192:
193: /**
194: * Adds indentation to the buffer, replacing spacing and tabs. Replaces tabs
195: * with <code>tabWidth</code> occurrences of <code>ch</code>.
196: *
197: * @param buf The buffer to be written to.
198: * @param line The current line number.
199: * @param column The column to indent to.
200: * @param ch The character with which to replace spaces and tabs.
201: */
202: protected void indent(StringBuffer buf, int line, int column,
203: char ch) {
204: buf.append(" ");
205:
206: // move it over for the column, replacing tabs with spaces
207: buf.append(StringExt.repeat(ch, column));
208: }
209:
210: /**
211: * Marks the given line with leading spaces to the column position
212: * (inclusive), and from there marking to the end of the line with
213: * "<---...".
214: *
215: * @param buf The buffer to be written to.
216: * @param line The current line number.
217: * @param column The column to mark to/from.
218: */
219: protected void markToEndOfLine(StringBuffer buf, int line,
220: int column) {
221: indent(buf, line, column, ' ');
222:
223: int len = contents[line].length();
224:
225: buf.append('<');
226: for (int i = column + 1; i < len; ++i) {
227: buf.append('-');
228: }
229: buf.append(EOLN);
230: }
231:
232: /**
233: * Marks the given line with "...--->" leading to the column position
234: * (inclusive).
235: *
236: * @param buf The buffer to be written to.
237: * @param line The current line number.
238: * @param column The column to mark to.
239: */
240: protected void markToStartPosition(StringBuffer buf, int line,
241: int column) {
242: indent(buf, line, column, '-');
243:
244: buf.append('>');
245: buf.append(EOLN);
246: }
247:
248: /**
249: * Underlines ("<--...-->") from <code>beginColumn</code> to
250: * <code>endColumn</code> in the given line. If the columns are equal, a
251: * single caret is shown.
252: *
253: * @param buf The buffer to be written to.
254: * @param line The current line number.
255: * @param beginColumn The column to mark from.
256: * @param endColumn The column to mark to.
257: */
258: protected void underline(StringBuffer buf, int line,
259: int beginColumn, int endColumn) {
260: indent(buf, line, beginColumn, ' ');
261:
262: if (beginColumn == endColumn) {
263: buf.append('^');
264: } else {
265: buf.append('<');
266: for (int i = beginColumn + 1; i < endColumn; ++i) {
267: buf.append('-');
268: }
269: buf.append('>');
270: }
271: buf.append(EOLN);
272: }
273:
274: /**
275: * Writes the given line, adding the line number, right-aligned. Appends the
276: * end-of-line character/sequence.
277: *
278: * @param buf The buffer to be written to.
279: * @param line The current line number.
280: */
281: protected void writeLine(StringBuffer buf, int line) {
282: StringBuffer lnBuf = new StringBuffer("" + (1 + line));
283: while (lnBuf.length() < 6) {
284: lnBuf.insert(0, ' ');
285: }
286:
287: buf.append(lnBuf);
288: buf.append(". ");
289: buf.append(contents[line]);
290: buf.append(EOLN);
291: }
292:
293: }
|