001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.coding; 020 021import com.google.common.collect.Lists; 022import com.google.common.collect.Maps; 023import com.puppycrawl.tools.checkstyle.api.Check; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.api.Utils; 027import java.util.BitSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031import java.util.regex.Pattern; 032 033/** 034 * Checks for multiple occurrences of the same string literal within a 035 * single file. 036 * 037 * @author Daniel Grenner 038 */ 039public class MultipleStringLiteralsCheck extends Check 040{ 041 /** 042 * The found strings and their positions. 043 * {@code <String, ArrayList>}, with the ArrayList containing StringInfo 044 * objects. 045 */ 046 private final Map<String, List<StringInfo>> stringMap = Maps.newHashMap(); 047 048 /** 049 * Marks the TokenTypes where duplicate strings should be ignored. 050 */ 051 private final BitSet ignoreOccurrenceContext = new BitSet(); 052 053 /** 054 * The allowed number of string duplicates in a file before an error is 055 * generated. 056 */ 057 private int allowedDuplicates = 1; 058 059 /** 060 * Sets the maximum allowed duplicates of a string. 061 * @param allowedDuplicates The maximum number of duplicates. 062 */ 063 public void setAllowedDuplicates(int allowedDuplicates) 064 { 065 this.allowedDuplicates = allowedDuplicates; 066 } 067 068 /** 069 * Pattern for matching ignored strings. 070 */ 071 private Pattern pattern; 072 073 /** 074 * Construct an instance with default values. 075 */ 076 public MultipleStringLiteralsCheck() 077 { 078 setIgnoreStringsRegexp("^\"\"$"); 079 ignoreOccurrenceContext.set(TokenTypes.ANNOTATION); 080 } 081 082 /** 083 * Sets regexp pattern for ignored strings. 084 * @param ignoreStringsRegexp regexp pattern for ignored strings 085 */ 086 public void setIgnoreStringsRegexp(String ignoreStringsRegexp) 087 { 088 if ((ignoreStringsRegexp != null) 089 && (ignoreStringsRegexp.length() > 0)) 090 { 091 pattern = Utils.getPattern(ignoreStringsRegexp); 092 } 093 else { 094 pattern = null; 095 } 096 } 097 098 /** 099 * Adds a set of tokens the check is interested in. 100 * @param strRep the string representation of the tokens interested in 101 */ 102 public final void setIgnoreOccurrenceContext(String[] strRep) 103 { 104 ignoreOccurrenceContext.clear(); 105 for (final String s : strRep) { 106 final int type = TokenTypes.getTokenId(s); 107 ignoreOccurrenceContext.set(type); 108 } 109 } 110 111 @Override 112 public int[] getDefaultTokens() 113 { 114 return new int[] {TokenTypes.STRING_LITERAL}; 115 } 116 117 @Override 118 public void visitToken(DetailAST ast) 119 { 120 if (isInIgnoreOccurrenceContext(ast)) { 121 return; 122 } 123 final String currentString = ast.getText(); 124 if ((pattern == null) || !pattern.matcher(currentString).find()) { 125 List<StringInfo> hitList = stringMap.get(currentString); 126 if (hitList == null) { 127 hitList = Lists.newArrayList(); 128 stringMap.put(currentString, hitList); 129 } 130 final int line = ast.getLineNo(); 131 final int col = ast.getColumnNo(); 132 hitList.add(new StringInfo(line, col)); 133 } 134 } 135 136 /** 137 * Analyses the path from the AST root to a given AST for occurrences 138 * of the token types in {@link #ignoreOccurrenceContext}. 139 * 140 * @param ast the node from where to start searching towards the root node 141 * @return whether the path from the root node to ast contains one of the 142 * token type in {@link #ignoreOccurrenceContext}. 143 */ 144 private boolean isInIgnoreOccurrenceContext(DetailAST ast) 145 { 146 for (DetailAST token = ast; 147 token.getParent() != null; 148 token = token.getParent()) 149 { 150 final int type = token.getType(); 151 if (ignoreOccurrenceContext.get(type)) { 152 return true; 153 } 154 } 155 return false; 156 } 157 158 @Override 159 public void beginTree(DetailAST rootAST) 160 { 161 super.beginTree(rootAST); 162 stringMap.clear(); 163 } 164 165 @Override 166 public void finishTree(DetailAST rootAST) 167 { 168 final Set<String> keys = stringMap.keySet(); 169 for (String key : keys) { 170 final List<StringInfo> hits = stringMap.get(key); 171 if (hits.size() > allowedDuplicates) { 172 final StringInfo firstFinding = hits.get(0); 173 final int line = firstFinding.getLine(); 174 final int col = firstFinding.getCol(); 175 log(line, col, "multiple.string.literal", key, hits.size()); 176 } 177 } 178 } 179 180 /** 181 * This class contains information about where a string was found. 182 */ 183 private static final class StringInfo 184 { 185 /** 186 * Line of finding 187 */ 188 private final int line; 189 /** 190 * Column of finding 191 */ 192 private final int col; 193 /** 194 * Creates information about a string position. 195 * @param line int 196 * @param col int 197 */ 198 private StringInfo(int line, int col) 199 { 200 this.line = line; 201 this.col = col; 202 } 203 204 /** 205 * The line where a string was found. 206 * @return int Line of the string. 207 */ 208 private int getLine() 209 { 210 return line; 211 } 212 213 /** 214 * The column where a string was found. 215 * @return int Column of the string. 216 */ 217 private int getCol() 218 { 219 return col; 220 } 221 } 222 223}