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