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.google.common.base.Function;
8   import com.google.common.base.Predicate;
9   import com.google.common.base.Predicates;
10  import com.google.common.collect.Collections2;
11  import com.google.common.collect.Iterables;
12  import com.jcabi.log.Logger;
13  import java.io.File;
14  import java.net.MalformedURLException;
15  import java.net.URI;
16  import java.net.URL;
17  import java.net.URLClassLoader;
18  import java.nio.charset.Charset;
19  import java.security.PrivilegedAction;
20  import java.util.Collection;
21  import java.util.LinkedList;
22  import java.util.List;
23  import java.util.Properties;
24  import javax.annotation.Nullable;
25  import org.apache.commons.io.FileUtils;
26  import org.apache.commons.io.FilenameUtils;
27  import org.apache.commons.io.filefilter.DirectoryFileFilter;
28  import org.apache.commons.io.filefilter.IOFileFilter;
29  import org.apache.commons.io.filefilter.WildcardFileFilter;
30  import org.apache.maven.artifact.Artifact;
31  import org.apache.maven.artifact.DependencyResolutionRequiredException;
32  import org.apache.maven.project.MavenProject;
33  import org.codehaus.plexus.context.Context;
34  
35  /**
36   * Environment, passed from MOJO to validators.
37   *
38   * @since 0.3
39   * @checkstyle ClassDataAbstractionCouplingCheck (300 lines)
40   */
41  @SuppressWarnings("PMD.TooManyMethods")
42  public final class DefaultMavenEnvironment implements MavenEnvironment {
43  
44      /**
45       * Maven project.
46       */
47      private MavenProject iproject;
48  
49      /**
50       * Plexus context.
51       */
52      private Context icontext;
53  
54      /**
55       * Plugin configuration.
56       */
57      private final Properties iproperties = new Properties();
58  
59      /**
60       * MOJO executor.
61       */
62      private MojoExecutor exectr;
63  
64      /**
65       * Excludes, regular expressions.
66       */
67      private final Collection<String> exc = new LinkedList<>();
68  
69      /**
70       * Xpath queries for pom.xml validation.
71       */
72      private final Collection<String> assertion = new LinkedList<>();
73  
74      /**
75       * Source code encoding charset.
76       */
77      private String charset = "UTF-8";
78  
79      @Override
80      public String param(final String name, final String value) {
81          String ret = this.iproperties.getProperty(name);
82          if (ret == null) {
83              ret = value;
84          }
85          return ret;
86      }
87  
88      @Override
89      public File basedir() {
90          return this.iproject.getBasedir();
91      }
92  
93      @Override
94      public File tempdir() {
95          return new File(this.iproject.getBuild().getOutputDirectory());
96      }
97  
98      @Override
99      public File outdir() {
100         return new File(this.iproject.getBuild().getOutputDirectory());
101     }
102 
103     @Override
104     @SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops", "deprecation"})
105     public Collection<String> classpath() {
106         final Collection<String> paths = new LinkedList<>();
107         final String blank = "%20";
108         final String whitespace = " ";
109         try {
110             for (final String name
111                 : this.iproject.getRuntimeClasspathElements()) {
112                 paths.add(
113                     name.replace(
114                         File.separatorChar, '/'
115                     ).replaceAll(whitespace, blank)
116                 );
117             }
118             for (final Artifact artifact
119                 : this.iproject.getDependencyArtifacts()) {
120                 if (artifact.getFile() != null) {
121                     paths.add(
122                         artifact.getFile().getAbsolutePath()
123                             .replace(File.separatorChar, '/')
124                             .replaceAll(whitespace, blank)
125                     );
126                 }
127             }
128         } catch (final DependencyResolutionRequiredException ex) {
129             throw new IllegalStateException("Failed to read classpath", ex);
130         }
131         return paths;
132     }
133 
134     @Override
135     public ClassLoader classloader() {
136         final List<URL> urls = new LinkedList<>();
137         for (final String path : this.classpath()) {
138             try {
139                 urls.add(
140                     URI.create(String.format("file:///%s", path)).toURL()
141                 );
142             } catch (final MalformedURLException ex) {
143                 throw new IllegalStateException("Failed to build URL", ex);
144             }
145         }
146         final URLClassLoader loader =
147             new DefaultMavenEnvironment.PrivilegedClassLoader(urls).run();
148         for (final URL url : loader.getURLs()) {
149             Logger.debug(this, "Classpath: %s", url);
150         }
151         return loader;
152     }
153 
154     @Override
155     public MavenProject project() {
156         return this.iproject;
157     }
158 
159     @Override
160     public Properties properties() {
161         return this.iproperties;
162     }
163 
164     @Override
165     public Context context() {
166         return this.icontext;
167     }
168 
169     @Override
170     public Properties config() {
171         return this.iproperties;
172     }
173 
174     @Override
175     public MojoExecutor executor() {
176         return this.exectr;
177     }
178 
179     @Override
180     public Collection<String> asserts() {
181         return this.assertion;
182     }
183 
184     @Override
185     @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
186     public Collection<File> files(final String pattern) {
187         final Collection<File> files = new LinkedList<>();
188         final IOFileFilter filter = WildcardFileFilter.builder().setWildcards(pattern).get();
189         final String[] dirs = {
190             "src",
191         };
192         for (final String dir : dirs) {
193             final File sources = new File(this.basedir(), dir);
194             if (sources.exists()) {
195                 files.addAll(
196                     FileUtils.listFiles(
197                         sources,
198                         filter,
199                         DirectoryFileFilter.INSTANCE
200                     )
201                 );
202             }
203         }
204         return files;
205     }
206 
207     @Override
208     public boolean exclude(final String check, final String name) {
209         return Iterables.any(
210             this.excludes(check),
211             new DefaultMavenEnvironment.PathPredicate(name)
212         );
213     }
214 
215     @Override
216     public Collection<String> excludes(final String checker) {
217         final Collection<String> excludes = Collections2.transform(
218             this.exc,
219             new DefaultMavenEnvironment.CheckerExcludes(checker)
220         );
221         return Collections2.filter(excludes, Predicates.notNull());
222     }
223 
224     /**
225      * Set Maven Project (used mostly for unit testing).
226      * @param proj The project to set
227      */
228     public void setProject(final MavenProject proj) {
229         this.iproject = proj;
230     }
231 
232     /**
233      * Set context.
234      * @param ctx The context to set
235      */
236     public void setContext(final Context ctx) {
237         this.icontext = ctx;
238     }
239 
240     /**
241      * Set executor.
242      * @param exec The executor
243      */
244     public void setMojoExecutor(final MojoExecutor exec) {
245         this.exectr = exec;
246     }
247 
248     /**
249      * Set property.
250      * @param name Its name
251      * @param value Its value
252      */
253     public void setProperty(final String name, final String value) {
254         this.iproperties.setProperty(name, value);
255     }
256 
257     /**
258      * Set list of regular expressions to exclude.
259      * @param exprs Expressions
260      */
261     public void setExcludes(final Collection<String> exprs) {
262         this.exc.clear();
263         this.exc.addAll(exprs);
264     }
265 
266     /**
267      * Set list of Xpath queries for pom.xml validation.
268      * @param ass Xpath queries
269      */
270     public void setAssertion(final Collection<String> ass) {
271         this.assertion.clear();
272         this.assertion.addAll(ass);
273     }
274 
275     public void setEncoding(final String encoding) {
276         this.charset = encoding;
277     }
278 
279     @Override
280     public Charset encoding() {
281         if (this.charset == null || this.charset.isEmpty()) {
282             this.charset = "UTF-8";
283         }
284         return Charset.forName(this.charset);
285     }
286 
287     /**
288      * Creates URL ClassLoader in privileged block.
289      *
290      * @since 0.1
291      */
292     private static final class PrivilegedClassLoader implements
293         PrivilegedAction<URLClassLoader> {
294         /**
295          * URLs for class loading.
296          */
297         private final List<URL> urls;
298 
299         /**
300          * Constructor.
301          * @param urls URLs for class loading.
302          */
303         private PrivilegedClassLoader(final List<URL> urls) {
304             this.urls = urls;
305         }
306 
307         @Override
308         public URLClassLoader run() {
309             return new URLClassLoader(
310                 this.urls.toArray(new URL[this.urls.size()]),
311                 Thread.currentThread().getContextClassLoader()
312             );
313         }
314     }
315 
316     /**
317      * Checks if two paths are equal.
318      *
319      * @since 0.1
320      */
321     private static class PathPredicate implements Predicate<String> {
322         /**
323          * Path to match.
324          */
325         private final String name;
326 
327         /**
328          * Constructor.
329          * @param name Path to match.
330          */
331         PathPredicate(final String name) {
332             this.name = name;
333         }
334 
335         @Override
336         public boolean apply(@Nullable final String input) {
337             return input != null
338                 && FilenameUtils.normalize(this.name, true).matches(input);
339         }
340     }
341 
342     /**
343      * Converts a checker exclude into exclude param.
344      *
345      * E.g. "checkstyle:.*" will become ".*".
346      *
347      * @since 0.1
348      */
349     private static class CheckerExcludes implements Function<String, String> {
350 
351         /**
352          * Name of checker.
353          */
354         private final String checker;
355 
356         /**
357          * Constructor.
358          * @param checker Name of checker.
359          */
360         CheckerExcludes(final String checker) {
361             this.checker = checker;
362         }
363 
364         @Nullable
365         @Override
366         public String apply(@Nullable final String input) {
367             String result = null;
368             if (input != null) {
369                 final String[] exclude = input.split(":", 2);
370                 if (this.checker.equals(exclude[0]) && exclude.length > 1) {
371                     result = exclude[1];
372                 }
373             }
374             return result;
375         }
376     }
377 }