View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.maven;
6   
7   import com.jcabi.log.Logger;
8   import com.qulice.spi.ResourceValidator;
9   import com.qulice.spi.ValidationException;
10  import com.qulice.spi.Validator;
11  import com.qulice.spi.Violation;
12  import java.io.File;
13  import java.util.ArrayList;
14  import java.util.Collection;
15  import java.util.Collections;
16  import java.util.List;
17  import java.util.Locale;
18  import java.util.concurrent.ExecutionException;
19  import java.util.concurrent.ExecutorService;
20  import java.util.concurrent.Executors;
21  import java.util.concurrent.Future;
22  import java.util.concurrent.TimeUnit;
23  import java.util.concurrent.TimeoutException;
24  import org.apache.maven.plugin.MojoFailureException;
25  import org.apache.maven.plugins.annotations.LifecyclePhase;
26  import org.apache.maven.plugins.annotations.Mojo;
27  import org.apache.maven.plugins.annotations.Parameter;
28  import org.apache.maven.plugins.annotations.ResolutionScope;
29  
30  /**
31   * Check the project and find all possible violations.
32   *
33   * @since 0.3
34   */
35  @Mojo(
36      name = "check",
37      defaultPhase = LifecyclePhase.VERIFY,
38      requiresDependencyResolution = ResolutionScope.TEST,
39      threadSafe = true
40  )
41  public final class CheckMojo extends AbstractQuliceMojo {
42  
43      /**
44       * Executors for validators.
45       */
46      private final ExecutorService executors;
47  
48      /**
49       * Provider of validators, if it was set from the outside.
50       */
51      private ValidatorsProvider provider;
52  
53      /**
54       * Check timeout.
55       * Can be a number of minutes.
56       * Can be a string with time units, like '10m' or '1h'.
57       * Time units are 's' for seconds, 'm' for minutes, 'h' for hours.
58       * Can also be a string 'forever' to disable timeout.
59       * Defaults to 10 minutes.
60       */
61      @Parameter(property = "qulice.check-timeout", defaultValue = "10")
62      private String timeout;
63  
64      /**
65       * Default constructor.
66       */
67      public CheckMojo() {
68          this(Executors.newFixedThreadPool(5));
69      }
70  
71      /**
72       * Primary constructor.
73       *
74       * @param svc Executors to run resource validators in
75       */
76      private CheckMojo(final ExecutorService svc) {
77          this.executors = svc;
78      }
79  
80      @Override
81      public String doExecute() throws MojoFailureException {
82          try {
83              return this.run();
84          } catch (final ValidationException ex) {
85              Logger.info(
86                  this,
87                  "Read our quality policy: https://www.qulice.com/quality.html"
88              );
89              throw new MojoFailureException("Failure", ex);
90          }
91      }
92  
93      /**
94       * Set provider of validators.
95       *
96       * @param prov The provider
97       */
98      public void setValidatorsProvider(final ValidatorsProvider prov) {
99          this.provider = prov;
100     }
101 
102     /**
103      * Set timeout for checks.
104      *
105      * @param time Timeout value
106      */
107     public void setTimeout(final String time) {
108         this.timeout = time;
109     }
110 
111     /**
112      * Filter files based on excludes.
113      *
114      * @param env Maven environment
115      * @param files Files to exclude
116      * @param validator Validator to use
117      * @return Filtered files
118      */
119     static Collection<File> filter(
120         final MavenEnvironment env,
121         final Collection<File> files, final ResourceValidator validator
122     ) {
123         final Collection<File> filtered = new ArrayList<>(files.size());
124         for (final File file : files) {
125             if (
126                 !env.exclude(
127                     validator.name().toLowerCase(Locale.ENGLISH),
128                     file.toString()
129                 )
130             ) {
131                 filtered.add(file);
132             }
133         }
134         return filtered;
135     }
136 
137     @SuppressWarnings("PMD.CognitiveComplexity")
138     private String run() throws ValidationException {
139         final List<Violation> results = new ArrayList<>(0);
140         final MavenEnvironment env = this.env();
141         final ValidatorsProvider prov = this.validators(env);
142         final Collection<ResourceValidator> resources = prov.externalResource();
143         final Collection<File> files = env.files("*.*");
144         if (!files.isEmpty()) {
145             final Collection<Future<Collection<Violation>>> futures =
146                 this.submit(env, files, resources);
147             for (final Future<Collection<Violation>> future : futures) {
148                 try {
149                     if ("forever".equalsIgnoreCase(this.timeout)) {
150                         results.addAll(future.get());
151                     } else {
152                         final long value = this.timeoutValue();
153                         final TimeUnit units = this.timeoutUnits();
154                         Logger.debug(
155                             this,
156                             "Waiting up to %d %s for validator result",
157                             value,
158                             units
159                         );
160                         results.addAll(future.get(value, units));
161                     }
162                 } catch (final InterruptedException ex) {
163                     Thread.currentThread().interrupt();
164                     throw new IllegalStateException(ex);
165                 } catch (final ExecutionException | TimeoutException ex) {
166                     throw new IllegalStateException(ex);
167                 }
168             }
169             Collections.sort(results);
170             for (final Violation result : results) {
171                 Logger.info(
172                     this,
173                     "%s: %s[%s]: %s (%s)",
174                     result.validator(),
175                     result.file().replace(
176                         String.format(
177                             "%s/", this.session().getExecutionRootDirectory()
178                         ),
179                         ""
180                     ),
181                     result.lines(),
182                     result.message(),
183                     result.name()
184                 );
185             }
186         }
187         if (!results.isEmpty()) {
188             throw new ValidationException(
189                 String.format("There are %d violations", results.size())
190             );
191         }
192         for (final Validator validator : prov.external()) {
193             Logger.info(this, "Starting %s validator", validator.name());
194             validator.validate(env);
195             Logger.info(this, "Finishing %s validator", validator.name());
196         }
197         for (final MavenValidator validator : prov.internal()) {
198             validator.validate(env);
199         }
200         return new Summary(files, resources).toString();
201     }
202 
203     private ValidatorsProvider validators(final MavenEnvironment env) {
204         final ValidatorsProvider prov;
205         if (this.provider == null) {
206             prov = new DefaultValidatorsProvider(env);
207         } else {
208             prov = this.provider;
209         }
210         return prov;
211     }
212 
213     private Collection<Future<Collection<Violation>>> submit(
214         final MavenEnvironment env, final Collection<File> files,
215         final Collection<ResourceValidator> validators
216     ) {
217         final Collection<Future<Collection<Violation>>> futures =
218             new ArrayList<>(validators.size());
219         for (final ResourceValidator validator : validators) {
220             futures.add(
221                 this.executors.submit(
222                     new ValidatorCallable(validator, env, files)
223                 )
224             );
225         }
226         return futures;
227     }
228 
229     private long timeoutValue() {
230         final String clear = this.clearTimeout();
231         final long res;
232         if (clear.isEmpty()) {
233             res = 10L;
234         } else if (clear.endsWith("s") || clear.endsWith("m") || clear.endsWith("h")) {
235             res = Long.parseLong(clear.substring(0, clear.length() - 1));
236         } else {
237             res = Long.parseLong(clear);
238         }
239         return res;
240     }
241 
242     private TimeUnit timeoutUnits() {
243         final String clear = this.clearTimeout();
244         final TimeUnit unit;
245         if (clear.endsWith("s")) {
246             unit = TimeUnit.SECONDS;
247         } else if (clear.endsWith("m")) {
248             unit = TimeUnit.MINUTES;
249         } else if (clear.endsWith("h")) {
250             unit = TimeUnit.HOURS;
251         } else {
252             unit = TimeUnit.MINUTES;
253         }
254         return unit;
255     }
256 
257     private String clearTimeout() {
258         final String clear;
259         if (this.timeout == null) {
260             clear = "";
261         } else {
262             clear = this.timeout.trim()
263             .replaceAll(" ", "")
264             .toLowerCase(Locale.ENGLISH);
265         }
266         return clear;
267     }
268 }