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.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         // adding error message count, if any
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         // check if working directory need to be assigned
178         if (pwd == null) {
179             try {
180                 this.pwd = new File(".").getCanonicalPath();
181             } catch (IOException ioErr) {
182                 // to avoid further error
183                 this.pwd = "";
184             }
185         }
186 
187         // make sure that strings match before doing any substring-ing
188         if (fileName.indexOf(this.pwd) == 0) {
189             relativePath = "." + fileName.substring(this.pwd.length());
190 
191             // remove current dir occuring twice - occurs if . was supplied as path
192             if (relativePath.startsWith("." + File.separator + "." + File.separator)) {
193                 relativePath = relativePath.substring(2);
194             }
195         } else {
196             // this happens when pmd's supplied argument deviates from the pwd 'branch' (god knows this terminolgy - i hope i make some sense).
197             // for instance, if supplied=/usr/lots/of/src and pwd=/usr/lots/of/shared/source
198             // TODO: a fix to get relative path?
199             relativePath = fileName;
200         }
201 
202         return relativePath;
203     }
204 }