1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks;
21
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.Serial;
26 import java.nio.file.Files;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Properties;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33
34 import com.puppycrawl.tools.checkstyle.StatelessCheck;
35 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
36 import com.puppycrawl.tools.checkstyle.api.FileText;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51 @StatelessCheck
52 public class UniquePropertiesCheck extends AbstractFileSetCheck {
53
54
55
56
57 public static final String MSG_KEY = "properties.duplicate.property";
58
59
60
61 public static final String MSG_IO_EXCEPTION_KEY = "unable.open.cause";
62
63
64
65
66 private static final Pattern SPACE_PATTERN = Pattern.compile(" ");
67
68
69
70
71 public UniquePropertiesCheck() {
72 setFileExtensions("properties");
73 }
74
75
76
77
78
79
80
81
82 @Override
83 public final void setFileExtensions(String... extensions) {
84 super.setFileExtensions(extensions);
85 }
86
87 @Override
88 protected void processFiltered(File file, FileText fileText) {
89 final UniqueProperties properties = new UniqueProperties();
90 try (InputStream inputStream = Files.newInputStream(file.toPath())) {
91 properties.load(inputStream);
92 }
93 catch (IOException exc) {
94 log(1, MSG_IO_EXCEPTION_KEY, file.getPath(),
95 exc.getLocalizedMessage());
96 }
97
98 for (Entry<String, Integer> duplication : properties
99 .getDuplicatedKeys().entrySet()) {
100 final String keyName = duplication.getKey();
101 final int lineNumber = getLineNumber(fileText, keyName);
102
103 log(lineNumber, MSG_KEY, keyName, duplication.getValue() + 1);
104 }
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118 private static int getLineNumber(FileText fileText, String keyName) {
119 final Pattern keyPattern = getKeyPattern(keyName);
120 int lineNumber = 1;
121 final Matcher matcher = keyPattern.matcher("");
122 for (int index = 0; index < fileText.size(); index++) {
123 final String line = fileText.get(index);
124 matcher.reset(line);
125 if (matcher.matches()) {
126 break;
127 }
128 ++lineNumber;
129 }
130
131
132 if (lineNumber > fileText.size() - 1) {
133 lineNumber = 1;
134 }
135 return lineNumber;
136 }
137
138
139
140
141
142
143
144
145 private static Pattern getKeyPattern(String keyName) {
146 final String keyPatternString = "^" + SPACE_PATTERN.matcher(keyName)
147 .replaceAll(Matcher.quoteReplacement("\\\\ ")) + "[\\s:=].*$";
148 return Pattern.compile(keyPatternString);
149 }
150
151
152
153
154
155
156
157
158 private static final class UniqueProperties extends Properties {
159
160
161 @Serial
162 private static final long serialVersionUID = 1L;
163
164
165
166
167 private final Map<String, Integer> duplicatedKeys = new HashMap<>();
168
169
170
171
172 @Override
173 public synchronized Object put(Object key, Object value) {
174 final Object oldValue = super.put(key, value);
175 if (oldValue != null && key instanceof String keyString) {
176
177 duplicatedKeys.put(keyString,
178 duplicatedKeys.getOrDefault(keyString, 0) + 1);
179 }
180 return oldValue;
181 }
182
183
184
185
186
187
188 public Map<String, Integer> getDuplicatedKeys() {
189 return new HashMap<>(duplicatedKeys);
190 }
191
192 }
193
194 }