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