001: /**
002: * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
003: */package net.sourceforge.pmd.renderers;
004:
005: import net.sourceforge.pmd.IRuleViolation;
006: import net.sourceforge.pmd.PMD;
007: import net.sourceforge.pmd.Report;
008:
009: import java.io.BufferedReader;
010: import java.io.File;
011: import java.io.FileNotFoundException;
012: import java.io.FileReader;
013: import java.io.IOException;
014: import java.io.Reader;
015: import java.io.Writer;
016: import java.util.Iterator;
017: import java.util.Map;
018:
019: /**
020: * <p>A console renderer with optional color support under *nix systems.</p>
021: * <p/>
022: * <pre>
023: * * file: ./src/gilot/Test.java
024: * src: Test.java:12
025: * rule: AtLeastOneConstructor
026: * msg: Each class should declare at least one constructor
027: * code: public class Test
028: * <p/>
029: * * file: ./src/gilot/log/format/LogInterpreter.java
030: * src: LogInterpreter.java:317
031: * rule: AvoidDuplicateLiterals
032: * msg: The same String literal appears 4 times in this file; the first occurrence is on line 317
033: * code: logger.error( "missing attribute 'app_arg' in rule '" + ((Element)element.getParent()).getAttributeValue( "name" ) + "'" );
034: * <p/>
035: * src: LogInterpreter.java:317
036: * rule: AvoidDuplicateLiterals
037: * msg: The same String literal appears 5 times in this file; the first occurrence is on line 317
038: * code: logger.error( "missing attribute 'app_arg' in rule '" + ((Element)element.getParent()).getAttributeValue( "name" ) + "'" );
039: * <p/>
040: * * warnings: 3
041: * <p/>
042: * </pre>
043: * <p/>
044: * <p>Colorization is turned on by supplying -D<b>pmd.color</b> - any value other than
045: * '0' or 'false', enables color - including an empty value (''). <b>Nota Bene:</b>
046: * colorization is atm only supported under *nix terminals accepting ansi escape
047: * sequences, such as xterm, rxvt et cetera.</p>
048: */
049: public class PapariTextRenderer extends AbstractRenderer {
050: /**
051: * Directory from where java was invoked.
052: */
053: private String pwd;
054:
055: private String yellowBold = "";
056: private String whiteBold = "";
057: private String redBold = "";
058: private String cyan = "";
059: private String green = "";
060:
061: private String colorReset = "";
062:
063: /**
064: * Enables colors on *nix systems - not windows. Color support depends
065: * on the pmd.color property, which should be set with the -D option
066: * during execution - a set value other than 'false' or '0' enables color.
067: * <p/>
068: * btw, is it possible to do this on windows (ie; console colors)?
069: */
070: private void initializeColorsIfSupported() {
071: if (System.getProperty("pmd.color") != null
072: && !(System.getProperty("pmd.color").equals("0") || System
073: .getProperty("pmd.color").equals("false"))) {
074: this .yellowBold = "\u001B[1;33m";
075: this .whiteBold = "\u001B[1;37m";
076: this .redBold = "\u001B[1;31m";
077: this .green = "\u001B[0;32m";
078: this .cyan = "\u001B[0;36m";
079:
080: this .colorReset = "\u001B[0m";
081: }
082: }
083:
084: public void render(Writer writer, Report report) throws IOException {
085: StringBuffer buf = new StringBuffer(PMD.EOL);
086: initializeColorsIfSupported();
087: String lastFile = null;
088: int numberOfErrors = 0;
089: int numberOfWarnings = 0;
090:
091: for (Iterator<IRuleViolation> i = report.iterator(); i
092: .hasNext();) {
093: buf.setLength(0);
094: numberOfWarnings++;
095: IRuleViolation rv = i.next();
096: if (!rv.getFilename().equals(lastFile)) {
097: lastFile = rv.getFilename();
098: buf.append(this .yellowBold + "*" + this .colorReset
099: + " file: " + this .whiteBold
100: + this .getRelativePath(lastFile)
101: + this .colorReset + PMD.EOL);
102: }
103: buf.append(this .green
104: + " src: "
105: + this .cyan
106: + lastFile.substring(lastFile
107: .lastIndexOf(File.separator) + 1)
108: + this .colorReset
109: + ":"
110: + this .cyan
111: + rv.getBeginLine()
112: + (rv.getEndLine() == -1 ? "" : ":"
113: + rv.getEndLine()) + this .colorReset
114: + PMD.EOL);
115: buf.append(this .green + " rule: " + this .colorReset
116: + rv.getRule().getName() + PMD.EOL);
117: buf.append(this .green + " msg: " + this .colorReset
118: + rv.getDescription() + PMD.EOL);
119: buf.append(this .green + " code: " + this .colorReset
120: + this .getLine(lastFile, rv.getBeginLine())
121: + PMD.EOL + PMD.EOL);
122: writer.write(buf.toString());
123: }
124: writer.write(PMD.EOL + PMD.EOL);
125: writer.write("Summary:" + PMD.EOL + PMD.EOL);
126: Map<String, Integer> summary = report.getCountSummary();
127: for (Map.Entry<String, Integer> entry : summary.entrySet()) {
128: buf.setLength(0);
129: String key = entry.getKey();
130: buf.append(key).append(" : ").append(entry.getValue())
131: .append(PMD.EOL);
132: writer.write(buf.toString());
133: }
134:
135: for (Iterator<Report.ProcessingError> i = report.errors(); i
136: .hasNext();) {
137: buf.setLength(0);
138: numberOfErrors++;
139: Report.ProcessingError error = i.next();
140: if (error.getFile().equals(lastFile)) {
141: lastFile = error.getFile();
142: buf.append(this .redBold + "*" + this .colorReset
143: + " file: " + this .whiteBold
144: + this .getRelativePath(lastFile)
145: + this .colorReset + PMD.EOL);
146: }
147: buf.append(this .green + " err: " + this .cyan
148: + error.getMsg() + this .colorReset + PMD.EOL
149: + PMD.EOL);
150: writer.write(buf.toString());
151: }
152:
153: // adding error message count, if any
154: if (numberOfErrors > 0) {
155: writer.write(this .redBold + "*" + this .colorReset
156: + " errors: " + this .whiteBold + numberOfWarnings
157: + this .colorReset + PMD.EOL);
158: }
159: writer.write(this .yellowBold + "*" + this .colorReset
160: + " warnings: " + this .whiteBold + numberOfWarnings
161: + this .colorReset + PMD.EOL);
162: }
163:
164: /**
165: * Retrieves the requested line from the specified file.
166: *
167: * @param sourceFile the java or cpp source file
168: * @param line line number to extract
169: * @return a trimmed line of source code
170: */
171: private String getLine(String sourceFile, int line) {
172: String code = null;
173: BufferedReader br = null;
174: try {
175: br = new BufferedReader(getReader(sourceFile));
176: for (int i = 0; line > i; i++) {
177: code = br.readLine().trim();
178: }
179: } catch (IOException ioErr) {
180: ioErr.printStackTrace();
181: } finally {
182: if (br != null) {
183: try {
184: br.close();
185: } catch (IOException ioErr) {
186: ioErr.printStackTrace();
187: }
188: }
189: }
190: return code;
191: }
192:
193: protected Reader getReader(String sourceFile)
194: throws FileNotFoundException {
195: return new FileReader(new File(sourceFile));
196: }
197:
198: /**
199: * Attempts to determine the relative path to the file. If relative path cannot be found,
200: * the original path is returnedi, ie - the current path for the supplied file.
201: *
202: * @param fileName well, the file with its original path.
203: * @return the relative path to the file
204: */
205: private String getRelativePath(String fileName) {
206: String relativePath;
207:
208: // check if working directory need to be assigned
209: if (pwd == null) {
210: try {
211: this .pwd = new File(".").getCanonicalPath();
212: } catch (IOException ioErr) {
213: // to avoid further error
214: this .pwd = "";
215: }
216: }
217:
218: // make sure that strings match before doing any substring-ing
219: if (fileName.indexOf(this .pwd) == 0) {
220: relativePath = "." + fileName.substring(this .pwd.length());
221:
222: // remove current dir occuring twice - occurs if . was supplied as path
223: if (relativePath.startsWith("." + File.separator + "."
224: + File.separator)) {
225: relativePath = relativePath.substring(2);
226: }
227: } else {
228: // this happens when pmd's supplied argument deviates from the pwd 'branch' (god knows this terminolgy - i hope i make some sense).
229: // for instance, if supplied=/usr/lots/of/src and pwd=/usr/lots/of/shared/source
230: // TODO: a fix to get relative path?
231: relativePath = fileName;
232: }
233:
234: return relativePath;
235: }
236: }
|