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