View Javadoc
1   /*
2    * Copyright (c) 2011-2024 Qulice.com
3    *
4    * All rights reserved.
5    *
6    * Redistribution and use in source and binary forms, with or without
7    * modification, are permitted provided that the following conditions
8    * are met: 1) Redistributions of source code must retain the above
9    * copyright notice, this list of conditions and the following
10   * disclaimer. 2) Redistributions in binary form must reproduce the above
11   * copyright notice, this list of conditions and the following
12   * disclaimer in the documentation and/or other materials provided
13   * with the distribution. 3) Neither the name of the Qulice.com nor
14   * the names of its contributors may be used to endorse or promote
15   * products derived from this software without specific prior written
16   * permission.
17   *
18   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19   * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
20   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
21   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22   * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
27   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
29   * OF THE POSSIBILITY OF SUCH DAMAGE.
30   */
31  package com.qulice.maven;
32  
33  import com.google.common.base.Function;
34  import com.google.common.base.Predicate;
35  import com.google.common.base.Predicates;
36  import com.google.common.collect.Collections2;
37  import com.google.common.collect.Iterables;
38  import com.jcabi.log.Logger;
39  import java.io.File;
40  import java.net.MalformedURLException;
41  import java.net.URI;
42  import java.net.URL;
43  import java.net.URLClassLoader;
44  import java.nio.charset.Charset;
45  import java.security.PrivilegedAction;
46  import java.util.Collection;
47  import java.util.LinkedList;
48  import java.util.List;
49  import java.util.Properties;
50  import javax.annotation.Nullable;
51  import org.apache.commons.io.FileUtils;
52  import org.apache.commons.io.FilenameUtils;
53  import org.apache.commons.io.filefilter.DirectoryFileFilter;
54  import org.apache.commons.io.filefilter.IOFileFilter;
55  import org.apache.commons.io.filefilter.WildcardFileFilter;
56  import org.apache.maven.artifact.Artifact;
57  import org.apache.maven.artifact.DependencyResolutionRequiredException;
58  import org.apache.maven.project.MavenProject;
59  import org.codehaus.plexus.context.Context;
60  
61  /**
62   * Environment, passed from MOJO to validators.
63   *
64   * @since 0.3
65   * @checkstyle ClassDataAbstractionCouplingCheck (300 lines)
66   */
67  @SuppressWarnings("PMD.TooManyMethods")
68  public final class DefaultMavenEnvironment implements MavenEnvironment {
69  
70      /**
71       * Maven project.
72       */
73      private MavenProject iproject;
74  
75      /**
76       * Plexus context.
77       */
78      private Context icontext;
79  
80      /**
81       * Plugin configuration.
82       */
83      private final Properties iproperties = new Properties();
84  
85      /**
86       * MOJO executor.
87       */
88      private MojoExecutor exectr;
89  
90      /**
91       * Excludes, regular expressions.
92       */
93      private final Collection<String> exc = new LinkedList<>();
94  
95      /**
96       * Xpath queries for pom.xml validation.
97       */
98      private final Collection<String> asser = new LinkedList<>();
99  
100     /**
101      * Source code encoding charset.
102      */
103     private String charset = "UTF-8";
104 
105     @Override
106     public String param(final String name, final String value) {
107         String ret = this.iproperties.getProperty(name);
108         if (ret == null) {
109             ret = value;
110         }
111         return ret;
112     }
113 
114     @Override
115     public File basedir() {
116         return this.iproject.getBasedir();
117     }
118 
119     @Override
120     public File tempdir() {
121         return new File(this.iproject.getBuild().getOutputDirectory());
122     }
123 
124     @Override
125     public File outdir() {
126         return new File(this.iproject.getBuild().getOutputDirectory());
127     }
128 
129     @Override
130     @SuppressWarnings({"PMD.AvoidInstantiatingObjectsInLoops", "deprecation"})
131     public Collection<String> classpath() {
132         final Collection<String> paths = new LinkedList<>();
133         final String blank = "%20";
134         final String whitespace = " ";
135         try {
136             for (final String name
137                 : this.iproject.getRuntimeClasspathElements()) {
138                 paths.add(
139                     name.replace(
140                         File.separatorChar, '/'
141                     ).replaceAll(whitespace, blank)
142                 );
143             }
144             for (final Artifact artifact
145                 : this.iproject.getDependencyArtifacts()) {
146                 if (artifact.getFile() != null) {
147                     paths.add(
148                         artifact.getFile().getAbsolutePath()
149                             .replace(File.separatorChar, '/')
150                             .replaceAll(whitespace, blank)
151                     );
152                 }
153             }
154         } catch (final DependencyResolutionRequiredException ex) {
155             throw new IllegalStateException("Failed to read classpath", ex);
156         }
157         return paths;
158     }
159 
160     @Override
161     public ClassLoader classloader() {
162         final List<URL> urls = new LinkedList<>();
163         for (final String path : this.classpath()) {
164             try {
165                 urls.add(
166                     URI.create(String.format("file:///%s", path)).toURL()
167                 );
168             } catch (final MalformedURLException ex) {
169                 throw new IllegalStateException("Failed to build URL", ex);
170             }
171         }
172         final URLClassLoader loader =
173             new DefaultMavenEnvironment.PrivilegedClassLoader(urls).run();
174         for (final URL url : loader.getURLs()) {
175             Logger.debug(this, "Classpath: %s", url);
176         }
177         return loader;
178     }
179 
180     @Override
181     public MavenProject project() {
182         return this.iproject;
183     }
184 
185     @Override
186     public Properties properties() {
187         return this.iproperties;
188     }
189 
190     @Override
191     public Context context() {
192         return this.icontext;
193     }
194 
195     @Override
196     public Properties config() {
197         return this.iproperties;
198     }
199 
200     @Override
201     public MojoExecutor executor() {
202         return this.exectr;
203     }
204 
205     @Override
206     public Collection<String> asserts() {
207         return this.asser;
208     }
209 
210     @Override
211     @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
212     public Collection<File> files(final String pattern) {
213         final Collection<File> files = new LinkedList<>();
214         final IOFileFilter filter = WildcardFileFilter.builder().setWildcards(pattern).get();
215         final String[] dirs = {
216             "src",
217         };
218         for (final String dir : dirs) {
219             final File sources = new File(this.basedir(), dir);
220             if (sources.exists()) {
221                 files.addAll(
222                     FileUtils.listFiles(
223                         sources,
224                         filter,
225                         DirectoryFileFilter.INSTANCE
226                     )
227                 );
228             }
229         }
230         return files;
231     }
232 
233     @Override
234     public boolean exclude(final String check, final String name) {
235         return Iterables.any(
236             this.excludes(check),
237             new DefaultMavenEnvironment.PathPredicate(name)
238         );
239     }
240 
241     @Override
242     public Collection<String> excludes(final String checker) {
243         final Collection<String> excludes = Collections2.transform(
244             this.exc,
245             new DefaultMavenEnvironment.CheckerExcludes(checker)
246         );
247         return Collections2.filter(excludes, Predicates.notNull());
248     }
249 
250     /**
251      * Set Maven Project (used mostly for unit testing).
252      * @param proj The project to set
253      */
254     public void setProject(final MavenProject proj) {
255         this.iproject = proj;
256     }
257 
258     /**
259      * Set context.
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      * @param exec The executor
269      */
270     public void setMojoExecutor(final MojoExecutor exec) {
271         this.exectr = exec;
272     }
273 
274     /**
275      * Set property.
276      * @param name Its name
277      * @param value Its value
278      */
279     public void setProperty(final String name, final String value) {
280         this.iproperties.setProperty(name, value);
281     }
282 
283     /**
284      * Set list of regular expressions to exclude.
285      * @param exprs Expressions
286      */
287     public void setExcludes(final Collection<String> exprs) {
288         this.exc.clear();
289         this.exc.addAll(exprs);
290     }
291 
292     /**
293      * Set list of Xpath queries for pom.xml validation.
294      * @param ass Xpath queries
295      */
296     public void setAsser(final Collection<String> ass) {
297         this.asser.clear();
298         this.asser.addAll(ass);
299     }
300 
301     public void setEncoding(final String encoding) {
302         this.charset = encoding;
303     }
304 
305     /**
306      * Get source files encoding.
307      * @return Charset of the source files
308      */
309     public Charset encoding() {
310         if (this.charset == null || this.charset.isEmpty()) {
311             this.charset = "UTF-8";
312         }
313         return Charset.forName(this.charset);
314     }
315 
316     /**
317      * Creates URL ClassLoader in privileged block.
318      *
319      * @since 0.1
320      */
321     private static final class PrivilegedClassLoader implements
322         PrivilegedAction<URLClassLoader> {
323         /**
324          * URLs for class loading.
325          */
326         private final List<URL> urls;
327 
328         /**
329          * Constructor.
330          * @param urls URLs for class loading.
331          */
332         private PrivilegedClassLoader(final List<URL> urls) {
333             this.urls = urls;
334         }
335 
336         @Override
337         public URLClassLoader run() {
338             return new URLClassLoader(
339                 this.urls.toArray(new URL[this.urls.size()]),
340                 Thread.currentThread().getContextClassLoader()
341             );
342         }
343     }
344 
345     /**
346      * Checks if two paths are equal.
347      *
348      * @since 0.1
349      */
350     private static class PathPredicate implements Predicate<String> {
351         /**
352          * Path to match.
353          */
354         private final String name;
355 
356         /**
357          * Constructor.
358          * @param name Path to match.
359          */
360         PathPredicate(final String name) {
361             this.name = name;
362         }
363 
364         @Override
365         public boolean apply(@Nullable final String input) {
366             return input != null
367                 && FilenameUtils.normalize(this.name, true).matches(input);
368         }
369     }
370 
371     /**
372      * Converts a checker exclude into exclude param.
373      *
374      * E.g. "checkstyle:.*" will become ".*".
375      *
376      * @since 0.1
377      */
378     private static class CheckerExcludes implements Function<String, String> {
379 
380         /**
381          * Name of checker.
382          */
383         private final String checker;
384 
385         /**
386          * Constructor.
387          * @param checker Name of checker.
388          */
389         CheckerExcludes(final String checker) {
390             this.checker = checker;
391         }
392 
393         @Nullable
394         @Override
395         public String apply(@Nullable final String input) {
396             String result = null;
397             if (input != null) {
398                 final String[] exclude = input.split(":", 2);
399                 if (this.checker.equals(exclude[0]) && exclude.length > 1) {
400                     result = exclude[1];
401                 }
402             }
403             return result;
404         }
405     }
406 }