View Javadoc

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 java.io.BufferedReader;
7   import java.io.File;
8   import java.io.FileNotFoundException;
9   import java.io.FileReader;
10  import java.io.IOException;
11  import java.io.Reader;
12  import java.util.Iterator;
13  import java.util.Map;
14  
15  import net.sourceforge.pmd.PMD;
16  import net.sourceforge.pmd.Report;
17  import net.sourceforge.pmd.RuleViolation;
18  import net.sourceforge.pmd.lang.rule.properties.StringProperty;
19  import net.sourceforge.pmd.util.IOUtil;
20  
21  /**
22   * <p>A console renderer with optional color support under *nix systems.</p>
23   * <p/>
24   * <pre>
25   * * file: ./src/gilot/Test.java
26   *     src:  Test.java:12
27   *     rule: AtLeastOneConstructor
28   *     msg:  Each class should declare at least one constructor
29   *     code: public class Test
30   * <p/>
31   * * file: ./src/gilot/log/format/LogInterpreter.java
32   *     src:  LogInterpreter.java:317
33   *     rule: AvoidDuplicateLiterals
34   *     msg:  The same String literal appears 4 times in this file; the first occurrence is on line 317
35   *     code: logger.error( "missing attribute 'app_arg' in rule '" + ((Element)element.getParent()).getAttributeValue( "name" ) + "'" );
36   * <p/>
37   *     src:  LogInterpreter.java:317
38   *     rule: AvoidDuplicateLiterals
39   *     msg:  The same String literal appears 5 times in this file; the first occurrence is on line 317
40   *     code: logger.error( "missing attribute 'app_arg' in rule '" + ((Element)element.getParent()).getAttributeValue( "name" ) + "'" );
41   * <p/>
42   * * warnings: 3
43   * <p/>
44   * </pre>
45   * <p/>
46   * <p>Colorization is turned on by supplying -D<b>pmd.color</b> - any value other than
47   * '0' or 'false', enables color - including an empty value (''). <b>Nota Bene:</b>
48   * colorization is atm only supported under *nix terminals accepting ansi escape
49   * sequences, such as xterm, rxvt et cetera.</p>
50   */
51  public class TextColorRenderer extends AbstractAccumulatingRenderer {
52  
53  
54      public static final String NAME = "textcolor";
55  
56      public static final StringProperty COLOR = new StringProperty("color", "Enables colors with anything other than 'false' or '0'.", "yes", 0);
57      private static final String SYSTEM_PROPERTY_PMD_COLOR = "pmd.color";
58  
59      /**
60       * Directory from where java was invoked.
61       */
62      private String pwd;
63  
64      private String yellowBold = "";
65      private String whiteBold = "";
66      private String redBold = "";
67      private String cyan = "";
68      private String green = "";
69  
70      private String colorReset = "";
71  
72      public TextColorRenderer() {
73  	// This Renderer was originally submitted by Adrian Papari and was called the "PapariTextRenderer" pre-PMD 5.0.
74  	super(NAME, "Text format, with color support (requires ANSI console support, e.g. xterm, rxvt, etc.).");
75  	definePropertyDescriptor(COLOR);
76      }
77  
78      public String defaultFileExtension() { return "txt"; }
79      
80      /**
81       * Enables colors on *nix systems - not windows. Color support depends
82       * on the pmd.color property, which should be set with the -D option
83       * during execution - a set value other than 'false' or '0' enables color.
84       * <p/>
85       * btw, is it possible to do this on windows (ie; console colors)?
86       */
87      private void initializeColorsIfSupported() {
88  	if (isPropertyEnabled(getProperty(COLOR)) || isPropertyEnabled(System.getProperty(SYSTEM_PROPERTY_PMD_COLOR))) {
89  	    this.yellowBold = "\u001B[1;33m";
90  	    this.whiteBold = "\u001B[1;37m";
91  	    this.redBold = "\u001B[1;31m";
92  	    this.green = "\u001B[0;32m";
93  	    this.cyan = "\u001B[0;36m";
94  
95  	    this.colorReset = "\u001B[0m";
96  	}
97      }
98  
99      private boolean isPropertyEnabled(String property) {
100 	return property != null && !(property.equals("0") || property.equalsIgnoreCase("false"));
101     }
102 
103     /**
104      * {@inheritDoc}
105      */
106     @Override
107     public void end() throws IOException {
108 	StringBuffer buf = new StringBuffer(500);
109 	buf.append(PMD.EOL);
110 	initializeColorsIfSupported();
111 	String lastFile = null;
112 	int numberOfErrors = 0;
113 	int numberOfWarnings = 0;
114 
115 	for (Iterator<RuleViolation> i = report.iterator(); i.hasNext();) {
116 	    buf.setLength(0);
117 	    numberOfWarnings++;
118 	    RuleViolation rv = i.next();
119 	    if (!rv.getFilename().equals(lastFile)) {
120 		lastFile = rv.getFilename();
121 		buf.append(this.yellowBold + "*" + this.colorReset + " file: " + this.whiteBold
122 			+ this.getRelativePath(lastFile) + this.colorReset + PMD.EOL);
123 	    }
124 	    buf.append(this.green + "    src:  " + this.cyan
125 		    + lastFile.substring(lastFile.lastIndexOf(File.separator) + 1) + this.colorReset + ":" + this.cyan
126 		    + rv.getBeginLine() + (rv.getEndLine() == -1 ? "" : ":" + rv.getEndLine()) + this.colorReset
127 		    + PMD.EOL);
128 	    buf.append(this.green + "    rule: " + this.colorReset + rv.getRule().getName() + PMD.EOL);
129 	    buf.append(this.green + "    msg:  " + this.colorReset + rv.getDescription() + PMD.EOL);
130 	    buf.append(this.green + "    code: " + this.colorReset + this.getLine(lastFile, rv.getBeginLine())
131 		    + PMD.EOL + PMD.EOL);
132 	    writer.write(buf.toString());
133 	}
134 	writer.write(PMD.EOL + PMD.EOL);
135 	writer.write("Summary:" + PMD.EOL + PMD.EOL);
136 	Map<String, Integer> summary = report.getCountSummary();
137 	for (Map.Entry<String, Integer> entry : summary.entrySet()) {
138 	    buf.setLength(0);
139 	    String key = entry.getKey();
140 	    buf.append(key).append(" : ").append(entry.getValue()).append(PMD.EOL);
141 	    writer.write(buf.toString());
142 	}
143 
144 	for (Iterator<Report.ProcessingError> i = report.errors(); i.hasNext();) {
145 	    buf.setLength(0);
146 	    numberOfErrors++;
147 	    Report.ProcessingError error = i.next();
148 	    if (error.getFile().equals(lastFile)) {
149 		lastFile = error.getFile();
150 		buf.append(this.redBold + "*" + this.colorReset + " file: " + this.whiteBold
151 			+ this.getRelativePath(lastFile) + this.colorReset + PMD.EOL);
152 	    }
153 	    buf.append(this.green + "    err:  " + this.cyan + error.getMsg() + this.colorReset + PMD.EOL + PMD.EOL);
154 	    writer.write(buf.toString());
155 	}
156 
157 	// adding error message count, if any
158 	if (numberOfErrors > 0) {
159 	    writer.write(this.redBold + "*" + this.colorReset + " errors:   " + this.whiteBold + numberOfWarnings
160 		    + this.colorReset + PMD.EOL);
161 	}
162 	writer.write(this.yellowBold + "*" + this.colorReset + " warnings: " + this.whiteBold + numberOfWarnings
163 		+ this.colorReset + PMD.EOL);
164     }
165 
166     /**
167      * Retrieves the requested line from the specified file.
168      *
169      * @param sourceFile the java or cpp source file
170      * @param line       line number to extract
171      * @return a trimmed line of source code
172      */
173     private String getLine(String sourceFile, int line) {
174 		String code = null;
175 		BufferedReader br = null;
176 		try {
177 		    br = new BufferedReader(getReader(sourceFile));
178 		    for (int i = 0; line > i; i++) {
179 		    	String txt = br.readLine();
180 				code = txt == null ? "" : txt.trim();
181 			    }
182 		} catch (IOException ioErr) {
183 		    ioErr.printStackTrace();
184 		} finally {
185 			IOUtil.closeQuietly(br);
186 		}
187 		return code;
188     }
189 
190     protected Reader getReader(String sourceFile) throws FileNotFoundException {
191 	return new FileReader(new File(sourceFile));
192     }
193 
194     /**
195      * Attempts to determine the relative path to the file. If relative path cannot be found,
196      * the original path is returnedi, ie - the current path for the supplied file.
197      *
198      * @param fileName well, the file with its original path.
199      * @return the relative path to the file
200      */
201     private String getRelativePath(String fileName) {
202 	String relativePath;
203 
204 	// check if working directory need to be assigned
205 	if (pwd == null) {
206 	    try {
207 		this.pwd = new File(".").getCanonicalPath();
208 	    } catch (IOException ioErr) {
209 		// to avoid further error
210 		this.pwd = "";
211 	    }
212 	}
213 
214 	// make sure that strings match before doing any substring-ing
215 	if (fileName.indexOf(this.pwd) == 0) {
216 	    relativePath = "." + fileName.substring(this.pwd.length());
217 
218 	    // remove current dir occuring twice - occurs if . was supplied as path
219 	    if (relativePath.startsWith("." + File.separator + "." + File.separator)) {
220 		relativePath = relativePath.substring(2);
221 	    }
222 	} else {
223 	    // this happens when pmd's supplied argument deviates from the pwd 'branch' (god knows this terminolgy - i hope i make some sense).
224 	    // for instance, if supplied=/usr/lots/of/src and pwd=/usr/lots/of/shared/source
225 	    // TODO: a fix to get relative path?
226 	    relativePath = fileName;
227 	}
228 
229 	return relativePath;
230     }
231 }