1 package com.github.mikkoi.maven.plugins.enforcer.rule.charsetencoding;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import com.google.common.io.ByteStreams;
23 import org.apache.maven.enforcer.rule.api.EnforcerRule;
24 import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
25 import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
26 import org.apache.maven.plugin.logging.Log;
27 import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
28
29 import javax.annotation.Nonnull;
30 import javax.annotation.Nullable;
31 import java.io.File;
32 import java.io.FileInputStream;
33 import java.io.IOException;
34 import java.nio.ByteBuffer;
35 import java.nio.charset.CharacterCodingException;
36 import java.nio.charset.Charset;
37 import java.nio.charset.CharsetDecoder;
38 import java.nio.charset.IllegalCharsetNameException;
39 import java.nio.charset.UnsupportedCharsetException;
40 import java.nio.file.FileVisitOption;
41 import java.nio.file.FileVisitResult;
42 import java.nio.file.FileVisitor;
43 import java.nio.file.Files;
44 import java.nio.file.Path;
45 import java.nio.file.Paths;
46 import java.nio.file.SimpleFileVisitor;
47 import java.nio.file.attribute.BasicFileAttributes;
48 import java.util.ArrayList;
49 import java.util.Collection;
50 import java.util.LinkedHashSet;
51 import java.util.Set;
52 import java.util.regex.Pattern;
53
54
55
56
57
58
59 @SuppressWarnings("WeakerAccess")
60 public final class CharacterSetEncodingRule implements EnforcerRule {
61
62 @Nonnull
63 private static final String INCLUDE_REGEX_DEFAULT = ".*";
64 @Nonnull
65 private static final String EXCLUDE_REGEX_DEFAULT = "";
66 @Nonnull
67 private static final String DIRECTORY_DEFAULT = "src";
68
69
70
71
72 @Nonnull
73 private final Collection<FileResult> faultyFiles = new ArrayList<>();
74
75
76
77
78 @Nullable
79 private String requireEncoding = null;
80
81
82
83 @Nullable
84 private String directory = null;
85
86
87
88 @Nullable
89 private String includeRegex = null;
90
91
92
93
94
95
96 @Nullable
97 private String excludeRegex = null;
98
99
100
101
102 @Nonnull
103 public Collection<FileResult> getFaultyFiles() {
104 return faultyFiles;
105 }
106
107
108
109
110
111 public void execute(@Nonnull final EnforcerRuleHelper helper)
112 throws EnforcerRuleException {
113 Log log = helper.getLog();
114
115 try {
116
117 String basedir = helper.evaluate("${project.basedir}").toString();
118
119 log.debug("Retrieved Basedir: " + basedir);
120 log.debug("requireEncoding: " + (requireEncoding == null ? "null" : requireEncoding));
121 log.debug("directory: " + (directory == null ? "null" : directory));
122 log.debug("includeRegex: " + (includeRegex == null ? "null" : includeRegex));
123 log.debug("excludeRegex: " + (excludeRegex == null ? "null" : excludeRegex));
124
125 if (this.getRequireEncoding() == null || this.getRequireEncoding().trim().length() == 0) {
126 final String sourceEncoding = (String) helper.evaluate("${project.build.sourceEncoding}");
127 log.info("No parameter 'requiredEncoding' set. Defaults to property 'project.build.sourceEncoding'.");
128 if (sourceEncoding != null && sourceEncoding.trim().length() > 0) {
129 this.setRequireEncoding(sourceEncoding);
130 } else {
131 throw new EnforcerRuleException("Missing parameter 'requireEncoding' and property 'project.build.sourceEncoding'.");
132 }
133 }
134 try {
135 Charset.forName(this.getRequireEncoding());
136 } catch (IllegalCharsetNameException e) {
137 throw new EnforcerRuleException("Illegal value (illegal character set name) '" + requireEncoding + "' for parameter 'requireEncoding'.");
138 } catch (UnsupportedCharsetException e) {
139 throw new EnforcerRuleException("Illegal value (not supported character set) '" + requireEncoding + "' for parameter 'requireEncoding'.");
140 } catch (IllegalArgumentException e) {
141 throw new EnforcerRuleException("Illegal value (empty) '" + requireEncoding + "' for parameter 'requireEncoding'.");
142 }
143 if (this.getDirectory() == null || this.getDirectory().trim().length() == 0) {
144 log.info("No parameter 'directory' set. Defaults to '" + DIRECTORY_DEFAULT + "'.");
145 this.setDirectory(DIRECTORY_DEFAULT);
146 }
147 if (this.getIncludeRegex() == null || this.getIncludeRegex().trim().length() == 0) {
148 log.info("No parameter 'includeRegex' set. Defaults to '" + INCLUDE_REGEX_DEFAULT + "'.");
149 this.setIncludeRegex(INCLUDE_REGEX_DEFAULT);
150 }
151 if (this.getExcludeRegex() == null || this.getExcludeRegex().trim().length() == 0) {
152 log.info("No parameter 'excludeRegex' set. Defaults to '" + EXCLUDE_REGEX_DEFAULT + "'.");
153 this.setExcludeRegex(EXCLUDE_REGEX_DEFAULT);
154 }
155 log.debug("requireEncoding: " + this.getRequireEncoding());
156 log.debug("directory: " + this.getDirectory());
157 log.debug("includeRegex: " + this.getIncludeRegex());
158 log.debug("excludeRegex: " + this.getExcludeRegex());
159
160
161 final Path dir = Paths.get(basedir, getDirectory());
162 log.debug("Get files in dir '" + dir.toString() + "'.");
163 if (!dir.toFile().exists()) {
164 throw new EnforcerRuleException(
165 "Directory '" + dir.toString() + "' not found."
166 + " Specified by parameter 'directory' (value: '" + this.getDirectory() + "')!"
167 );
168 }
169
170
171 Collection<FileResult> allFiles = getFileResults(log, dir);
172
173
174 log.debug("Moving possible faulty files (faulty encoding) to another list.");
175 for (FileResult res : allFiles) {
176 log.debug("Checking if file '" + res.getPath().toString() + "' has encoding '" + requireEncoding + "'.");
177 boolean hasCorrectEncoding = true;
178 try (FileInputStream fileInputStream = new FileInputStream(res.getPath().toFile())) {
179 byte[] bytes = ByteStreams.toByteArray(fileInputStream);
180 Charset charset = Charset.forName(this.getRequireEncoding());
181 CharsetDecoder decoder = charset.newDecoder();
182 ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
183 decoder.decode(byteBuffer);
184 } catch (CharacterCodingException e) {
185 hasCorrectEncoding = false;
186 } catch (IOException e) {
187 e.printStackTrace();
188 log.error(e.getMessage());
189 hasCorrectEncoding = false;
190 }
191 if (!hasCorrectEncoding) {
192 log.debug("Moving faulty file: " + res.getPath());
193 FileResult faultyFile = new FileResult.Builder(res.getPath())
194 .lastModified(res.getLastModified())
195 .build();
196 faultyFiles.add(faultyFile);
197 } else {
198 log.debug("Has correct encoding. Not moving to faulty files list.");
199 }
200 }
201 log.debug("All faulty files moved.");
202
203
204 if (!faultyFiles.isEmpty()) {
205 final StringBuilder builder = new StringBuilder();
206 builder.append("Wrong encoding in following files:");
207 builder.append(System.getProperty("line.separator"));
208 for (FileResult res : faultyFiles) {
209 builder.append(res.getPath());
210 builder.append(System.getProperty("line.separator"));
211 }
212 throw new EnforcerRuleException(builder.toString());
213 }
214 } catch (ExpressionEvaluationException e) {
215 throw new EnforcerRuleException(
216 "Unable to lookup an expression " + e.getLocalizedMessage(), e
217 );
218 }
219 }
220
221 @Nonnull
222 private Collection<FileResult> getFileResults(final Log log, final Path dir) {
223 Collection<FileResult> allFiles = new ArrayList<>();
224 FileVisitor<Path> fileVisitor = new GetEncodingsFileVisitor(
225 log,
226 this.getIncludeRegex() != null ? this.getIncludeRegex() : INCLUDE_REGEX_DEFAULT,
227 this.getExcludeRegex() != null ? this.getExcludeRegex() : EXCLUDE_REGEX_DEFAULT,
228 allFiles
229 );
230 try {
231 Set<FileVisitOption> visitOptions = new LinkedHashSet<>();
232 visitOptions.add(FileVisitOption.FOLLOW_LINKS);
233 Files.walkFileTree(dir,
234 visitOptions,
235 Integer.MAX_VALUE,
236 fileVisitor
237 );
238 } catch (Exception e) {
239 log.error(e.getCause() + e.getMessage());
240 }
241 return allFiles;
242 }
243
244
245
246
247
248
249
250
251
252
253
254
255 @Nullable
256 public String getCacheId() {
257 return String.valueOf(false);
258 }
259
260
261
262
263
264
265
266
267
268 public boolean isCacheable() {
269 return false;
270 }
271
272
273
274
275
276
277
278
279
280
281
282 public boolean isResultValid(@Nullable final EnforcerRule arg0) {
283 return false;
284 }
285
286
287
288
289
290 @SuppressWarnings("WeakerAccess")
291 @Nullable
292 public String getDirectory() {
293 return directory;
294 }
295
296 @SuppressWarnings("WeakerAccess")
297 public void setDirectory(@Nullable final String directory) {
298 this.directory = directory;
299 }
300
301 @SuppressWarnings("WeakerAccess")
302 @Nullable
303 public String getIncludeRegex() {
304 return includeRegex;
305 }
306
307 @SuppressWarnings("WeakerAccess")
308 public void setIncludeRegex(@Nullable final String includeRegex) {
309 this.includeRegex = includeRegex;
310 }
311
312 @SuppressWarnings("WeakerAccess")
313 @Nullable
314 public String getExcludeRegex() {
315 return excludeRegex;
316 }
317
318 @SuppressWarnings("WeakerAccess")
319 public void setExcludeRegex(@Nullable final String excludeRegex) {
320 this.excludeRegex = excludeRegex;
321 }
322
323 @SuppressWarnings("WeakerAccess")
324 @Nullable
325 public String getRequireEncoding() {
326 return requireEncoding;
327 }
328
329 @SuppressWarnings("WeakerAccess")
330 public void setRequireEncoding(@Nullable final String requireEncoding) {
331 this.requireEncoding = requireEncoding;
332 }
333
334
335
336
337 private static class GetEncodingsFileVisitor extends SimpleFileVisitor<Path> {
338 @Nonnull
339 private final Log log;
340 private final boolean includeRegexUsed;
341 @Nonnull
342 private final Pattern includeRegexPattern;
343 private final boolean excludeRegexUsed;
344 @Nonnull
345 private final Pattern excludeRegexPattern;
346 @Nonnull
347 private final Collection<FileResult> results;
348
349
350
351
352
353
354
355
356
357 GetEncodingsFileVisitor(
358 @Nonnull final Log pluginLog,
359 @Nonnull final String includeRegex,
360 @Nonnull final String excludeRegex,
361 @Nonnull final Collection<FileResult> fileResults
362 ) {
363 this.log = pluginLog;
364
365
366
367 includeRegexUsed = true;
368 includeRegexPattern = Pattern.compile(includeRegex);
369 if (excludeRegex.length() > 0) {
370 excludeRegexUsed = true;
371 excludeRegexPattern = Pattern.compile(excludeRegex);
372 } else {
373 excludeRegexUsed = false;
374 excludeRegexPattern = Pattern.compile("");
375 }
376 this.results = fileResults;
377 }
378
379 @Override
380 public FileVisitResult visitFile(
381 final Path aFile, final BasicFileAttributes aAttrs
382 ) throws IOException {
383 log.debug("Visiting file '" + aFile.toString() + "'.");
384 if (includeRegexUsed && !includeRegexPattern.matcher(aFile.toString()).find()) {
385 log.debug("File not matches includeRegex in-filter. Exclude file from list!");
386 return FileVisitResult.CONTINUE;
387 }
388 if (excludeRegexUsed && excludeRegexPattern.matcher(aFile.toString()).find()) {
389 log.debug("File matches excludeRegex out-filter. Exclude file from list!");
390 return FileVisitResult.CONTINUE;
391 }
392 log.debug("File matches includeRegex in-filter and not matches excludeRegex out-filter. Include file to list!");
393 File file = aFile.toFile();
394 FileResult res = new FileResult.Builder(aFile.toAbsolutePath())
395 .lastModified(file.lastModified())
396 .build();
397 results.add(res);
398 return FileVisitResult.CONTINUE;
399 }
400
401 @Override
402 public FileVisitResult preVisitDirectory(
403 final Path aDir, final BasicFileAttributes aAttrs
404 ) throws IOException {
405 log.debug("Visiting directory '" + aDir.toString() + "'.");
406 return FileVisitResult.CONTINUE;
407 }
408
409 }
410
411 }