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 }