1 package com.github.mikkoi.maven.plugins.enforcer.rule.propertyusage;
2
3 import com.github.mikkoi.maven.plugins.enforcer.rule.propertyusage.UsageFiles.UsageLocation;
4 import com.github.mikkoi.maven.plugins.enforcer.rule.propertyusage.configuration.Definitions;
5 import com.github.mikkoi.maven.plugins.enforcer.rule.propertyusage.configuration.FileSpecs;
6 import com.github.mikkoi.maven.plugins.enforcer.rule.propertyusage.configuration.Templates;
7 import com.github.mikkoi.maven.plugins.enforcer.rule.propertyusage.configuration.Usages;
8 import org.apache.maven.enforcer.rule.api.EnforcerRule;
9 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
10 import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
11 import org.apache.maven.plugin.logging.Log;
12 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
13 import org.codehaus.plexus.util.StringUtils;
14
15 import javax.annotation.Nonnull;
16 import javax.annotation.Nullable;
17 import java.io.IOException;
18 import java.nio.charset.Charset;
19 import java.nio.file.Path;
20 import java.nio.file.Paths;
21 import java.util.Collection;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.concurrent.ConcurrentHashMap;
26 import java.util.stream.Collectors;
27
28
29
30
31 public final class PropertyUsageRule implements EnforcerRule {
32
33
34
35
36 private static final Charset DEFAULT_CHAR_SET = Charset.forName("UTF-8");
37
38
39
40
41 @Nonnull
42 private final Map<String, Integer> propertiesDefinedMoreThanOnce = new ConcurrentHashMap<>();
43
44
45
46
47 @Nonnull
48 private final Set<String> propertiesNotUsed = new HashSet<>();
49
50
51
52
53 @Nonnull
54 private final Set<UsageFiles.UsageLocation> propertiesNotDefined = new HashSet<>();
55
56
57
58
59 @Nonnull
60 private final Map<String, Set<PropertyDefinition>> propertiesDefined = new ConcurrentHashMap<>();
61
62
63
64
65 Log log;
66
67
68
69
70
71
72
73
74
75
76 private String sourceEncoding = DEFAULT_CHAR_SET.toString();
77
78
79
80
81 private String propertiesEncoding = DEFAULT_CHAR_SET.toString();
82
83
84
85
86 private boolean definitionsOnlyOnce = true;
87
88
89
90
91 private boolean definedPropertiesAreUsed = true;
92
93
94
95
96 private boolean usedPropertiesAreDefined = false;
97
98
99
100
101 private boolean reportDuplicateDefinitions = false;
102
103
104
105
106 @Nonnull
107 private String replaceInTemplateWithPropertyName = Templates.DEFAULT_REPLACE_IN_TEMPLATE_WITH_PROPERTY_NAME;
108
109
110
111
112
113 @Nonnull
114 private String propertyNameRegexp = Templates.PROPERTY_NAME_REGEXP;
115
116
117
118
119 @Nonnull
120 private Collection<String> definitions = Definitions.getDefault();
121
122
123
124 @Nonnull
125 private Collection<String> templates = Templates.getDefault();
126
127
128
129 @Nonnull
130 private Collection<String> usages = Usages.getDefault();
131
132
133
134
135
136 @Override
137 @SuppressWarnings({
138 "squid:S3776",
139 "squid:S1067",
140 "squid:S1192",
141 "squid:MethodCyclomaticComplexity"
142 })
143
144 public void execute(@Nonnull final EnforcerRuleHelper helper)
145 throws EnforcerRuleException {
146 log = helper.getLog();
147
148 Path basedir = Paths.get("");
149 try {
150 basedir = Paths.get(helper.evaluate("${project.basedir}").toString());
151 } catch (ExpressionEvaluationException e) {
152 log.error("Cannot get property 'project.basedir'. Using current working directory. Error:" + e);
153 }
154
155 Charset propertiesEnc = DEFAULT_CHAR_SET;
156 Charset sourceEnc = DEFAULT_CHAR_SET;
157 try {
158 if (StringUtils.isNotBlank(propertiesEncoding)) {
159 propertiesEnc = Charset.forName(propertiesEncoding);
160 } else {
161 propertiesEnc = Charset.forName(helper.evaluate("${project.build.sourceEncoding}").toString());
162 }
163 if (StringUtils.isNotBlank(sourceEncoding)) {
164 sourceEnc = Charset.forName(sourceEncoding);
165 } else {
166 sourceEnc = Charset.forName(helper.evaluate("${project.build.sourceEncoding}").toString());
167 }
168 } catch (ExpressionEvaluationException e) {
169 log.error("Cannot get property 'project.build.sourceEncoding'. Using default (UTF-8). Error:" + e);
170 }
171
172 log.debug("PropertyUsageRule:execute() - Settings:");
173 log.debug("basedir:" + basedir);
174 log.debug("propertiesEnc:" + propertiesEnc);
175 log.debug("sourceEnc:" + sourceEnc);
176 log.debug("replaceInTemplateWithPropertyName:" + replaceInTemplateWithPropertyName);
177 log.debug("propertyNameRegexp:" + propertyNameRegexp);
178 log.debug("definitions:" + definitions);
179 log.debug("templates:" + templates);
180 log.debug("usages:" + usages);
181
182 try {
183 log.debug("PropertyUsageRule:execute() - Run:");
184
185
186 log.debug("definitions:");
187 definitions.stream().forEach(a -> log.debug(a));
188 log.debug(":END");
189 final Collection<String> propertyFilenames = FileSpecs.getAbsoluteFilenames(definitions, basedir, log)
190 .stream().sorted().collect(Collectors.toSet());
191
192 Map<String, Set<PropertyDefinition>> definedProperties = getPropertiesDefined(propertiesEnc, propertyFilenames);
193 definedProperties.forEach((prop, defs) -> {
194 log.debug("Property '" + prop + "' defined " + defs.size() + " times.");
195 if (defs.size() > 1) {
196 propertiesDefinedMoreThanOnce.put(prop, defs.size());
197 }
198 });
199
200
201
202 final Collection<String> usageFilenames = FileSpecs.getAbsoluteFilenames(usages, basedir, log)
203 .stream().sorted().collect(Collectors.toSet());
204
205
206 final UsageFiles usageFiles = new UsageFiles(log);
207 if (definedPropertiesAreUsed) {
208 log.debug("definedPropertiesAreUsed");
209 final Map<String, String> readyTemplates = new ConcurrentHashMap<>();
210 templates.forEach(tpl -> definedProperties.forEach(
211 (propertyDefinition, nrPropertyDefinitions) ->
212 readyTemplates.put(
213 tpl.replaceAll(replaceInTemplateWithPropertyName, propertyDefinition),
214 propertyDefinition
215 )
216 )
217 );
218 log.debug("readyTemplates:" + readyTemplates);
219 final Collection<String> usedProperties
220 = usageFiles.readDefinedUsagesFromFiles(usageFilenames, readyTemplates, sourceEnc);
221 definedProperties.forEach((prop, nrOf) -> {
222 if (!usedProperties.contains(prop)) {
223 log.debug("Property " + prop + " not used.");
224 propertiesNotUsed.add(prop);
225 }
226 });
227 }
228 if (usedPropertiesAreDefined) {
229 log.debug("usedPropertiesAreDefined");
230 final Set<String> readyTemplates = new HashSet<>();
231 templates.forEach(tpl -> readyTemplates.add(
232 tpl.replaceAll(replaceInTemplateWithPropertyName, propertyNameRegexp)
233 )
234 );
235 log.debug("readyTemplates:" + readyTemplates);
236 final Collection<UsageLocation> usageLocations
237 = usageFiles.readAllUsagesFromFiles(usageFilenames, readyTemplates, sourceEnc);
238 usageLocations.forEach(loc -> {
239 if (definedProperties.containsKey(loc.getProperty())) {
240 log.debug("Property " + loc.getProperty() + " defined.");
241 } else {
242 log.debug("Property " + loc.getProperty() + " not defined.");
243 propertiesNotDefined.add(loc);
244 }
245 });
246 }
247 } catch (IOException e) {
248 throw new EnforcerRuleException(
249 "IO error: " + e.getLocalizedMessage(), e
250 );
251 }
252
253
254
255 if (definitionsOnlyOnce) {
256 propertiesDefinedMoreThanOnce.forEach((key, value) ->
257 log.error("Property '" + key + "' defined " + value + " times!")
258 );
259 }
260
261 if (definedPropertiesAreUsed) {
262 propertiesNotUsed.forEach(key ->
263 log.error("Property '" + key + "' not used!")
264 );
265 }
266
267 if (usedPropertiesAreDefined) {
268 propertiesNotDefined.forEach(loc ->
269 log.error("Property '" + loc.getProperty() + "' used without defining it ("
270 + loc.getFilename() + ":" + loc.getRow() + ")"));
271 }
272
273 if (reportDuplicateDefinitions) {
274 propertiesDefined.entrySet().stream().filter(entry -> entry.getValue().size() > 1).forEach(
275 entry -> entry.getValue().forEach(
276 propDef -> log.info("Defined '" + propDef.getKey() + "' with value '" + propDef.getValue()
277 + "' in " + propDef.getFilename() + ":" + propDef.getLinenumber())
278 )
279 );
280 }
281
282
283 if (definedPropertiesAreUsed && !propertiesNotUsed.isEmpty()
284 || usedPropertiesAreDefined && !propertiesNotDefined.isEmpty()
285 || definitionsOnlyOnce && !propertiesDefinedMoreThanOnce.isEmpty()
286 ) {
287 throw new EnforcerRuleException(
288 "Errors in property definitions or usage!"
289 );
290 }
291 }
292
293 private Map<String, Integer> getPropertiesDefinedMoreThanOnce(final Charset propertiesEnc, final Collection<String> propertyFilenames) throws IOException {
294 Map<String, Integer> definedProperties;
295 if (definitionsOnlyOnce) {
296 definedProperties = new PropertyFiles(log, propertiesEnc).readPropertiesFromFilesWithCount(propertyFilenames);
297 definedProperties.forEach((prop, nrOf) -> {
298 log.debug("Property '" + prop + "' defined " + nrOf + " times.");
299 if (nrOf > 1) {
300 propertiesDefinedMoreThanOnce.put(prop, nrOf);
301 }
302 });
303 } else {
304 definedProperties = new PropertyFiles(log, propertiesEnc).readPropertiesFromFilesWithoutCount(propertyFilenames);
305 }
306 return definedProperties;
307 }
308
309 private Map<String, Set<PropertyDefinition>> getPropertiesDefined(final Charset propertiesEnc, final Collection<String> propertyFilenames) throws IOException {
310 Map<String, Set<PropertyDefinition>> definedProperties;
311 definedProperties = new PropertyFiles(log, propertiesEnc).readPropertiesFromFilesGetDefinitions(propertyFilenames);
312 return definedProperties;
313 }
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330 @Override
331 @Nullable
332 public String getCacheId() {
333 return String.valueOf(false);
334 }
335
336
337
338
339
340
341
342
343
344
345 @Override
346 public boolean isCacheable() {
347 return false;
348 }
349
350
351
352
353
354
355
356
357
358
359
360
361 @Override
362 public boolean isResultValid(@Nullable final EnforcerRule arg0) {
363 return false;
364 }
365
366
367
368
369
370 @Nonnull
371 public Set<String> getPropertiesNotUsed() {
372 return propertiesNotUsed;
373 }
374
375 @Nonnull
376 public Set<UsageFiles.UsageLocation> getPropertiesNotDefined() {
377 return propertiesNotDefined;
378 }
379
380 @Nonnull
381 public Map<String, Integer> getPropertiesDefinedMoreThanOnce() {
382 return propertiesDefinedMoreThanOnce;
383 }
384
385 @Nonnull
386 public Map<String, Set<PropertyDefinition>> getPropertiesDefined() {
387 return propertiesDefined;
388 }
389
390 public boolean isDefinedPropertiesAreUsed() {
391 return definedPropertiesAreUsed;
392 }
393
394 public void setDefinedPropertiesAreUsed(final boolean definedPropertiesAreUsed) {
395 this.definedPropertiesAreUsed = definedPropertiesAreUsed;
396 }
397
398 public boolean isUsedPropertiesAreDefined() {
399 return usedPropertiesAreDefined;
400 }
401
402 public void setUsedPropertiesAreDefined(final boolean usedPropertiesAreDefined) {
403 this.usedPropertiesAreDefined = usedPropertiesAreDefined;
404 }
405
406 public boolean isDefinitionsOnlyOnce() {
407 return definitionsOnlyOnce;
408 }
409
410 public void setDefinitionsOnlyOnce(final boolean definitionsOnlyOnce) {
411 this.definitionsOnlyOnce = definitionsOnlyOnce;
412 }
413
414 @Nonnull
415 public String getReplaceInTemplateWithPropertyName() {
416 return replaceInTemplateWithPropertyName;
417 }
418
419 public void setReplaceInTemplateWithPropertyName(@Nonnull final String replaceInTemplateWithPropertyName) {
420 this.replaceInTemplateWithPropertyName = replaceInTemplateWithPropertyName;
421 }
422
423 @Nonnull
424 public Collection<String> getDefinitions() {
425 return definitions;
426 }
427
428
429
430
431
432
433 public void setDefinitions(@Nonnull final Collection<String> definitions) {
434 this.definitions = definitions;
435 }
436
437 @Nonnull
438 public Collection<String> getTemplates() {
439 return templates;
440 }
441
442 public void setTemplates(@Nonnull final Collection<String> templates) {
443 this.templates = templates;
444 }
445
446 @Nonnull
447 public Collection<String> getUsages() {
448 return usages;
449 }
450
451 public void setUsages(@Nonnull final Collection<String> usages) {
452 this.usages = usages;
453 }
454 }