Coverage Report - com.puppycrawl.tools.checkstyle.checks.coding.MultipleStringLiteralsCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
MultipleStringLiteralsCheck
96%
49/51
90%
20/22
2.083
MultipleStringLiteralsCheck$1
N/A
N/A
2.083
MultipleStringLiteralsCheck$StringInfo
100%
7/7
N/A
2.083
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  Oliver Burn
 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  
 package com.puppycrawl.tools.checkstyle.checks.coding;
 20  
 
 21  
 import com.google.common.collect.Lists;
 22  
 import com.google.common.collect.Maps;
 23  
 import com.puppycrawl.tools.checkstyle.api.Check;
 24  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 25  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 26  
 import com.puppycrawl.tools.checkstyle.api.Utils;
 27  
 import java.util.BitSet;
 28  
 import java.util.List;
 29  
 import java.util.Map;
 30  
 import java.util.Set;
 31  
 import java.util.regex.Pattern;
 32  
 
 33  
 /**
 34  
  * Checks for multiple occurrences of the same string literal within a
 35  
  * single file.
 36  
  *
 37  
  * @author Daniel Grenner
 38  
  */
 39  
 public class MultipleStringLiteralsCheck extends Check
 40  
 {
 41  
     /**
 42  
      * The found strings and their positions.
 43  
      * <String, ArrayList>, with the ArrayList containing StringInfo objects.
 44  
      */
 45  4
     private final Map<String, List<StringInfo>> mStringMap = Maps.newHashMap();
 46  
 
 47  
     /**
 48  
      * Marks the TokenTypes where duplicate strings should be ignored.
 49  
      */
 50  4
     private final BitSet mIgnoreOccurrenceContext = new BitSet();
 51  
 
 52  
     /**
 53  
      * The allowed number of string duplicates in a file before an error is
 54  
      * generated.
 55  
      */
 56  4
     private int mAllowedDuplicates = 1;
 57  
 
 58  
     /**
 59  
      * Sets the maximum allowed duplicates of a string.
 60  
      * @param aAllowedDuplicates The maximum number of duplicates.
 61  
      */
 62  
     public void setAllowedDuplicates(int aAllowedDuplicates)
 63  
     {
 64  4
         mAllowedDuplicates = aAllowedDuplicates;
 65  4
     }
 66  
 
 67  
     /**
 68  
      * Pattern for matching ignored strings.
 69  
      */
 70  
     private Pattern mPattern;
 71  
 
 72  
     /**
 73  
      * Construct an instance with default values.
 74  
      */
 75  
     public MultipleStringLiteralsCheck()
 76  4
     {
 77  4
         setIgnoreStringsRegexp("^\"\"$");
 78  4
         mIgnoreOccurrenceContext.set(TokenTypes.ANNOTATION);
 79  4
     }
 80  
 
 81  
     /**
 82  
      * Sets regexp pattern for ignored strings.
 83  
      * @param aIgnoreStringsRegexp regexp pattern for ignored strings
 84  
      */
 85  
     public void setIgnoreStringsRegexp(String aIgnoreStringsRegexp)
 86  
     {
 87  6
         if ((aIgnoreStringsRegexp != null)
 88  
             && (aIgnoreStringsRegexp.length() > 0))
 89  
         {
 90  5
             mPattern = Utils.getPattern(aIgnoreStringsRegexp);
 91  
         }
 92  
         else {
 93  1
             mPattern = null;
 94  
         }
 95  6
     }
 96  
 
 97  
     /**
 98  
      * Adds a set of tokens the check is interested in.
 99  
      * @param aStrRep the string representation of the tokens interested in
 100  
      */
 101  
     public final void setIgnoreOccurrenceContext(String[] aStrRep)
 102  
     {
 103  1
         mIgnoreOccurrenceContext.clear();
 104  1
         for (final String s : aStrRep) {
 105  0
             final int type = TokenTypes.getTokenId(s);
 106  0
             mIgnoreOccurrenceContext.set(type);
 107  
         }
 108  1
     }
 109  
 
 110  
     @Override
 111  
     public int[] getDefaultTokens()
 112  
     {
 113  4
         return new int[] {TokenTypes.STRING_LITERAL};
 114  
     }
 115  
 
 116  
     @Override
 117  
     public void visitToken(DetailAST aAST)
 118  
     {
 119  76
         if (isInIgnoreOccurrenceContext(aAST)) {
 120  12
             return;
 121  
         }
 122  64
         final String currentString = aAST.getText();
 123  64
         if ((mPattern == null) || !mPattern.matcher(currentString).find()) {
 124  49
             List<StringInfo> hitList = mStringMap.get(currentString);
 125  49
             if (hitList == null) {
 126  25
                 hitList = Lists.newArrayList();
 127  25
                 mStringMap.put(currentString, hitList);
 128  
             }
 129  49
             final int line = aAST.getLineNo();
 130  49
             final int col = aAST.getColumnNo();
 131  49
             hitList.add(new StringInfo(line, col));
 132  
         }
 133  64
     }
 134  
 
 135  
     /**
 136  
      * Analyses the path from the AST root to a given AST for occurrences
 137  
      * of the token types in {@link #mIgnoreOccurrenceContext}.
 138  
      *
 139  
      * @param aAST the node from where to start searching towards the root node
 140  
      * @return whether the path from the root node to aAST contains one of the
 141  
      * token type in {@link #mIgnoreOccurrenceContext}.
 142  
      */
 143  
     private boolean isInIgnoreOccurrenceContext(DetailAST aAST)
 144  
     {
 145  76
         for (DetailAST token = aAST;
 146  512
              token.getParent() != null;
 147  436
              token = token.getParent())
 148  
         {
 149  448
             final int type = token.getType();
 150  448
             if (mIgnoreOccurrenceContext.get(type)) {
 151  12
                 return true;
 152  
             }
 153  
         }
 154  64
         return false;
 155  
     }
 156  
 
 157  
     @Override
 158  
     public void beginTree(DetailAST aRootAST)
 159  
     {
 160  4
         super.beginTree(aRootAST);
 161  4
         mStringMap.clear();
 162  4
     }
 163  
 
 164  
     @Override
 165  
     public void finishTree(DetailAST aRootAST)
 166  
     {
 167  4
         final Set<String> keys = mStringMap.keySet();
 168  4
         for (String key : keys) {
 169  25
             final List<StringInfo> hits = mStringMap.get(key);
 170  25
             if (hits.size() > mAllowedDuplicates) {
 171  7
                 final StringInfo firstFinding = hits.get(0);
 172  7
                 final int line = firstFinding.getLine();
 173  7
                 final int col = firstFinding.getCol();
 174  7
                 log(line, col, "multiple.string.literal", key, hits.size());
 175  
             }
 176  25
         }
 177  4
     }
 178  
 
 179  
     /**
 180  
      * This class contains information about where a string was found.
 181  
      */
 182  63
     private static final class StringInfo
 183  
     {
 184  
         /**
 185  
          * Line of finding
 186  
          */
 187  
         private final int mLine;
 188  
         /**
 189  
          * Column of finding
 190  
          */
 191  
         private final int mCol;
 192  
         /**
 193  
          * Creates information about a string position.
 194  
          * @param aLine int
 195  
          * @param aCol int
 196  
          */
 197  
         private StringInfo(int aLine, int aCol)
 198  49
         {
 199  49
             mLine = aLine;
 200  49
             mCol = aCol;
 201  49
         }
 202  
 
 203  
         /**
 204  
          * The line where a string was found.
 205  
          * @return int Line of the string.
 206  
          */
 207  
         private int getLine()
 208  
         {
 209  7
             return mLine;
 210  
         }
 211  
 
 212  
         /**
 213  
          * The column where a string was found.
 214  
          * @return int Column of the string.
 215  
          */
 216  
         private int getCol()
 217  
         {
 218  7
             return mCol;
 219  
         }
 220  
     }
 221  
 
 222  
 }