View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.checkstyle;
6   
7   import com.jcabi.log.Logger;
8   import com.puppycrawl.tools.checkstyle.Checker;
9   import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
10  import com.puppycrawl.tools.checkstyle.PropertiesExpander;
11  import com.puppycrawl.tools.checkstyle.api.AuditEvent;
12  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
13  import com.puppycrawl.tools.checkstyle.api.Configuration;
14  import com.qulice.spi.Environment;
15  import com.qulice.spi.ResourceValidator;
16  import com.qulice.spi.Violation;
17  import java.io.File;
18  import java.util.Collection;
19  import java.util.LinkedList;
20  import java.util.List;
21  import java.util.Properties;
22  import org.xml.sax.InputSource;
23  
24  /**
25   * Validator with Checkstyle.
26   *
27   * @since 0.3
28   * @checkstyle ClassDataAbstractionCoupling (260 lines)
29   */
30  public final class CheckstyleValidator implements ResourceValidator {
31  
32      /**
33       * Checkstyle checker.
34       */
35      private final Checker checker;
36  
37      /**
38       * Listener of checkstyle messages.
39        */
40      private final CheckstyleListener listener;
41  
42      /**
43       * Environment to use.
44       */
45      private final Environment env;
46  
47      /**
48       * Constructor.
49       * @param env Environment to use.
50       */
51      @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
52      public CheckstyleValidator(final Environment env) {
53          this.env = env;
54          this.checker = new Checker();
55          this.checker.setModuleClassLoader(
56              Thread.currentThread().getContextClassLoader()
57          );
58          try {
59              this.checker.configure(this.configuration());
60          } catch (final CheckstyleException ex) {
61              throw new IllegalStateException("Failed to configure checker", ex);
62          }
63          this.listener = new CheckstyleListener(this.env);
64          this.checker.addListener(this.listener);
65      }
66  
67      @Override
68      @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
69      public Collection<Violation> validate(final Collection<File> files) {
70          final List<File> sources = this.getNonExcludedFiles(files);
71          try {
72              Logger.debug(this, "Checkstyle processing %d files", sources.size());
73              final long start = System.currentTimeMillis();
74              this.checker.process(sources);
75              Logger.debug(
76                  this,
77                  "Checkstyle processed %d files in %[ms]s",
78                  sources.size(),
79                  System.currentTimeMillis() - start
80              );
81          } catch (final CheckstyleException ex) {
82              throw new IllegalStateException("Failed to process files", ex);
83          }
84          final List<AuditEvent> events = this.listener.events();
85          final Collection<Violation> results = new LinkedList<>();
86          for (final AuditEvent event : events) {
87              final String check = event.getSourceName();
88              results.add(
89                  new Violation.Default(
90                      this.name(),
91                      check.substring(check.lastIndexOf('.') + 1),
92                      event.getFileName(),
93                      String.valueOf(event.getLine()),
94                      event.getMessage()
95                  )
96              );
97          }
98          return results;
99      }
100 
101     @Override public String name() {
102         return "Checkstyle";
103     }
104 
105     /**
106      * Filters out excluded files from further validation.
107      * @param files Files to validate
108      * @return List of relevant files
109      */
110     public List<File> getNonExcludedFiles(final Collection<File> files) {
111         final List<File> relevant = new LinkedList<>();
112         for (final File file : files) {
113             final String name = file.getPath().substring(
114                 this.env.basedir().toString().length()
115             );
116             if (this.env.exclude("checkstyle", name)) {
117                 continue;
118             }
119             if (!name.toLowerCase(java.util.Locale.ROOT).endsWith(".java")) {
120                 continue;
121             }
122             relevant.add(file);
123         }
124         return relevant;
125     }
126 
127     /**
128      * Load checkstyle configuration.
129      * @return The configuration just loaded
130      * @see #validate(Collection)
131      */
132     private Configuration configuration() {
133         final File cache =
134             new File(this.env.tempdir(), "checkstyle/checkstyle.cache");
135         final File parent = cache.getParentFile();
136         if (!parent.exists() && !parent.mkdirs()) {
137             throw new IllegalStateException(
138                 String.format(
139                     "Unable to create directories needed for %s",
140                     cache.getPath()
141                 )
142             );
143         }
144         final Properties props = new Properties();
145         props.setProperty("cache.file", cache.getPath());
146         final Configuration config;
147         try (java.io.InputStream stream = this.getClass().getResourceAsStream("checks.xml")) {
148             if (stream == null) {
149                 throw new IllegalStateException(
150                     "Checkstyle configuration file 'checks.xml' not found in classpath."
151                 );
152             }
153             final InputSource src = new InputSource(stream);
154             config = ConfigurationLoader.loadConfiguration(
155                 src,
156                 new PropertiesExpander(props),
157                 ConfigurationLoader.IgnoredModulesOptions.OMIT
158             );
159         } catch (final CheckstyleException | java.io.IOException ex) {
160             throw new IllegalStateException("Failed to load config", ex);
161         }
162         return config;
163     }
164 }