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