1 /**
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package net.sourceforge.pmd.renderers;
5
6 import net.sourceforge.pmd.IRuleViolation;
7 import net.sourceforge.pmd.PMD;
8 import net.sourceforge.pmd.Report;
9
10 import java.io.BufferedReader;
11 import java.io.File;
12 import java.io.FileNotFoundException;
13 import java.io.FileReader;
14 import java.io.IOException;
15 import java.io.Reader;
16 import java.io.Writer;
17 import java.util.Iterator;
18 import java.util.Map;
19
20 /**
21 * <p>A console renderer with optional color support under *nix systems.</p>
22 * <p/>
23 * <pre>
24 * * file: ./src/gilot/Test.java
25 * src: Test.java:12
26 * rule: AtLeastOneConstructor
27 * msg: Each class should declare at least one constructor
28 * code: public class Test
29 * <p/>
30 * * file: ./src/gilot/log/format/LogInterpreter.java
31 * src: LogInterpreter.java:317
32 * rule: AvoidDuplicateLiterals
33 * msg: The same String literal appears 4 times in this file; the first occurrence is on line 317
34 * code: logger.error( "missing attribute 'app_arg' in rule '" + ((Element)element.getParent()).getAttributeValue( "name" ) + "'" );
35 * <p/>
36 * src: LogInterpreter.java:317
37 * rule: AvoidDuplicateLiterals
38 * msg: The same String literal appears 5 times in this file; the first occurrence is on line 317
39 * code: logger.error( "missing attribute 'app_arg' in rule '" + ((Element)element.getParent()).getAttributeValue( "name" ) + "'" );
40 * <p/>
41 * * warnings: 3
42 * <p/>
43 * </pre>
44 * <p/>
45 * <p>Colorization is turned on by supplying -D<b>pmd.color</b> - any value other than
46 * '0' or 'false', enables color - including an empty value (''). <b>Nota Bene:</b>
47 * colorization is atm only supported under *nix terminals accepting ansi escape
48 * sequences, such as xterm, rxvt et cetera.</p>
49 */
50 public class PapariTextRenderer extends AbstractRenderer {
51 /**
52 * Directory from where java was invoked.
53 */
54 private String pwd;
55
56 private String yellowBold = "";
57 private String whiteBold = "";
58 private String redBold = "";
59 private String cyan = "";
60 private String green = "";
61
62 private String colorReset = "";
63
64 /**
65 * Enables colors on *nix systems - not windows. Color support depends
66 * on the pmd.color property, which should be set with the -D option
67 * during execution - a set value other than 'false' or '0' enables color.
68 * <p/>
69 * btw, is it possible to do this on windows (ie; console colors)?
70 */
71 private void initializeColorsIfSupported() {
72 if (System.getProperty("pmd.color") != null &&
73 !(System.getProperty("pmd.color").equals("0") || System.getProperty("pmd.color").equals("false"))) {
74 this.yellowBold = "\u001B[1;33m";
75 this.whiteBold = "\u001B[1;37m";
76 this.redBold = "\u001B[1;31m";
77 this.green = "\u001B[0;32m";
78 this.cyan = "\u001B[0;36m";
79
80 this.colorReset = "\u001B[0m";
81 }
82 }
83
84 public void render(Writer writer, Report report) throws IOException {
85 StringBuffer buf = new StringBuffer(PMD.EOL);
86 initializeColorsIfSupported();
87 String lastFile = null;
88 int numberOfErrors = 0;
89 int numberOfWarnings = 0;
90
91 for (Iterator<IRuleViolation> i = report.iterator(); i.hasNext();) {
92 buf.setLength(0);
93 numberOfWarnings++;
94 IRuleViolation rv = i.next();
95 if (!rv.getFilename().equals(lastFile)) {
96 lastFile = rv.getFilename();
97 buf.append(this.yellowBold + "*" + this.colorReset + " file: " + this.whiteBold + this.getRelativePath(lastFile) + this.colorReset + PMD.EOL);
98 }
99 buf.append(this.green + " src: " + this.cyan + lastFile.substring(lastFile.lastIndexOf(File.separator) + 1) + this.colorReset + ":" + this.cyan + rv.getBeginLine() + (rv.getEndLine() == -1 ? "" : ":" + rv.getEndLine()) + this.colorReset + PMD.EOL);
100 buf.append(this.green + " rule: " + this.colorReset + rv.getRule().getName() + PMD.EOL);
101 buf.append(this.green + " msg: " + this.colorReset + rv.getDescription() + PMD.EOL);
102 buf.append(this.green + " code: " + this.colorReset + this.getLine(lastFile, rv.getBeginLine()) + PMD.EOL + PMD.EOL);
103 writer.write(buf.toString());
104 }
105 writer.write(PMD.EOL + PMD.EOL);
106 writer.write("Summary:" + PMD.EOL + PMD.EOL);
107 Map<String, Integer> summary = report.getCountSummary();
108 for (Map.Entry<String, Integer> entry : summary.entrySet()) {
109 buf.setLength(0);
110 String key = entry.getKey();
111 buf.append(key).append(" : ").append(entry.getValue()).append(PMD.EOL);
112 writer.write(buf.toString());
113 }
114
115 for (Iterator<Report.ProcessingError> i = report.errors(); i.hasNext();) {
116 buf.setLength(0);
117 numberOfErrors++;
118 Report.ProcessingError error = i.next();
119 if (error.getFile().equals(lastFile)) {
120 lastFile = error.getFile();
121 buf.append(this.redBold + "*" + this.colorReset + " file: " + this.whiteBold + this.getRelativePath(lastFile) + this.colorReset + PMD.EOL);
122 }
123 buf.append(this.green + " err: " + this.cyan + error.getMsg() + this.colorReset + PMD.EOL + PMD.EOL);
124 writer.write(buf.toString());
125 }
126
127
128 if (numberOfErrors > 0) {
129 writer.write(this.redBold + "*" + this.colorReset + " errors: " + this.whiteBold + numberOfWarnings + this.colorReset + PMD.EOL);
130 }
131 writer.write(this.yellowBold + "*" + this.colorReset + " warnings: " + this.whiteBold + numberOfWarnings + this.colorReset + PMD.EOL);
132 }
133
134 /**
135 * Retrieves the requested line from the specified file.
136 *
137 * @param sourceFile the java or cpp source file
138 * @param line line number to extract
139 * @return a trimmed line of source code
140 */
141 private String getLine(String sourceFile, int line) {
142 String code = null;
143 BufferedReader br = null;
144 try {
145 br = new BufferedReader(getReader(sourceFile));
146 for (int i = 0; line > i; i++) {
147 code = br.readLine().trim();
148 }
149 } catch (IOException ioErr) {
150 ioErr.printStackTrace();
151 } finally {
152 if (br != null) {
153 try {
154 br.close();
155 } catch (IOException ioErr) {
156 ioErr.printStackTrace();
157 }
158 }
159 }
160 return code;
161 }
162
163 protected Reader getReader(String sourceFile) throws FileNotFoundException {
164 return new FileReader(new File(sourceFile));
165 }
166
167 /**
168 * Attempts to determine the relative path to the file. If relative path cannot be found,
169 * the original path is returnedi, ie - the current path for the supplied file.
170 *
171 * @param fileName well, the file with its original path.
172 * @return the relative path to the file
173 */
174 private String getRelativePath(String fileName) {
175 String relativePath;
176
177
178 if (pwd == null) {
179 try {
180 this.pwd = new File(".").getCanonicalPath();
181 } catch (IOException ioErr) {
182
183 this.pwd = "";
184 }
185 }
186
187
188 if (fileName.indexOf(this.pwd) == 0) {
189 relativePath = "." + fileName.substring(this.pwd.length());
190
191
192 if (relativePath.startsWith("." + File.separator + "." + File.separator)) {
193 relativePath = relativePath.substring(2);
194 }
195 } else {
196
197
198
199 relativePath = fileName;
200 }
201
202 return relativePath;
203 }
204 }