View Javadoc

    1   /***
    2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
    3    */
    4   package net.sourceforge.pmd.rules.strings;
    5
    6   import net.sourceforge.pmd.AbstractRule;
    7   import net.sourceforge.pmd.ast.ASTCompilationUnit;
    8   import net.sourceforge.pmd.ast.ASTLiteral;
    9   import net.sourceforge.pmd.ast.SimpleNode;
    10
    11  import java.io.BufferedReader;
    12  import java.io.File;
    13  import java.io.FileReader;
    14  import java.io.IOException;
    15  import java.io.LineNumberReader;
    16  import java.util.ArrayList;
    17  import java.util.HashMap;
    18  import java.util.HashSet;
    19  import java.util.Iterator;
    20  import java.util.List;
    21  import java.util.Map;
    22  import java.util.Set;
    23
    24  public class AvoidDuplicateLiteralsRule extends AbstractRule {
    25
    26      public static class ExceptionParser {
    27
    28          private static final char ESCAPE_CHAR = '//';
    29          private char delimiter;
    30
    31          public ExceptionParser(char delimiter) {
    32              this.delimiter = delimiter;
    33          }
    34
    35          public Set parse(String in) {
    36              Set result = new HashSet();
    37              StringBuffer currentToken = new StringBuffer();
    38              boolean inEscapeMode = false;
    39              for (int i = 0; i < in.length(); i++) {
    40                  if (inEscapeMode) {
    41                      inEscapeMode = false;
    42                      currentToken.append(in.charAt(i));
    43                      continue;
    44                  }
    45                  if (in.charAt(i) == ESCAPE_CHAR) {
    46                      inEscapeMode = true;
    47                      continue;
    48                  }
    49                  if (in.charAt(i) == delimiter) {
    50                      result.add(currentToken.toString());
    51                      currentToken = new StringBuffer();
    52                  } else {
    53                      currentToken.append(in.charAt(i));
    54                  }
    55              }
    56              if (currentToken.length() > 0) {
    57                  result.add(currentToken.toString());
    58              }
    59              return result;
    60          }
    61      }
    62
    63      private static final char DEFAULT_SEPARATOR = ',';
    64      private static final String EXCEPTION_LIST_PROPERTY = "exceptionlist";
    65      private static final String SEPARATOR_PROPERTY = "separator";
    66      private static final String EXCEPTION_FILE_NAME_PROPERTY = "exceptionfile";
    67
    68      private Map literals = new HashMap();
    69      private Set exceptions = new HashSet();
    70
    71      public Object visit(ASTCompilationUnit node, Object data) {
    72          literals.clear();
    73
    74          if (hasProperty(EXCEPTION_LIST_PROPERTY)) {
    75              ExceptionParser p;
    76              if (hasProperty(SEPARATOR_PROPERTY)) {
    77                  p = new ExceptionParser(getStringProperty(SEPARATOR_PROPERTY).charAt(0));
    78              } else {
    79                  p = new ExceptionParser(DEFAULT_SEPARATOR);
    80              }
    81              exceptions = p.parse(getStringProperty(EXCEPTION_LIST_PROPERTY));
    82          } else if (hasProperty(EXCEPTION_FILE_NAME_PROPERTY)) {
    83              exceptions = new HashSet();
    84              LineNumberReader reader = null;
    85              try {
    86                  reader = new LineNumberReader(new BufferedReader(new FileReader(new File(getStringProperty(EXCEPTION_FILE_NAME_PROPERTY)))));
    87                  String line;
    88                  while ((line = reader.readLine()) != null) {
    89                      exceptions.add(line);
    90                  }
    91              } catch (IOException ioe) {
    92                  ioe.printStackTrace();
    93              } finally {
    94                  try {
    95                      if (reader != null)
    96                          reader.close();
    97                  } catch (IOException ioe) {
    98                      ioe.printStackTrace();
    99                  }
    100             }
    101         }
    102
    103         super.visit(node, data);
    104
    105         int threshold = getIntProperty("threshold");
    106         for (Iterator i = literals.keySet().iterator(); i.hasNext();) {
    107             String key = (String) i.next();
    108             List occurrences = (List) literals.get(key);
    109             if (occurrences.size() >= threshold) {
    110                 Object[] args = new Object[]{key, new Integer(occurrences.size()), new Integer(((SimpleNode) occurrences.get(0)).getBeginLine())};
    111                 addViolation(data, (SimpleNode) occurrences.get(0), args);
    112             }
    113         }
    114         return data;
    115     }
    116
    117     public Object visit(ASTLiteral node, Object data) {
    118         // just catching strings of 5 chars or more (including the enclosing quotes) for now - no numbers
    119         if (node.getImage() == null || node.getImage().indexOf('\"') == -1 || node.getImage().length() < 5) {
    120             return data;
    121         }
    122
    123         // skip any exceptions
    124         if (exceptions.contains(node.getImage().substring(1, node.getImage().length() - 1))) {
    125             return data;
    126         }
    127
    128         if (literals.containsKey(node.getImage())) {
    129             List occurrences = (List) literals.get(node.getImage());
    130             occurrences.add(node);
    131         } else {
    132             List occurrences = new ArrayList();
    133             occurrences.add(node);
    134             literals.put(node.getImage(), occurrences);
    135         }
    136
    137         return data;
    138     }
    139 }
    140