1
2
3
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 com.qulice.spi.Binary;
14 import java.io.File;
15 import java.net.MalformedURLException;
16 import java.net.URI;
17 import java.net.URL;
18 import java.net.URLClassLoader;
19 import java.nio.charset.Charset;
20 import java.security.PrivilegedAction;
21 import java.util.Collection;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Properties;
25 import javax.annotation.Nullable;
26 import org.apache.commons.io.FileUtils;
27 import org.apache.commons.io.FilenameUtils;
28 import org.apache.commons.io.filefilter.DirectoryFileFilter;
29 import org.apache.commons.io.filefilter.IOFileFilter;
30 import org.apache.commons.io.filefilter.WildcardFileFilter;
31 import org.apache.maven.artifact.Artifact;
32 import org.apache.maven.artifact.DependencyResolutionRequiredException;
33 import org.apache.maven.model.Build;
34 import org.apache.maven.model.Resource;
35 import org.apache.maven.project.MavenProject;
36 import org.codehaus.plexus.context.Context;
37
38
39
40
41
42
43 @SuppressWarnings({"PMD.TooManyMethods", "PMD.GodClass"})
44 public final class DefaultMavenEnvironment implements MavenEnvironment {
45
46
47
48
49 private MavenProject iproject;
50
51
52
53
54 private Context icontext;
55
56
57
58
59 private final Properties iproperties = new Properties();
60
61
62
63
64 private MojoExecutor exectr;
65
66
67
68
69 private final Collection<String> exc = new LinkedList<>();
70
71
72
73
74 private final Collection<String> assertion = new LinkedList<>();
75
76
77
78
79 private String charset = "UTF-8";
80
81 @Override
82 public String param(final String name, final String value) {
83 String ret = this.iproperties.getProperty(name);
84 if (ret == null) {
85 ret = value;
86 }
87 return ret;
88 }
89
90 @Override
91 public File basedir() {
92 return this.iproject.getBasedir();
93 }
94
95 @Override
96 public File tempdir() {
97 return new File(this.iproject.getBuild().getOutputDirectory());
98 }
99
100 @Override
101 public File outdir() {
102 return new File(this.iproject.getBuild().getOutputDirectory());
103 }
104
105 @Override
106 @SuppressWarnings("deprecation")
107 public Collection<String> classpath() {
108 final Collection<String> paths = new LinkedList<>();
109 final String blank = "%20";
110 final String whitespace = " ";
111 try {
112 for (final String name
113 : this.iproject.getRuntimeClasspathElements()) {
114 paths.add(
115 name.replace(
116 File.separatorChar, '/'
117 ).replaceAll(whitespace, blank)
118 );
119 }
120 for (final Artifact artifact
121 : this.iproject.getDependencyArtifacts()) {
122 if (artifact.getFile() != null) {
123 paths.add(
124 artifact.getFile().getAbsolutePath()
125 .replace(File.separatorChar, '/')
126 .replaceAll(whitespace, blank)
127 );
128 }
129 }
130 } catch (final DependencyResolutionRequiredException ex) {
131 throw new IllegalStateException("Failed to read classpath", ex);
132 }
133 return paths;
134 }
135
136 @Override
137 public ClassLoader classloader() {
138 final List<URL> urls = new LinkedList<>();
139 for (final String path : this.classpath()) {
140 try {
141 urls.add(
142 URI.create(String.format("file:///%s", path)).toURL()
143 );
144 } catch (final MalformedURLException ex) {
145 throw new IllegalStateException("Failed to build URL", ex);
146 }
147 }
148 final URLClassLoader loader =
149 new DefaultMavenEnvironment.PrivilegedClassLoader(urls).run();
150 for (final URL url : loader.getURLs()) {
151 Logger.debug(this, "Classpath: %s", url);
152 }
153 return loader;
154 }
155
156 @Override
157 public MavenProject project() {
158 return this.iproject;
159 }
160
161 @Override
162 public Properties properties() {
163 return this.iproperties;
164 }
165
166 @Override
167 public Context context() {
168 return this.icontext;
169 }
170
171 @Override
172 public Properties config() {
173 return this.iproperties;
174 }
175
176 @Override
177 public MojoExecutor executor() {
178 return this.exectr;
179 }
180
181 @Override
182 public Collection<String> asserts() {
183 return this.assertion;
184 }
185
186 @Override
187 public Collection<File> files(final String pattern) {
188 final Collection<File> files = new LinkedList<>();
189 final IOFileFilter filter = WildcardFileFilter.builder().setWildcards(pattern).get();
190 for (final File sources : this.sources()) {
191 if (sources.exists()) {
192 for (final File found : FileUtils.listFiles(
193 sources,
194 filter,
195 DirectoryFileFilter.INSTANCE
196 )) {
197 if (new Binary(found).yes()) {
198 Logger.debug(
199 this,
200 "Skipping binary file %s",
201 found
202 );
203 } else {
204 files.add(found);
205 }
206 }
207 }
208 }
209 return files;
210 }
211
212 @Override
213 public boolean exclude(final String check, final String name) {
214 return Iterables.any(
215 this.excludes(check),
216 new DefaultMavenEnvironment.PathPredicate(name)
217 );
218 }
219
220 @Override
221 public Collection<String> excludes(final String checker) {
222 return Collections2.filter(
223 Collections2.transform(
224 this.exc,
225 new DefaultMavenEnvironment.CheckerExcludes(checker)
226 ),
227 Predicates.notNull()
228 );
229 }
230
231
232
233
234
235 public void setProject(final MavenProject proj) {
236 this.iproject = proj;
237 }
238
239
240
241
242
243 public void setContext(final Context ctx) {
244 this.icontext = ctx;
245 }
246
247
248
249
250
251 public void setMojoExecutor(final MojoExecutor exec) {
252 this.exectr = exec;
253 }
254
255
256
257
258
259
260 public void setProperty(final String name, final String value) {
261 this.iproperties.setProperty(name, value);
262 }
263
264
265
266
267
268 public void setExcludes(final Collection<String> exprs) {
269 this.exc.clear();
270 this.exc.addAll(exprs);
271 }
272
273
274
275
276
277 public void setAssertion(final Collection<String> ass) {
278 this.assertion.clear();
279 this.assertion.addAll(ass);
280 }
281
282 public void setEncoding(final String encoding) {
283 this.charset = encoding;
284 }
285
286 @Override
287 public Charset encoding() {
288 if (this.charset == null || this.charset.isEmpty()) {
289 this.charset = "UTF-8";
290 }
291 return Charset.forName(this.charset);
292 }
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309 private Collection<File> sources() {
310 final Collection<File> dirs = new LinkedList<>();
311 final Build build = this.iproject.getBuild();
312 final File output = this.buildDirectory(build);
313 this.addRoots(dirs, this.iproject.getCompileSourceRoots(), output);
314 this.addRoots(dirs, this.iproject.getTestCompileSourceRoots(), output);
315 if (build != null) {
316 this.addResources(dirs, build.getResources(), output);
317 this.addResources(dirs, build.getTestResources(), output);
318 }
319 if (dirs.isEmpty()) {
320 dirs.add(new File(this.basedir(), "src"));
321 }
322 return dirs;
323 }
324
325
326
327
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
340
341
342
343
344
345 private void addRoots(final Collection<File> dirs,
346 final List<String> roots, @Nullable final File output) {
347 if (roots != null) {
348 for (final String root : roots) {
349 final File resolved = this.resolve(root);
350 if (DefaultMavenEnvironment.outside(resolved, output)) {
351 dirs.add(resolved);
352 } else {
353 Logger.debug(
354 this,
355 "Skipping generated source root %s under %s",
356 resolved, output
357 );
358 }
359 }
360 }
361 }
362
363
364
365
366
367
368
369 private void addResources(final Collection<File> dirs,
370 final List<Resource> resources, @Nullable final File output) {
371 if (resources != null) {
372 for (final Resource res : resources) {
373 final File resolved = this.resolve(res.getDirectory());
374 if (DefaultMavenEnvironment.outside(resolved, output)) {
375 dirs.add(resolved);
376 } else {
377 Logger.debug(
378 this,
379 "Skipping generated resource directory %s under %s",
380 resolved, output
381 );
382 }
383 }
384 }
385 }
386
387
388
389
390
391
392
393 private static boolean outside(final File file,
394 @Nullable final File parent) {
395 boolean answer = true;
396 if (parent != null) {
397 final String head = FilenameUtils.normalize(
398 parent.getAbsolutePath(), true
399 );
400 final String tail = FilenameUtils.normalize(
401 file.getAbsolutePath(), true
402 );
403 if (head != null && tail != null
404 && (tail.equals(head) || tail.startsWith(head.concat("/")))) {
405 answer = false;
406 }
407 }
408 return answer;
409 }
410
411
412
413
414
415
416 private File resolve(final String path) {
417 final File file = new File(path);
418 final File resolved;
419 if (file.isAbsolute()) {
420 resolved = file;
421 } else {
422 resolved = new File(this.basedir(), path);
423 }
424 return resolved;
425 }
426
427
428
429
430
431 private static final class PrivilegedClassLoader implements
432 PrivilegedAction<URLClassLoader> {
433
434
435
436
437 private final List<URL> urls;
438
439
440
441
442
443 private PrivilegedClassLoader(final List<URL> urls) {
444 this.urls = urls;
445 }
446
447 @Override
448 public URLClassLoader run() {
449 return new URLClassLoader(
450 this.urls.toArray(new URL[0]),
451 Thread.currentThread().getContextClassLoader()
452 );
453 }
454 }
455
456
457
458
459
460 private static class PathPredicate implements Predicate<String> {
461
462
463
464
465 private final String name;
466
467
468
469
470
471 PathPredicate(final String name) {
472 this.name = name;
473 }
474
475 @Override
476 public boolean apply(@Nullable final String input) {
477 return input != null
478 && FilenameUtils.normalize(this.name, true).matches(input);
479 }
480 }
481
482
483
484
485
486
487
488
489 private static class CheckerExcludes implements Function<String, String> {
490
491
492
493
494 private static final String ALL = "*";
495
496
497
498
499 private final String checker;
500
501
502
503
504
505 CheckerExcludes(final String checker) {
506 this.checker = checker;
507 }
508
509 @Nullable
510 @Override
511 public String apply(@Nullable final String input) {
512 String result = null;
513 if (input != null) {
514 final String[] exclude = input.split(":", 2);
515 final String check = exclude[0];
516 final boolean appropriate = CheckerExcludes.ALL.equals(check)
517 || this.checker.equals(check);
518 if (appropriate && exclude.length > 1) {
519 result = exclude[1];
520 }
521 }
522 return result;
523 }
524 }
525 }