| 1 | //////////////////////////////////////////////////////////////////////////////// | |
| 2 | // checkstyle: Checks Java source code for adherence to a set of rules. | |
| 3 | // Copyright (C) 2001-2018 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 | private final Map<String, List<StringInfo>> stringMap = new HashMap<>(); | |
| 56 | ||
| 57 | /** | |
| 58 | * Marks the TokenTypes where duplicate strings should be ignored. | |
| 59 | */ | |
| 60 | 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 | 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 | public MultipleStringLiteralsCheck() { | |
| 77 |
1
1. |
setIgnoreStringsRegexp(Pattern.compile("^\"\"$")); |
| 78 |
1
1. |
ignoreOccurrenceContext.set(TokenTypes.ANNOTATION); |
| 79 | } | |
| 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 | this.allowedDuplicates = allowedDuplicates; | |
| 87 | } | |
| 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 |
2
1. setIgnoreStringsRegexp : negated conditional → KILLED 2. setIgnoreStringsRegexp : negated conditional → KILLED |
if (ignoreStringsRegexp == null || ignoreStringsRegexp.pattern().isEmpty()) { |
| 97 | this.ignoreStringsRegexp = null; | |
| 98 | } | |
| 99 | else { | |
| 100 | this.ignoreStringsRegexp = ignoreStringsRegexp; | |
| 101 | } | |
| 102 | } | |
| 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 |
1
1. setIgnoreOccurrenceContext : removed call to java/util/BitSet::clear → KILLED |
ignoreOccurrenceContext.clear(); |
| 110 | for (final String s : strRep) { | |
| 111 | final int type = TokenUtils.getTokenId(s); | |
| 112 |
1
1. setIgnoreOccurrenceContext : removed call to java/util/BitSet::set → KILLED |
ignoreOccurrenceContext.set(type); |
| 113 | } | |
| 114 | } | |
| 115 | ||
| 116 | @Override | |
| 117 | public int[] getDefaultTokens() { | |
| 118 |
1
1. getDefaultTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/MultipleStringLiteralsCheck::getDefaultTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return getRequiredTokens(); |
| 119 | } | |
| 120 | ||
| 121 | @Override | |
| 122 | public int[] getAcceptableTokens() { | |
| 123 |
1
1. getAcceptableTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/MultipleStringLiteralsCheck::getAcceptableTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return getRequiredTokens(); |
| 124 | } | |
| 125 | ||
| 126 | @Override | |
| 127 | public int[] getRequiredTokens() { | |
| 128 |
1
1. getRequiredTokens : mutated return of Object value for com/puppycrawl/tools/checkstyle/checks/coding/MultipleStringLiteralsCheck::getRequiredTokens to ( if (x != null) null else throw new RuntimeException ) → KILLED |
return new int[] {TokenTypes.STRING_LITERAL}; |
| 129 | } | |
| 130 | ||
| 131 | @Override | |
| 132 | public void visitToken(DetailAST ast) { | |
| 133 |
1
1. visitToken : negated conditional → KILLED |
if (!isInIgnoreOccurrenceContext(ast)) { |
| 134 | final String currentString = ast.getText(); | |
| 135 |
2
1. visitToken : negated conditional → KILLED 2. visitToken : negated conditional → KILLED |
if (ignoreStringsRegexp == null || !ignoreStringsRegexp.matcher(currentString).find()) { |
| 136 | List<StringInfo> hitList = stringMap.get(currentString); | |
| 137 |
1
1. visitToken : negated conditional → KILLED |
if (hitList == null) { |
| 138 | hitList = new ArrayList<>(); | |
| 139 | stringMap.put(currentString, hitList); | |
| 140 | } | |
| 141 | final int line = ast.getLineNo(); | |
| 142 | final int col = ast.getColumnNo(); | |
| 143 | hitList.add(new StringInfo(line, col)); | |
| 144 | } | |
| 145 | } | |
| 146 | } | |
| 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 | boolean isInIgnoreOccurrenceContext = false; | |
| 158 | for (DetailAST token = ast; | |
| 159 |
1
1. isInIgnoreOccurrenceContext : negated conditional → KILLED |
token.getParent() != null; |
| 160 | token = token.getParent()) { | |
| 161 | final int type = token.getType(); | |
| 162 |
1
1. isInIgnoreOccurrenceContext : negated conditional → KILLED |
if (ignoreOccurrenceContext.get(type)) { |
| 163 | isInIgnoreOccurrenceContext = true; | |
| 164 | break; | |
| 165 | } | |
| 166 | } | |
| 167 |
1
1. isInIgnoreOccurrenceContext : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return isInIgnoreOccurrenceContext; |
| 168 | } | |
| 169 | ||
| 170 | @Override | |
| 171 | public void beginTree(DetailAST rootAST) { | |
| 172 |
1
1. beginTree : removed call to java/util/Map::clear → KILLED |
stringMap.clear(); |
| 173 | } | |
| 174 | ||
| 175 | @Override | |
| 176 | public void finishTree(DetailAST rootAST) { | |
| 177 | for (Map.Entry<String, List<StringInfo>> stringListEntry : stringMap.entrySet()) { | |
| 178 | final List<StringInfo> hits = stringListEntry.getValue(); | |
| 179 |
2
1. finishTree : changed conditional boundary → KILLED 2. finishTree : negated conditional → KILLED |
if (hits.size() > allowedDuplicates) { |
| 180 | final StringInfo firstFinding = hits.get(0); | |
| 181 | final int line = firstFinding.getLine(); | |
| 182 | final int col = firstFinding.getCol(); | |
| 183 |
1
1. finishTree : removed call to com/puppycrawl/tools/checkstyle/checks/coding/MultipleStringLiteralsCheck::log → KILLED |
log(line, col, MSG_KEY, stringListEntry.getKey(), hits.size()); |
| 184 | } | |
| 185 | } | |
| 186 | } | |
| 187 | ||
| 188 | /** | |
| 189 | * This class contains information about where a string was found. | |
| 190 | */ | |
| 191 | private static final class StringInfo { | |
| 192 | ||
| 193 | /** | |
| 194 | * Line of finding. | |
| 195 | */ | |
| 196 | private final int line; | |
| 197 | /** | |
| 198 | * Column of finding. | |
| 199 | */ | |
| 200 | private final int col; | |
| 201 | ||
| 202 | /** | |
| 203 | * Creates information about a string position. | |
| 204 | * @param line int | |
| 205 | * @param col int | |
| 206 | */ | |
| 207 | StringInfo(int line, int col) { | |
| 208 | this.line = line; | |
| 209 | this.col = col; | |
| 210 | } | |
| 211 | ||
| 212 | /** | |
| 213 | * The line where a string was found. | |
| 214 | * @return int Line of the string. | |
| 215 | */ | |
| 216 | private int getLine() { | |
| 217 |
1
1. getLine : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return line; |
| 218 | } | |
| 219 | ||
| 220 | /** | |
| 221 | * The column where a string was found. | |
| 222 | * @return int Column of the string. | |
| 223 | */ | |
| 224 | private int getCol() { | |
| 225 |
1
1. getCol : replaced return of integer sized value with (x == 0 ? 1 : 0) → KILLED |
return col; |
| 226 | } | |
| 227 | ||
| 228 | } | |
| 229 | ||
| 230 | } | |
Mutations | ||
| 77 |
1.1 |
|
| 78 |
1.1 |
|
| 96 |
1.1 2.2 |
|
| 109 |
1.1 |
|
| 112 |
1.1 |
|
| 118 |
1.1 |
|
| 123 |
1.1 |
|
| 128 |
1.1 |
|
| 133 |
1.1 |
|
| 135 |
1.1 2.2 |
|
| 137 |
1.1 |
|
| 159 |
1.1 |
|
| 162 |
1.1 |
|
| 167 |
1.1 |
|
| 172 |
1.1 |
|
| 179 |
1.1 2.2 |
|
| 183 |
1.1 |
|
| 217 |
1.1 |
|
| 225 |
1.1 |