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