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.nio.file.Files;
26 import java.util.HashMap;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Properties;
30 import java.util.concurrent.atomic.AtomicInteger;
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 @StatelessCheck
44 public class UniquePropertiesCheck extends AbstractFileSetCheck {
45
46
47
48
49 public static final String MSG_KEY = "properties.duplicate.property";
50
51
52
53 public static final String MSG_IO_EXCEPTION_KEY = "unable.open.cause";
54
55
56
57
58 private static final Pattern SPACE_PATTERN = Pattern.compile(" ");
59
60
61
62
63 public UniquePropertiesCheck() {
64 setFileExtensions("properties");
65 }
66
67 @Override
68 protected void processFiltered(File file, FileText fileText) {
69 final UniqueProperties properties = new UniqueProperties();
70 try (InputStream inputStream = Files.newInputStream(file.toPath())) {
71 properties.load(inputStream);
72 }
73 catch (IOException ex) {
74 log(1, MSG_IO_EXCEPTION_KEY, file.getPath(),
75 ex.getLocalizedMessage());
76 }
77
78 for (Entry<String, AtomicInteger> duplication : properties
79 .getDuplicatedKeys().entrySet()) {
80 final String keyName = duplication.getKey();
81 final int lineNumber = getLineNumber(fileText, keyName);
82
83 log(lineNumber, MSG_KEY, keyName, duplication.getValue().get() + 1);
84 }
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98 private static int getLineNumber(FileText fileText, String keyName) {
99 final Pattern keyPattern = getKeyPattern(keyName);
100 int lineNumber = 1;
101 final Matcher matcher = keyPattern.matcher("");
102 for (int index = 0; index < fileText.size(); index++) {
103 final String line = fileText.get(index);
104 matcher.reset(line);
105 if (matcher.matches()) {
106 break;
107 }
108 ++lineNumber;
109 }
110
111
112 if (lineNumber > fileText.size() - 1) {
113 lineNumber = 1;
114 }
115 return lineNumber;
116 }
117
118
119
120
121
122
123
124
125 private static Pattern getKeyPattern(String keyName) {
126 final String keyPatternString = "^" + SPACE_PATTERN.matcher(keyName)
127 .replaceAll(Matcher.quoteReplacement("\\\\ ")) + "[\\s:=].*$";
128 return Pattern.compile(keyPatternString);
129 }
130
131
132
133
134
135
136 private static class UniqueProperties extends Properties {
137
138 private static final long serialVersionUID = 1L;
139
140
141
142
143 private final Map<String, AtomicInteger> duplicatedKeys = new HashMap<>();
144
145
146
147
148
149 @Override
150 public synchronized Object put(Object key, Object value) {
151 final Object oldValue = super.put(key, value);
152 if (oldValue != null && key instanceof String) {
153 final String keyString = (String) key;
154
155 duplicatedKeys.computeIfAbsent(keyString, empty -> new AtomicInteger(0))
156 .incrementAndGet();
157 }
158 return oldValue;
159 }
160
161
162
163
164
165
166 public Map<String, AtomicInteger> getDuplicatedKeys() {
167 return new HashMap<>(duplicatedKeys);
168 }
169
170 }
171
172 }