View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.rat.mp;
20  
21  import static org.apache.rat.mp.util.ExclusionHelper.addEclipseDefaults;
22  import static org.apache.rat.mp.util.ExclusionHelper.addIdeaDefaults;
23  import static org.apache.rat.mp.util.ExclusionHelper.addMavenDefaults;
24  import static org.apache.rat.mp.util.ExclusionHelper.addPlexusAndScmDefaults;
25  
26  import java.io.BufferedInputStream;
27  import java.io.BufferedReader;
28  import java.io.File;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.InputStreamReader;
32  import java.io.Reader;
33  import java.lang.reflect.UndeclaredThrowableException;
34  import java.net.MalformedURLException;
35  import java.net.URL;
36  import java.nio.file.Files;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Collection;
40  import java.util.Collections;
41  import java.util.List;
42  import java.util.Objects;
43  import java.util.SortedSet;
44  import java.util.function.Consumer;
45  import java.util.stream.Collectors;
46  import java.util.stream.Stream;
47  
48  import org.apache.commons.lang3.StringUtils;
49  import org.apache.maven.plugin.AbstractMojo;
50  import org.apache.maven.plugin.MojoExecutionException;
51  import org.apache.maven.plugin.logging.Log;
52  import org.apache.maven.plugins.annotations.Parameter;
53  import org.apache.maven.project.MavenProject;
54  import org.apache.rat.ConfigurationException;
55  import org.apache.rat.Defaults;
56  import org.apache.rat.ReportConfiguration;
57  import org.apache.rat.analysis.license.DeprecatedConfig;
58  import org.apache.rat.config.SourceCodeManagementSystems;
59  import org.apache.rat.configuration.Format;
60  import org.apache.rat.configuration.LicenseReader;
61  import org.apache.rat.configuration.MatcherReader;
62  import org.apache.rat.license.ILicense;
63  import org.apache.rat.license.ILicenseFamily;
64  import org.apache.rat.license.LicenseSetFactory.LicenseFilter;
65  import org.apache.rat.license.SimpleLicenseFamily;
66  import org.apache.rat.mp.util.ScmIgnoreParser;
67  import org.apache.rat.mp.util.ignore.GlobIgnoreMatcher;
68  import org.apache.rat.mp.util.ignore.IgnoreMatcher;
69  import org.apache.rat.mp.util.ignore.IgnoringDirectoryScanner;
70  import org.apache.rat.report.IReportable;
71  import org.codehaus.plexus.util.DirectoryScanner;
72  
73  /**
74   * Abstract base class for Mojos, which are running Rat.
75   */
76  public abstract class AbstractRatMojo extends AbstractMojo {
77  
78      /**
79       * The base directory, in which to search for files.
80       */
81      @Parameter(property = "rat.basedir", defaultValue = "${basedir}", required = true)
82      private File basedir;
83  
84      /**
85       * Specifies the licenses to accept. By default, these are added to the default
86       * licenses, unless you set {@link #addDefaultLicenseMatchers} to false.
87       *
88       * @since 0.8
89       */
90      @Parameter
91      private String[] defaultLicenseFiles;
92  
93      @Parameter
94      private String[] additionalLicenseFiles;
95  
96      /**
97       * Whether to add the default list of licenses.
98       */
99      @Parameter(property = "rat.addDefaultLicenses", defaultValue = "true")
100     private boolean addDefaultLicenses;
101 
102     /**
103      * Whether to add the default list of license matchers.
104      */
105     @Parameter(property = "rat.addDefaultLicenseMatchers", defaultValue = "true")
106     private boolean addDefaultLicenseMatchers;
107 
108     @Parameter(required = false)
109     private String[] approvedLicenses;
110 
111     @Parameter(property = "rat.approvedFile")
112     private String approvedLicenseFile;
113     
114     /**
115      * Specifies the license families to accept.
116      *
117      * @since 0.8
118      * @deprecated
119      */
120     @Deprecated // remove in v1.0
121     @Parameter
122     private SimpleLicenseFamily[] licenseFamilies;
123     
124     /**
125      * This is an object to accept both License of DeprecatedConfig objects.
126      */
127     @Deprecated // convert this to an org.apache.rat.mp.License object in v1.0
128     @Parameter
129     private Object[] licenses;
130     
131     @Parameter
132     private Family[] families;
133 
134     /**
135      * Specifies files, which are included in the report. By default, all files are
136      * included.
137      */
138     @Parameter
139     private String[] includes;
140 
141     /**
142      * Specifies a file, from which to read includes. Basically, an alternative to
143      * specifying the includes as a list.
144      */
145     @Parameter(property = "rat.includesFile")
146     private String includesFile;
147 
148     /**
149      * Specifies the include files character set. Defaults
150      * to @code{${project.build.sourceEncoding}), or @code{UTF8}.
151      */
152     @Parameter(property = "rat.includesFileCharset", defaultValue = "${project.build.sourceEncoding}")
153     private String includesFileCharset;
154 
155     /**
156      * Specifies files, which are excluded in the report. By default, no files are
157      * excluded.
158      */
159     @Parameter
160     private String[] excludes;
161 
162     /**
163      * Specifies a file, from which to read excludes. Basically, an alternative to
164      * specifying the excludes as a list. The excludesFile is assumed to be using
165      * the UFT8 character set.
166      */
167     @Parameter(property = "rat.excludesFile")
168     private String excludesFile;
169 
170     /**
171      * Specifies the include files character set. Defaults
172      * to @code{${project.build.sourceEncoding}), or @code{UTF8}.
173      */
174     @Parameter(property = "rat.excludesFileCharset", defaultValue = "${project.build.sourceEncoding}")
175     private String excludesFileCharset;
176 
177     /**
178      * Whether to use the default excludes when scanning for files. The default
179      * excludes are:
180      * <ul>
181      * <li>meta data files for source code management / revision control systems,
182      * see {@link SourceCodeManagementSystems}</li>
183      * <li>temporary files used by Maven, see
184      * <a href="#useMavenDefaultExcludes">useMavenDefaultExcludes</a></li>
185      * <li>configuration files for Eclipse, see
186      * <a href="#useEclipseDefaultExcludes">useEclipseDefaultExcludes</a></li>
187      * <li>configuration files for IDEA, see
188      * <a href="#useIdeaDefaultExcludes">useIdeaDefaultExcludes</a></li>
189      * </ul>
190      */
191     @Parameter(property = "rat.useDefaultExcludes", defaultValue = "true")
192     private boolean useDefaultExcludes;
193 
194     /**
195      * Whether to use the Maven specific default excludes when scanning for files.
196      * Maven specific default excludes are given by the constant
197      * MAVEN_DEFAULT_EXCLUDES: The <code>target</code> directory, the
198      * <code>cobertura.ser</code> file, and so on.
199      */
200     @Parameter(property = "rat.useMavenDefaultExcludes", defaultValue = "true")
201     private boolean useMavenDefaultExcludes;
202 
203     /**
204      * Whether to parse source code management system (SCM) ignore files and use
205      * their contents as excludes. At the moment this works for the following SCMs:
206      *
207      * @see org.apache.rat.config.SourceCodeManagementSystems
208      */
209     @Parameter(property = "rat.parseSCMIgnoresAsExcludes", defaultValue = "true")
210     private boolean parseSCMIgnoresAsExcludes;
211 
212     /**
213      * Whether to use the Eclipse specific default excludes when scanning for files.
214      * Eclipse specific default excludes are given by the constant
215      * ECLIPSE_DEFAULT_EXCLUDES: The <code>.classpath</code> and
216      * <code>.project</code> files, the <code>.settings</code> directory, and so on.
217      */
218     @Parameter(property = "rat.useEclipseDefaultExcludes", defaultValue = "true")
219     private boolean useEclipseDefaultExcludes;
220 
221     /**
222      * Whether to use the IDEA specific default excludes when scanning for files.
223      * IDEA specific default excludes are given by the constant
224      * IDEA_DEFAULT_EXCLUDES: The <code>*.iml</code>, <code>*.ipr</code> and
225      * <code>*.iws</code> files and the <code>.idea</code> directory.
226      */
227     @Parameter(property = "rat.useIdeaDefaultExcludes", defaultValue = "true")
228     private boolean useIdeaDefaultExcludes;
229 
230     /**
231      * Whether to exclude subprojects. This is recommended, if you want a separate
232      * apache-rat-plugin report for each subproject.
233      */
234     @Parameter(property = "rat.excludeSubprojects", defaultValue = "true")
235     private boolean excludeSubProjects;
236 
237     /**
238      * Will skip the plugin execution, e.g. for technical builds that do not take
239      * license compliance into account.
240      *
241      * @since 0.11
242      */
243     @Parameter(property = "rat.skip", defaultValue = "false")
244     protected boolean skip;
245 
246     /**
247      * Holds the maven-internal project to allow resolution of artifact properties
248      * during mojo runs.
249      */
250     @Parameter(defaultValue = "${project}", required = true, readonly = true)
251     protected MavenProject project;
252 
253     /**
254      * @return Returns the Maven project.
255      */
256     protected MavenProject getProject() {
257         return project;
258     }
259 
260     protected Defaults.Builder getDefaultsBuilder() {
261         Defaults.Builder result = Defaults.builder();
262         if (defaultLicenseFiles != null) {
263             for (String defaultLicenseFile : defaultLicenseFiles) {
264                 try {
265                     result.add(defaultLicenseFile);
266                 } catch (MalformedURLException e) {
267                     throw new ConfigurationException(defaultLicenseFile + " is not a valid license file", e);
268                 }
269             }
270         }
271         return result;
272     }
273     
274     @Deprecated // remove this for version 1.0
275     private Stream<License> getLicenses() {
276         if (licenses == null) {
277             return Stream.empty();
278         }
279         return Arrays.stream(licenses).filter( s -> {return s instanceof License;}).map(License.class::cast);
280     }
281 
282     @Deprecated // remove this for version 1.0
283     private Stream<DeprecatedConfig> getDeprecatedConfigs() {
284         if (licenses == null) {
285             return Stream.empty();
286         }
287         return Arrays.stream(licenses).filter( s -> {return s instanceof DeprecatedConfig;}).map(DeprecatedConfig.class::cast);
288     }
289     
290     @Deprecated // remove this for version 1.0
291     private void reportDeprecatedProcessing()
292     {
293         if (getDeprecatedConfigs().findAny().isPresent()) {
294             Log log = getLog();
295             log.warn("Configuration uses deprecated configuration.  Please upgrade to v0.17 configuration options");
296         }
297     }
298     
299     @Deprecated // remove this for version 1.0
300     private void processLicenseFamilies(ReportConfiguration config) {
301         List<ILicenseFamily> families = getDeprecatedConfigs().map(DeprecatedConfig::getLicenseFamily).filter(Objects::nonNull).collect(Collectors.toList());
302         if (licenseFamilies != null) {
303             for (SimpleLicenseFamily slf : licenseFamilies) {
304                 if (StringUtils.isBlank(slf.getFamilyCategory())) {
305                     families.stream().filter( f -> f.getFamilyName().equalsIgnoreCase(slf.getFamilyName())).findFirst()
306                     .ifPresent(config::addApprovedLicenseCategory);
307                 } else {
308                     config.addApprovedLicenseCategory(ILicenseFamily.builder().setLicenseFamilyCategory(slf.getFamilyCategory())
309                     .setLicenseFamilyName(StringUtils.defaultIfBlank(slf.getFamilyName(), slf.getFamilyCategory()))
310                     .build());
311                 }
312             }
313         }
314     }
315     
316     private org.apache.rat.utils.Log makeLog() {
317         return new org.apache.rat.utils.Log() {
318             Log log = getLog();
319             @Override
320             public void log(Level level, String msg) {
321                 switch (level)
322                 {
323                 case DEBUG:
324                     log.debug(msg);
325                     break;
326                 case INFO:
327                     log.info(msg);;
328                     break;
329                 case WARN:
330                     log.warn(msg);
331                     break;
332                 case ERROR:
333                     log.error(msg);
334                     break;
335             }
336             }};
337     }
338     
339     protected ReportConfiguration getConfiguration() throws MojoExecutionException {
340         ReportConfiguration config = new ReportConfiguration(makeLog());
341         reportDeprecatedProcessing();
342         if (addDefaultLicenses) {
343             config.setFrom(getDefaultsBuilder().build());
344         } else {
345             config.setStyleSheet(Defaults.getPlainStyleSheet());
346         }
347         if (additionalLicenseFiles != null) {
348             for (String licenseFile : additionalLicenseFiles) {
349                 try {
350                     URL url = new File(licenseFile).toURI().toURL();
351                     Format fmt = Format.fromName(licenseFile);
352                     MatcherReader mReader = fmt.matcherReader();
353                     if (mReader != null) {
354                         mReader.addMatchers(url);
355                     }
356                     LicenseReader lReader = fmt.licenseReader();
357                     if (lReader != null) {
358                             lReader.addLicenses(url);
359                     config.addLicenses(lReader.readLicenses());
360                     config.addApprovedLicenseCategories(lReader.approvedLicenseId());
361                     }
362                 } catch (MalformedURLException e) {
363                     throw new ConfigurationException(licenseFile + " is not a valid license file", e);
364                 }
365             }
366         }
367         if (families != null || getDeprecatedConfigs().findAny().isPresent()) {
368             Log log = getLog();
369             if (log.isDebugEnabled()) {
370                 log.debug(String.format("%s license families loaded from pom", families.length));
371             }
372             Consumer<ILicenseFamily> logger = log.isDebugEnabled() ? (l) -> log.debug(String.format("Family: %s", l))
373                     : (l) -> {
374                     };
375 
376             Consumer<ILicenseFamily> process = logger.andThen(config::addFamily);
377             getDeprecatedConfigs().map(DeprecatedConfig::getLicenseFamily).filter(Objects::nonNull).forEach(process);
378             if (families != null)  { // TODO remove if check in v1.0
379                 Arrays.stream(families).map(Family::build).forEach(process);
380             }
381         }
382 
383         processLicenseFamilies(config);
384         
385         if (approvedLicenses != null && approvedLicenses.length > 0) {
386             Arrays.stream(approvedLicenses).forEach(config::addApprovedLicenseCategory);
387         }
388 
389         if (licenses != null) {
390             Log log = getLog();
391             if (log.isDebugEnabled()) {
392                 log.debug(String.format("%s licenses loaded from pom", licenses.length));
393             }
394             Consumer<ILicense> logger = log.isDebugEnabled() ? (l) -> log.debug(String.format("License: %s", l))
395                     : (l) -> {
396                     };
397             Consumer<ILicense> addApproved = (approvedLicenses == null || approvedLicenses.length == 0)
398                     ? (l) -> config.addApprovedLicenseCategory(l.getLicenseFamily())
399                     : (l) -> {
400                     };
401 
402             Consumer<ILicense> process = logger.andThen(config::addLicense).andThen(addApproved);
403             SortedSet<ILicenseFamily> families = config.getLicenseFamilies(LicenseFilter.all);
404             getDeprecatedConfigs().map(DeprecatedConfig::getLicense).filter(Objects::nonNull)
405             .map(x -> x.build(families)).forEach(process);
406             getLicenses().map(x -> x.build(families)).forEach(process);
407         }
408 
409         config.setReportable(getReportable());
410         return config;
411     }
412 
413     protected void logLicenses(Collection<ILicense> licenses) {
414         if (getLog().isDebugEnabled()) {
415             getLog().debug("The following " + licenses.size() + " licenses are activated:");
416             for (ILicense license : licenses) {
417                 getLog().debug("* " + license.toString());
418             }
419         }
420     }
421 
422     /**
423      * Creates an iterator over the files to check.
424      *
425      * @return A container of files, which are being checked.
426      * @throws MojoExecutionException in case of errors. I/O errors result in
427      * UndeclaredThrowableExceptions.
428      */
429     private IReportable getReportable() throws MojoExecutionException {
430         final IgnoringDirectoryScanner ds = new IgnoringDirectoryScanner();
431         ds.setBasedir(basedir);
432         setExcludes(ds);
433         setIncludes(ds);
434         ds.scan();
435         whenDebuggingLogExcludedFiles(ds);
436         final String[] files = ds.getIncludedFiles();
437         logAboutIncludedFiles(files);
438         try {
439             return new FilesReportable(basedir, files);
440         } catch (final IOException e) {
441             throw new UndeclaredThrowableException(e);
442         }
443     }
444 
445     private void logAboutIncludedFiles(final String[] files) {
446         if (files.length == 0) {
447             getLog().warn("No resources included.");
448         } else {
449             getLog().debug(files.length + " resources included");
450             if (getLog().isDebugEnabled()) {
451                 for (final String resource : files) {
452                     getLog().debug(" - included " + resource);
453                 }
454             }
455         }
456     }
457 
458     private void whenDebuggingLogExcludedFiles(final DirectoryScanner ds) {
459         if (getLog().isDebugEnabled()) {
460             final String[] excludedFiles = ds.getExcludedFiles();
461             if (excludedFiles.length == 0) {
462                 getLog().debug("No excluded resources.");
463             } else {
464                 getLog().debug("Excluded " + excludedFiles.length + " resources:");
465                 for (final String resource : excludedFiles) {
466                     getLog().debug(" - excluded " + resource);
467                 }
468             }
469         }
470     }
471 
472     private void setIncludes(DirectoryScanner ds) throws MojoExecutionException {
473         if ((includes != null && includes.length > 0) || includesFile != null) {
474             final List<String> includeList = new ArrayList<>();
475             if (includes != null) {
476                 includeList.addAll(Arrays.asList(includes));
477             }
478             if (includesFile != null) {
479                 final String charset = includesFileCharset == null ? "UTF8" : includesFileCharset;
480                 final File f = new File(includesFile);
481                 if (!f.isFile()) {
482                     getLog().error("IncludesFile not found: " + f.getAbsolutePath());
483                 } else {
484                     getLog().debug("Includes loaded from file " + includesFile + ", using character set " + charset);
485                 }
486                 includeList.addAll(getPatternsFromFile(f, charset));
487             }
488             ds.setIncludes(includeList.toArray(new String[includeList.size()]));
489         }
490     }
491 
492     private List<String> getPatternsFromFile(File pFile, String pCharset) throws MojoExecutionException {
493         InputStream is = null;
494         BufferedInputStream bis = null;
495         Reader r = null;
496         BufferedReader br = null;
497         Throwable th = null;
498         final List<String> patterns = new ArrayList<>();
499         try {
500             is = Files.newInputStream(pFile.toPath());
501             bis = new BufferedInputStream(is);
502             r = new InputStreamReader(bis, pCharset);
503             br = new BufferedReader(r);
504             for (;;) {
505                 final String s = br.readLine();
506                 if (s == null) {
507                     break;
508                 }
509                 patterns.add(s);
510             }
511             br.close();
512             br = null;
513             r.close();
514             r = null;
515             bis.close();
516             bis = null;
517             is.close();
518             is = null;
519         } catch (Throwable t) {
520             th = t;
521         } finally {
522             if (br != null) {
523                 try {
524                     br.close();
525                 } catch (Throwable t) {
526                     if (th == null) {
527                         th = t;
528                     }
529                 }
530             }
531             if (r != null) {
532                 try {
533                     r.close();
534                 } catch (Throwable t) {
535                     if (th == null) {
536                         th = t;
537                     }
538                 }
539             }
540             if (bis != null) {
541                 try {
542                     bis.close();
543                 } catch (Throwable t) {
544                     if (th == null) {
545                         th = t;
546                     }
547                 }
548             }
549             if (is != null) {
550                 try {
551                     is.close();
552                 } catch (Throwable t) {
553                     if (th == null) {
554                         th = t;
555                     }
556                 }
557             }
558         }
559         if (th != null) {
560             if (th instanceof RuntimeException) {
561                 throw (RuntimeException) th;
562             }
563             if (th instanceof Error) {
564                 throw (Error) th;
565             }
566             throw new MojoExecutionException(th.getMessage(), th);
567         }
568         return patterns;
569     }
570 
571     private void setExcludes(IgnoringDirectoryScanner ds) throws MojoExecutionException {
572         final List<IgnoreMatcher> ignoreMatchers = mergeDefaultExclusions();
573         if (excludes == null || excludes.length == 0) {
574             getLog().debug("No excludes explicitly specified.");
575         } else {
576             getLog().debug(excludes.length + " explicit excludes.");
577             for (final String exclude : excludes) {
578                 getLog().debug("Exclude: " + exclude);
579             }
580         }
581 
582         final List<String> globExcludes = new ArrayList<>();
583         for (IgnoreMatcher ignoreMatcher : ignoreMatchers) {
584             if (ignoreMatcher instanceof GlobIgnoreMatcher) {
585                 // The glob matching we do via the DirectoryScanner
586                 globExcludes.addAll(((GlobIgnoreMatcher) ignoreMatcher).getExclusionLines());
587             } else {
588                 // All others (git) are used directly
589                 ds.addIgnoreMatcher(ignoreMatcher);
590             }
591         }
592 
593         if (excludes != null) {
594             Collections.addAll(globExcludes, excludes);
595         }
596         if (!globExcludes.isEmpty()) {
597             final String[] allExcludes = globExcludes.toArray(new String[globExcludes.size()]);
598             ds.setExcludes(allExcludes);
599         }
600     }
601 
602     private List<IgnoreMatcher> mergeDefaultExclusions() throws MojoExecutionException {
603         List<IgnoreMatcher> ignoreMatchers = new ArrayList<>();
604 
605         final GlobIgnoreMatcher basicRules = new GlobIgnoreMatcher();
606 
607         basicRules.addRules(addPlexusAndScmDefaults(getLog(), useDefaultExcludes));
608         basicRules.addRules(addMavenDefaults(getLog(), useMavenDefaultExcludes));
609         basicRules.addRules(addEclipseDefaults(getLog(), useEclipseDefaultExcludes));
610         basicRules.addRules(addIdeaDefaults(getLog(), useIdeaDefaultExcludes));
611 
612         if (parseSCMIgnoresAsExcludes) {
613             getLog().debug("Will parse SCM ignores for exclusions...");
614             ignoreMatchers.addAll(ScmIgnoreParser.getExclusionsFromSCM(getLog(), project.getBasedir()));
615             getLog().debug("Finished adding exclusions from SCM ignore files.");
616         }
617 
618         if (excludeSubProjects && project != null && project.getModules() != null) {
619             for (final String moduleSubPath : project.getModules()) {
620                 if (new File(basedir, moduleSubPath).isDirectory()) {
621                     basicRules.addRule(moduleSubPath + "/**/*");
622                 } else {
623                     basicRules.addRule(StringUtils.substringBeforeLast(moduleSubPath, "/") + "/**/*");
624                 }
625             }
626         }
627 
628         if (getLog().isDebugEnabled()) {
629             getLog().debug("Finished creating list of implicit excludes.");
630             if (basicRules.getExclusionLines().isEmpty() && ignoreMatchers.isEmpty()) {
631                 getLog().debug("No excludes implicitly specified.");
632             } else {
633                 if (!basicRules.getExclusionLines().isEmpty()) {
634                     getLog().debug(basicRules.getExclusionLines().size() + " implicit excludes.");
635                     for (final String exclude : basicRules.getExclusionLines()) {
636                         getLog().debug("Implicit exclude: " + exclude);
637                     }
638                 }
639                 for (IgnoreMatcher ignoreMatcher : ignoreMatchers) {
640                     if (ignoreMatcher instanceof GlobIgnoreMatcher) {
641                         GlobIgnoreMatcher globIgnoreMatcher = (GlobIgnoreMatcher) ignoreMatcher;
642                         if (!globIgnoreMatcher.getExclusionLines().isEmpty()) {
643                             getLog().debug(globIgnoreMatcher.getExclusionLines().size() + " implicit excludes from SCM.");
644                             for (final String exclude : globIgnoreMatcher.getExclusionLines()) {
645                                 getLog().debug("Implicit exclude: " + exclude);
646                             }
647                         }
648                     } else {
649                         getLog().debug("Implicit exclude: \n" + ignoreMatcher);
650                     }
651                 }
652             }
653         }
654         if (excludesFile != null) {
655             final File f = new File(excludesFile);
656             if (!f.isFile()) {
657                 getLog().error("Excludes file not found: " + f.getAbsolutePath());
658             }
659             if (!f.canRead()) {
660                 getLog().error("Excludes file not readable: " + f.getAbsolutePath());
661             }
662             final String charset = excludesFileCharset == null ? "UTF8" : excludesFileCharset;
663             getLog().debug("Loading excludes from file " + f + ", using character set " + charset);
664             basicRules.addRules(getPatternsFromFile(f, charset));
665         }
666 
667         if (!basicRules.isEmpty()) {
668             ignoreMatchers.add(basicRules);
669         }
670 
671         return ignoreMatchers;
672     }
673 }