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