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.Collection;
14  import java.util.Collections;
15  import java.util.LinkedList;
16  import java.util.List;
17  import java.util.Locale;
18  import java.util.concurrent.Callable;
19  import java.util.concurrent.ExecutionException;
20  import java.util.concurrent.ExecutorService;
21  import java.util.concurrent.Executors;
22  import java.util.concurrent.Future;
23  import java.util.concurrent.TimeUnit;
24  import java.util.concurrent.TimeoutException;
25  import org.apache.maven.plugin.MojoFailureException;
26  import org.apache.maven.plugins.annotations.LifecyclePhase;
27  import org.apache.maven.plugins.annotations.Mojo;
28  import org.apache.maven.plugins.annotations.Parameter;
29  import org.apache.maven.plugins.annotations.ResolutionScope;
30  
31  /**
32   * Check the project and find all possible violations.
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          Executors.newFixedThreadPool(5);
48  
49      /**
50       * Provider of validators.
51       */
52      private ValidatorsProvider provider =
53          new DefaultValidatorsProvider(this.env());
54  
55      /**
56       * Check timeout.
57       * Can be a number of minutes.
58       * Can be a string with time units, like '10m' or '1h'.
59       * Time units are 's' for seconds, 'm' for minutes, 'h' for hours.
60       * Can also be a string 'forever' to disable timeout.
61       * Defaults to 10 minutes.
62       */
63      @Parameter(property = "qulice.check-timeout", defaultValue = "10")
64      private String timeout;
65  
66      @Override
67      public void doExecute() throws MojoFailureException {
68          try {
69              this.run();
70          } catch (final ValidationException ex) {
71              Logger.info(
72                  this,
73                  "Read our quality policy: https://www.qulice.com/quality.html"
74              );
75              throw new MojoFailureException("Failure", ex);
76          }
77      }
78  
79      /**
80       * Set provider of validators.
81       * @param prov The provider
82       */
83      public void setValidatorsProvider(final ValidatorsProvider prov) {
84          this.provider = prov;
85      }
86  
87      /**
88       * Set timeout for checks.
89       * @param time Timeout value
90       */
91      public void setTimeout(final String time) {
92          this.timeout = time;
93      }
94  
95      /**
96       * Run them all.
97       * @throws ValidationException If any of them fail
98       */
99      @SuppressWarnings("PMD.CognitiveComplexity")
100     private void run() throws ValidationException {
101         final List<Violation> results = new LinkedList<>();
102         final MavenEnvironment env = this.env();
103         final Collection<File> files = env.files("*.*");
104         if (!files.isEmpty()) {
105             final Collection<Future<Collection<Violation>>> futures =
106                 this.submit(env, files, this.provider.externalResource());
107             for (final Future<Collection<Violation>> future : futures) {
108                 try {
109                     if ("forever".equalsIgnoreCase(this.timeout)) {
110                         results.addAll(future.get());
111                     } else {
112                         final long value = this.timeoutValue();
113                         final TimeUnit units = this.timeoutUnits();
114                         Logger.debug(
115                             this,
116                             "Waiting up to %d %s for validator result",
117                             value,
118                             units
119                         );
120                         results.addAll(future.get(value, units));
121                     }
122                 } catch (final InterruptedException ex) {
123                     Thread.currentThread().interrupt();
124                     throw new IllegalStateException(ex);
125                 } catch (final ExecutionException | TimeoutException ex) {
126                     throw new IllegalStateException(ex);
127                 }
128             }
129             Collections.sort(results);
130             for (final Violation result : results) {
131                 Logger.info(
132                     this,
133                     "%s: %s[%s]: %s (%s)",
134                     result.validator(),
135                     result.file().replace(
136                         String.format(
137                             "%s/", this.session().getExecutionRootDirectory()
138                         ),
139                         ""
140                     ),
141                     result.lines(),
142                     result.message(),
143                     result.name()
144                 );
145             }
146         }
147         if (!results.isEmpty()) {
148             throw new ValidationException(
149                 String.format("There are %d violations", results.size())
150             );
151         }
152         for (final Validator validator : this.provider.external()) {
153             Logger.info(this, "Starting %s validator", validator.name());
154             validator.validate(env);
155             Logger.info(this, "Finishing %s validator", validator.name());
156         }
157         for (final MavenValidator validator : this.provider.internal()) {
158             validator.validate(env);
159         }
160     }
161 
162     /**
163      * Submit validators to executor.
164      * @param env Maven environment
165      * @param files List of files to validate
166      * @param validators Validators to use
167      * @return List of futures
168      */
169     private Collection<Future<Collection<Violation>>> submit(
170         final MavenEnvironment env, final Collection<File> files,
171         final Collection<ResourceValidator> validators
172     ) {
173         final Collection<Future<Collection<Violation>>> futures =
174             new LinkedList<>();
175         for (final ResourceValidator validator : validators) {
176             futures.add(
177                 this.executors.submit(
178                     new CheckMojo.ValidatorCallable(validator, env, files)
179                 )
180             );
181         }
182         return futures;
183     }
184 
185     /**
186      * Timeout value for timeout.
187      * @return Timeout value
188      */
189     private long timeoutValue() {
190         final String clear = this.clearTimeout();
191         final long res;
192         if (clear.isEmpty()) {
193             res = 10L;
194         } else if (clear.endsWith("s") || clear.endsWith("m") || clear.endsWith("h")) {
195             res = Long.parseLong(clear.substring(0, clear.length() - 1));
196         } else {
197             res = Long.parseLong(clear);
198         }
199         return res;
200     }
201 
202     /**
203      * Time unit for timeout.
204      * @return Time unit
205      */
206     private TimeUnit timeoutUnits() {
207         final String clear = this.clearTimeout();
208         final TimeUnit unit;
209         if (clear.endsWith("s")) {
210             unit = TimeUnit.SECONDS;
211         } else if (clear.endsWith("m")) {
212             unit = TimeUnit.MINUTES;
213         } else if (clear.endsWith("h")) {
214             unit = TimeUnit.HOURS;
215         } else {
216             unit = TimeUnit.MINUTES;
217         }
218         return unit;
219     }
220 
221     /**
222     * Clear timeout string.
223     * @return Cleaned timeout
224     */
225     private String clearTimeout() {
226         final String clear;
227         if (this.timeout == null) {
228             clear = "";
229         } else {
230             clear = this.timeout.trim()
231             .replaceAll(" ", "")
232             .toLowerCase(Locale.ENGLISH);
233         }
234         return clear;
235     }
236 
237     /**
238      * Filter files based on excludes.
239      * @param env Maven environment
240      * @param files Files to exclude
241      * @param validator Validator to use
242      * @return Filtered files
243      */
244     private static Collection<File> filter(
245         final MavenEnvironment env,
246         final Collection<File> files, final ResourceValidator validator
247     ) {
248         final Collection<File> filtered = new LinkedList<>();
249         for (final File file : files) {
250             if (
251                 !env.exclude(
252                     validator.name().toLowerCase(Locale.ENGLISH),
253                     file.toString()
254                 )
255             ) {
256                 filtered.add(file);
257             }
258         }
259         return filtered;
260     }
261 
262     /**
263      * Callable for validators.
264      * @since 0.1
265      */
266     private static class ValidatorCallable
267         implements Callable<Collection<Violation>> {
268 
269         /**
270          * Validator to use.
271          */
272         private final ResourceValidator validator;
273 
274         /**
275          * Maven environment.
276          */
277         private final MavenEnvironment env;
278 
279         /**
280          * List of files to validate.
281          */
282         private final Collection<File> files;
283 
284         /**
285          * Constructor.
286          * @param validator Validator to use
287          * @param env Maven environment
288          * @param files List of files to validate
289          */
290         ValidatorCallable(
291             final ResourceValidator validator,
292             final MavenEnvironment env, final Collection<File> files
293         ) {
294             this.validator = validator;
295             this.env = env;
296             this.files = files;
297         }
298 
299         @Override
300         public Collection<Violation> call() {
301             return this.validator.validate(
302                 CheckMojo.filter(this.env, this.files, this.validator)
303             );
304         }
305     }
306 }