View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 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.Locale;
17  import java.util.concurrent.Callable;
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(name = "check", defaultPhase = LifecyclePhase.VERIFY,
36      requiresDependencyResolution = ResolutionScope.TEST,
37      threadSafe = true)
38  public final class CheckMojo extends AbstractQuliceMojo {
39  
40      /**
41       * Executors for validators.
42       */
43      private final ExecutorService executors =
44          Executors.newFixedThreadPool(5);
45  
46      /**
47       * Provider of validators.
48       */
49      private ValidatorsProvider provider =
50          new DefaultValidatorsProvider(this.env());
51  
52      /**
53       * Check timeout.
54       * Can be a number of minutes.
55       * Can be a string with time units, like '10m' or '1h'.
56       * Time units are 's' for seconds, 'm' for minutes, 'h' for hours.
57       * Can also be a string 'forever' to disable timeout.
58       * Defaults to 10 minutes.
59       */
60      @Parameter(property = "qulice.check-timeout", defaultValue = "10")
61      private String timeout;
62  
63      @Override
64      public void doExecute() throws MojoFailureException {
65          try {
66              this.run();
67          } catch (final ValidationException ex) {
68              Logger.info(
69                  this,
70                  "Read our quality policy: http://www.qulice.com/quality.html"
71              );
72              throw new MojoFailureException("Failure", ex);
73          }
74      }
75  
76      /**
77       * Set provider of validators.
78       * @param prov The provider
79       */
80      public void setValidatorsProvider(final ValidatorsProvider prov) {
81          this.provider = prov;
82      }
83  
84      /**
85       * Set timeout for checks.
86       * @param time Timeout value
87       */
88      public void setTimeout(final String time) {
89          this.timeout = time;
90      }
91  
92      /**
93       * Run them all.
94       * @throws ValidationException If any of them fail
95       */
96      @SuppressWarnings("PMD.CognitiveComplexity")
97      private void run() throws ValidationException {
98          final LinkedList<Violation> results = new LinkedList<>();
99          final MavenEnvironment env = this.env();
100         final Collection<File> files = env.files("*.*");
101         if (!files.isEmpty()) {
102             final Collection<ResourceValidator> validators =
103                 this.provider.externalResource();
104             final Collection<Future<Collection<Violation>>> futures =
105                 this.submit(env, files, validators);
106             for (final Future<Collection<Violation>> future : futures) {
107                 try {
108                     if ("forever".equalsIgnoreCase(this.timeout)) {
109                         results.addAll(future.get());
110                     } else {
111                         final var value = this.timeoutValue();
112                         final var units = this.timeoutUnits();
113                         Logger.debug(
114                             this,
115                             "Waiting up to %d %s for validator result",
116                             value,
117                             units
118                         );
119                         results.addAll(future.get(value, units));
120                     }
121                 } catch (final InterruptedException ex) {
122                     Thread.currentThread().interrupt();
123                     throw new IllegalStateException(ex);
124                 } catch (final ExecutionException | TimeoutException ex) {
125                     throw new IllegalStateException(ex);
126                 }
127             }
128             Collections.sort(results);
129             for (final Violation result : results) {
130                 Logger.info(
131                     this,
132                     "%s: %s[%s]: %s (%s)",
133                     result.validator(),
134                     result.file().replace(
135                         String.format(
136                             "%s/", this.session().getExecutionRootDirectory()
137                         ),
138                         ""
139                     ),
140                     result.lines(),
141                     result.message(),
142                     result.name()
143                 );
144             }
145         }
146         if (!results.isEmpty()) {
147             throw new ValidationException(
148                 String.format("There are %d violations", results.size())
149             );
150         }
151         for (final Validator validator : this.provider.external()) {
152             Logger.info(this, "Starting %s validator", validator.name());
153             validator.validate(env);
154             Logger.info(this, "Finishing %s validator", validator.name());
155         }
156         for (final MavenValidator validator : this.provider.internal()) {
157             validator.validate(env);
158         }
159     }
160 
161     /**
162      * Submit validators to executor.
163      * @param env Maven environment
164      * @param files List of files to validate
165      * @param validators Validators to use
166      * @return List of futures
167      */
168     @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
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 ValidatorCallable(validator, env, files)
179                 )
180             );
181         }
182         return futures;
183     }
184 
185     /**
186      * Teimeout 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      *
265      * @since 0.1
266      */
267     private static class ValidatorCallable
268         implements Callable<Collection<Violation>> {
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 }