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