Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.MultipleStringLiteralsCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
MultipleStringLiteralsCheck
100%
52/52
100%
22/22
1.786
MultipleStringLiteralsCheck$StringInfo
100%
7/7
N/A
1.786
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.coding;
 21  
 
 22  
 import java.util.ArrayList;
 23  
 import java.util.BitSet;
 24  
 import java.util.HashMap;
 25  
 import java.util.List;
 26  
 import java.util.Map;
 27  
 import java.util.regex.Pattern;
 28  
 
 29  
 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
 30  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 31  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 32  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 33  
 import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
 34  
 
 35  
 /**
 36  
  * Checks for multiple occurrences of the same string literal within a
 37  
  * single file.
 38  
  *
 39  
  * @author Daniel Grenner
 40  
  */
 41  
 @FileStatefulCheck
 42  
 public class MultipleStringLiteralsCheck extends AbstractCheck {
 43  
 
 44  
     /**
 45  
      * A key is pointing to the warning message text in "messages.properties"
 46  
      * file.
 47  
      */
 48  
     public static final String MSG_KEY = "multiple.string.literal";
 49  
 
 50  
     /**
 51  
      * The found strings and their positions.
 52  
      * {@code <String, ArrayList>}, with the ArrayList containing StringInfo
 53  
      * objects.
 54  
      */
 55  20
     private final Map<String, List<StringInfo>> stringMap = new HashMap<>();
 56  
 
 57  
     /**
 58  
      * Marks the TokenTypes where duplicate strings should be ignored.
 59  
      */
 60  20
     private final BitSet ignoreOccurrenceContext = new BitSet();
 61  
 
 62  
     /**
 63  
      * The allowed number of string duplicates in a file before an error is
 64  
      * generated.
 65  
      */
 66  20
     private int allowedDuplicates = 1;
 67  
 
 68  
     /**
 69  
      * Pattern for matching ignored strings.
 70  
      */
 71  
     private Pattern ignoreStringsRegexp;
 72  
 
 73  
     /**
 74  
      * Construct an instance with default values.
 75  
      */
 76  20
     public MultipleStringLiteralsCheck() {
 77  20
         setIgnoreStringsRegexp(Pattern.compile("^\"\"$"));
 78  20
         ignoreOccurrenceContext.set(TokenTypes.ANNOTATION);
 79  20
     }
 80  
 
 81  
     /**
 82  
      * Sets the maximum allowed duplicates of a string.
 83  
      * @param allowedDuplicates The maximum number of duplicates.
 84  
      */
 85  
     public void setAllowedDuplicates(int allowedDuplicates) {
 86  6
         this.allowedDuplicates = allowedDuplicates;
 87  6
     }
 88  
 
 89  
     /**
 90  
      * Sets regular expression pattern for ignored strings.
 91  
      * @param ignoreStringsRegexp
 92  
      *        regular expression pattern for ignored strings
 93  
      * @noinspection WeakerAccess
 94  
      */
 95  
     public final void setIgnoreStringsRegexp(Pattern ignoreStringsRegexp) {
 96  25
         if (ignoreStringsRegexp == null || ignoreStringsRegexp.pattern().isEmpty()) {
 97  3
             this.ignoreStringsRegexp = null;
 98  
         }
 99  
         else {
 100  22
             this.ignoreStringsRegexp = ignoreStringsRegexp;
 101  
         }
 102  25
     }
 103  
 
 104  
     /**
 105  
      * Adds a set of tokens the check is interested in.
 106  
      * @param strRep the string representation of the tokens interested in
 107  
      */
 108  
     public final void setIgnoreOccurrenceContext(String... strRep) {
 109  4
         ignoreOccurrenceContext.clear();
 110  6
         for (final String s : strRep) {
 111  2
             final int type = TokenUtils.getTokenId(s);
 112  2
             ignoreOccurrenceContext.set(type);
 113  
         }
 114  4
     }
 115  
 
 116  
     @Override
 117  
     public int[] getDefaultTokens() {
 118  33
         return getRequiredTokens();
 119  
     }
 120  
 
 121  
     @Override
 122  
     public int[] getAcceptableTokens() {
 123  5
         return getRequiredTokens();
 124  
     }
 125  
 
 126  
     @Override
 127  
     public int[] getRequiredTokens() {
 128  71
         return new int[] {TokenTypes.STRING_LITERAL};
 129  
     }
 130  
 
 131  
     @Override
 132  
     public void visitToken(DetailAST ast) {
 133  135
         if (!isInIgnoreOccurrenceContext(ast)) {
 134  101
             final String currentString = ast.getText();
 135  101
             if (ignoreStringsRegexp == null || !ignoreStringsRegexp.matcher(currentString).find()) {
 136  76
                 List<StringInfo> hitList = stringMap.get(currentString);
 137  76
                 if (hitList == null) {
 138  39
                     hitList = new ArrayList<>();
 139  39
                     stringMap.put(currentString, hitList);
 140  
                 }
 141  76
                 final int line = ast.getLineNo();
 142  76
                 final int col = ast.getColumnNo();
 143  76
                 hitList.add(new StringInfo(line, col));
 144  
             }
 145  
         }
 146  135
     }
 147  
 
 148  
     /**
 149  
      * Analyses the path from the AST root to a given AST for occurrences
 150  
      * of the token types in {@link #ignoreOccurrenceContext}.
 151  
      *
 152  
      * @param ast the node from where to start searching towards the root node
 153  
      * @return whether the path from the root node to ast contains one of the
 154  
      *     token type in {@link #ignoreOccurrenceContext}.
 155  
      */
 156  
     private boolean isInIgnoreOccurrenceContext(DetailAST ast) {
 157  135
         boolean isInIgnoreOccurrenceContext = false;
 158  135
         for (DetailAST token = ast;
 159  880
              token.getParent() != null;
 160  745
              token = token.getParent()) {
 161  779
             final int type = token.getType();
 162  779
             if (ignoreOccurrenceContext.get(type)) {
 163  34
                 isInIgnoreOccurrenceContext = true;
 164  34
                 break;
 165  
             }
 166  
         }
 167  135
         return isInIgnoreOccurrenceContext;
 168  
     }
 169  
 
 170  
     @Override
 171  
     public void beginTree(DetailAST rootAST) {
 172  9
         stringMap.clear();
 173  9
     }
 174  
 
 175  
     @Override
 176  
     public void finishTree(DetailAST rootAST) {
 177  9
         for (Map.Entry<String, List<StringInfo>> stringListEntry : stringMap.entrySet()) {
 178  39
             final List<StringInfo> hits = stringListEntry.getValue();
 179  39
             if (hits.size() > allowedDuplicates) {
 180  13
                 final StringInfo firstFinding = hits.get(0);
 181  13
                 final int line = firstFinding.getLine();
 182  13
                 final int col = firstFinding.getCol();
 183  13
                 log(line, col, MSG_KEY, stringListEntry.getKey(), hits.size());
 184  
             }
 185  39
         }
 186  9
     }
 187  
 
 188  
     /**
 189  
      * This class contains information about where a string was found.
 190  
      */
 191  26
     private static final class StringInfo {
 192  
         /**
 193  
          * Line of finding.
 194  
          */
 195  
         private final int line;
 196  
         /**
 197  
          * Column of finding.
 198  
          */
 199  
         private final int col;
 200  
 
 201  
         /**
 202  
          * Creates information about a string position.
 203  
          * @param line int
 204  
          * @param col int
 205  
          */
 206  76
         StringInfo(int line, int col) {
 207  76
             this.line = line;
 208  76
             this.col = col;
 209  76
         }
 210  
 
 211  
         /**
 212  
          * The line where a string was found.
 213  
          * @return int Line of the string.
 214  
          */
 215  
         private int getLine() {
 216  13
             return line;
 217  
         }
 218  
 
 219  
         /**
 220  
          * The column where a string was found.
 221  
          * @return int Column of the string.
 222  
          */
 223  
         private int getCol() {
 224  13
             return col;
 225  
         }
 226  
     }
 227  
 
 228  
 }