1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.plugins.javadoc;
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.FileNotFoundException;
24 import java.io.FileOutputStream;
25 import java.io.IOException;
26 import java.io.InputStreamReader;
27 import java.io.OutputStream;
28 import java.io.PrintStream;
29 import java.io.UnsupportedEncodingException;
30 import java.lang.reflect.Modifier;
31 import java.net.SocketTimeoutException;
32 import java.net.URI;
33 import java.net.URISyntaxException;
34 import java.net.URL;
35 import java.net.URLClassLoader;
36 import java.nio.charset.Charset;
37 import java.nio.charset.IllegalCharsetNameException;
38 import java.nio.file.FileVisitResult;
39 import java.nio.file.Files;
40 import java.nio.file.Path;
41 import java.nio.file.Paths;
42 import java.nio.file.SimpleFileVisitor;
43 import java.nio.file.StandardCopyOption;
44 import java.nio.file.attribute.BasicFileAttributes;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.Collection;
48 import java.util.Collections;
49 import java.util.LinkedHashSet;
50 import java.util.List;
51 import java.util.NoSuchElementException;
52 import java.util.Properties;
53 import java.util.Set;
54 import java.util.StringTokenizer;
55 import java.util.jar.JarEntry;
56 import java.util.jar.JarInputStream;
57 import java.util.regex.Matcher;
58 import java.util.regex.Pattern;
59 import java.util.regex.PatternSyntaxException;
60 import java.util.stream.Collectors;
61
62 import org.apache.http.HttpHeaders;
63 import org.apache.http.HttpHost;
64 import org.apache.http.HttpResponse;
65 import org.apache.http.HttpStatus;
66 import org.apache.http.auth.AuthScope;
67 import org.apache.http.auth.Credentials;
68 import org.apache.http.auth.UsernamePasswordCredentials;
69 import org.apache.http.client.CredentialsProvider;
70 import org.apache.http.client.config.CookieSpecs;
71 import org.apache.http.client.config.RequestConfig;
72 import org.apache.http.client.methods.HttpGet;
73 import org.apache.http.client.protocol.HttpClientContext;
74 import org.apache.http.config.Registry;
75 import org.apache.http.config.RegistryBuilder;
76 import org.apache.http.conn.socket.ConnectionSocketFactory;
77 import org.apache.http.conn.socket.PlainConnectionSocketFactory;
78 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
79 import org.apache.http.impl.client.BasicCredentialsProvider;
80 import org.apache.http.impl.client.CloseableHttpClient;
81 import org.apache.http.impl.client.HttpClientBuilder;
82 import org.apache.http.impl.client.HttpClients;
83 import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
84 import org.apache.http.message.BasicHeader;
85 import org.apache.maven.plugin.logging.Log;
86 import org.apache.maven.project.MavenProject;
87 import org.apache.maven.settings.Proxy;
88 import org.apache.maven.settings.Settings;
89 import org.apache.maven.shared.invoker.DefaultInvocationRequest;
90 import org.apache.maven.shared.invoker.DefaultInvoker;
91 import org.apache.maven.shared.invoker.InvocationOutputHandler;
92 import org.apache.maven.shared.invoker.InvocationRequest;
93 import org.apache.maven.shared.invoker.InvocationResult;
94 import org.apache.maven.shared.invoker.Invoker;
95 import org.apache.maven.shared.invoker.MavenInvocationException;
96 import org.apache.maven.shared.invoker.PrintStreamHandler;
97 import org.apache.maven.shared.utils.io.DirectoryScanner;
98 import org.apache.maven.shared.utils.io.FileUtils;
99 import org.codehaus.plexus.languages.java.version.JavaVersion;
100 import org.codehaus.plexus.util.cli.CommandLineException;
101 import org.codehaus.plexus.util.cli.CommandLineUtils;
102 import org.codehaus.plexus.util.cli.Commandline;
103
104
105
106
107
108
109
110 public class JavadocUtil {
111
112 public static final int DEFAULT_TIMEOUT = 2000;
113
114
115 protected static final String ERROR_INIT_VM =
116 "Error occurred during initialization of VM, try to reduce the Java heap size for the MAVEN_OPTS "
117 + "environment variable using -Xms:<size> and -Xmx:<size>.";
118
119
120
121
122
123
124
125
126
127
128
129
130 public static Collection<Path> prunePaths(MavenProject project, Collection<String> paths, boolean includeFiles) {
131 final Path projectBasedir = project.getBasedir().toPath();
132
133 Set<Path> pruned = new LinkedHashSet<>(paths.size());
134 for (String path : paths) {
135 if (path == null) {
136 continue;
137 }
138
139 Path resolvedPath = projectBasedir.resolve(path);
140
141 if (Files.isDirectory(resolvedPath) || includeFiles && Files.isRegularFile(resolvedPath)) {
142 pruned.add(resolvedPath.toAbsolutePath());
143 }
144 }
145
146 return pruned;
147 }
148
149
150
151
152
153
154
155
156
157 public static Collection<Path> pruneDirs(MavenProject project, Collection<String> dirs) {
158 return prunePaths(project, dirs, false);
159 }
160
161
162
163
164
165
166
167
168 protected static List<String> pruneFiles(Collection<String> files) {
169 List<String> pruned = new ArrayList<>(files.size());
170 for (String f : files) {
171 if (!shouldPruneFile(f, pruned)) {
172 pruned.add(f);
173 }
174 }
175
176 return pruned;
177 }
178
179
180
181
182
183
184
185
186
187 public static boolean shouldPruneFile(String f, List<String> pruned) {
188 if (f != null) {
189 if (Files.isRegularFile(Paths.get(f)) && !pruned.contains(f)) {
190 return false;
191 }
192 }
193
194 return true;
195 }
196
197
198
199
200
201
202
203
204 protected static List<String> getExcludedPackages(
205 Collection<Path> sourcePaths, Collection<String> excludedPackages) {
206 List<String> excludedNames = new ArrayList<>();
207 for (Path sourcePath : sourcePaths) {
208 excludedNames.addAll(getExcludedPackages(sourcePath, excludedPackages));
209 }
210
211 return excludedNames;
212 }
213
214
215
216
217
218
219
220
221
222 protected static String quotedArgument(String value) {
223 if (value == null) {
224 return null;
225 }
226 String arg = value;
227
228 List<String> list = Arrays.stream(arg.split("\n")).map(String::trim).collect(Collectors.toList());
229 arg = String.join("", list);
230
231 if (arg != null && !arg.isEmpty()) {
232 arg = arg.replace("'", "\\'");
233 arg = "'" + arg + "'";
234 }
235
236 return arg;
237 }
238
239
240
241
242
243
244
245
246 protected static String quotedPathArgument(String value) {
247 String path = value;
248
249 if (path != null && !path.isEmpty()) {
250 path = path.replace('\\', '/');
251 if (path.contains("'")) {
252 StringBuilder pathBuilder = new StringBuilder();
253 pathBuilder.append('\'');
254 String[] split = path.split("'");
255
256 for (int i = 0; i < split.length; i++) {
257 if (i != split.length - 1) {
258 pathBuilder.append(split[i].trim()).append("\\'");
259 } else {
260 pathBuilder.append(split[i]);
261 }
262 }
263 pathBuilder.append('\'');
264 path = pathBuilder.toString();
265 } else {
266 path = "'" + path + "'";
267 }
268 }
269
270 return path;
271 }
272
273
274
275
276
277
278
279
280
281
282
283 protected static void copyJavadocResources(File outputDirectory, File javadocDir, String excludedocfilessubdir)
284 throws IOException {
285 if (!javadocDir.isDirectory()) {
286 return;
287 }
288
289 List<String> excludes = new ArrayList<>(Arrays.asList(FileUtils.getDefaultExcludes()));
290
291 if (excludedocfilessubdir != null && !excludedocfilessubdir.isEmpty()) {
292 StringTokenizer st = new StringTokenizer(excludedocfilessubdir, ":");
293 String current;
294 while (st.hasMoreTokens()) {
295 current = st.nextToken();
296 excludes.add("**/" + current.trim() + "/**");
297 }
298 }
299
300 List<String> docFiles = FileUtils.getDirectoryNames(
301 javadocDir, "resources,**/doc-files", String.join(",", excludes), false, true);
302 for (String docFile : docFiles) {
303 File docFileOutput = new File(outputDirectory, docFile);
304 Files.createDirectories(Paths.get(docFileOutput.getAbsolutePath()));
305 FileUtils.copyDirectoryStructure(new File(javadocDir, docFile), docFileOutput);
306 List<String> files = FileUtils.getFileAndDirectoryNames(
307 docFileOutput, String.join(",", excludes), null, true, true, true, true);
308 for (String filename : files) {
309 File file = new File(filename);
310
311 if (file.isDirectory()) {
312 FileUtils.deleteDirectory(file);
313 } else {
314 file.delete();
315 }
316 }
317 }
318 }
319
320
321
322
323
324
325
326
327
328 protected static List<String> getIncludedFiles(
329 File sourceDirectory, String[] fileList, Collection<String> excludePackages) {
330 List<String> files = new ArrayList<>();
331
332 List<Pattern> excludePackagePatterns = new ArrayList<>(excludePackages.size());
333 for (String excludePackage : excludePackages) {
334 excludePackagePatterns.add(Pattern.compile(excludePackage
335 .replace('.', File.separatorChar)
336 .replace("\\", "\\\\")
337 .replace("*", ".+")
338 .concat("[\\\\/][^\\\\/]+\\.java")));
339 }
340
341 for (String file : fileList) {
342 boolean excluded = false;
343 for (Pattern excludePackagePattern : excludePackagePatterns) {
344 if (excludePackagePattern.matcher(file).matches()) {
345 excluded = true;
346 break;
347 }
348 }
349
350 if (!excluded) {
351 files.add(file.replace('\\', '/'));
352 }
353 }
354
355 return files;
356 }
357
358
359
360
361
362
363
364
365
366 protected static Collection<String> getExcludedPackages(
367 final Path sourceDirectory, Collection<String> excludePackagenames) {
368 final String regexFileSeparator = File.separator.replace("\\", "\\\\");
369
370 final Collection<String> fileList = new ArrayList<>();
371
372 try {
373 Files.walkFileTree(sourceDirectory, new SimpleFileVisitor<Path>() {
374 @Override
375 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
376 if (file.getFileName().toString().endsWith(".java")) {
377 fileList.add(
378 sourceDirectory.relativize(file.getParent()).toString());
379 }
380 return FileVisitResult.CONTINUE;
381 }
382 });
383 } catch (IOException e) {
384
385 }
386
387 List<String> files = new ArrayList<>();
388 for (String excludePackagename : excludePackagenames) {
389
390
391
392
393
394 Pattern p = Pattern.compile(excludePackagename
395 .replace(".", regexFileSeparator)
396 .replaceFirst("^\\*", ".+")
397 .replace("*", "[^" + regexFileSeparator + "]+"));
398
399 for (String aFileList : fileList) {
400 if (p.matcher(aFileList).matches()) {
401 files.add(aFileList.replace(File.separatorChar, '.'));
402 }
403 }
404 }
405
406 return files;
407 }
408
409
410
411
412
413
414
415
416
417
418 protected static List<String> getFilesFromSource(
419 File sourceDirectory,
420 List<String> sourceFileIncludes,
421 List<String> sourceFileExcludes,
422 Collection<String> excludePackages) {
423 DirectoryScanner ds = new DirectoryScanner();
424 if (sourceFileIncludes == null) {
425 sourceFileIncludes = Collections.singletonList("**/*.java");
426 }
427 ds.setIncludes(sourceFileIncludes.toArray(new String[0]));
428 if (sourceFileExcludes != null && !sourceFileExcludes.isEmpty()) {
429 ds.setExcludes(sourceFileExcludes.toArray(new String[0]));
430 }
431 ds.setBasedir(sourceDirectory);
432 ds.scan();
433
434 String[] fileList = ds.getIncludedFiles();
435
436 List<String> files = new ArrayList<>();
437 if (fileList.length != 0) {
438 files.addAll(getIncludedFiles(sourceDirectory, fileList, excludePackages));
439 }
440
441 return files;
442 }
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459 protected static JavaVersion getJavadocVersion(File javadocExe)
460 throws IOException, CommandLineException, IllegalArgumentException {
461 if ((javadocExe == null) || (!javadocExe.exists()) || (!javadocExe.isFile())) {
462 throw new IOException("The javadoc executable '" + javadocExe + "' doesn't exist or is not a file. ");
463 }
464
465 Commandline cmd = new Commandline();
466 cmd.setExecutable(javadocExe.getAbsolutePath());
467 cmd.setWorkingDirectory(javadocExe.getParentFile());
468 cmd.createArg().setValue("-J-version");
469
470 CommandLineUtils.StringStreamConsumer out = new JavadocOutputStreamConsumer();
471 CommandLineUtils.StringStreamConsumer err = new JavadocOutputStreamConsumer();
472
473 int exitCode = CommandLineUtils.executeCommandLine(cmd, out, err);
474 String errOutput = err.getOutput();
475 if (exitCode != 0) {
476 StringBuilder msg = new StringBuilder("Exit code: " + exitCode + " - " + errOutput);
477 msg.append('\n');
478 msg.append("Command line was:").append(CommandLineUtils.toString(cmd.getCommandline()));
479 throw new CommandLineException(msg.toString());
480 }
481
482 if (!errOutput.isEmpty()) {
483 return JavaVersion.parse(extractJavadocVersion(errOutput));
484 } else {
485 String outOutput = out.getOutput();
486 if (!outOutput.isEmpty()) {
487 return JavaVersion.parse(extractJavadocVersion(outOutput));
488 }
489 }
490
491 throw new IllegalArgumentException("No output found from the command line 'javadoc -J-version'");
492 }
493
494 private static final Pattern EXTRACT_JAVADOC_VERSION_PATTERN =
495 Pattern.compile("(?s).*?[^a-zA-Z](([0-9]+\\.?[0-9]*)(\\.[0-9]+)?).*");
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538 protected static String extractJavadocVersion(String output) {
539 if (output == null) {
540 throw new NullPointerException("The output cannot be null.");
541 }
542
543 if (output.isEmpty()) {
544 throw new IllegalArgumentException("The output cannot be empty.");
545 }
546
547 Pattern pattern = EXTRACT_JAVADOC_VERSION_PATTERN;
548
549 Matcher matcher = pattern.matcher(output);
550 if (!matcher.matches()) {
551 throw new PatternSyntaxException(
552 "Unrecognized version of Javadoc: '" + output + "'",
553 pattern.pattern(),
554 pattern.toString().length() - 1);
555 }
556
557 return matcher.group(1);
558 }
559
560 private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_0 = Pattern.compile("^\\s*(\\d+)\\s*?\\s*$");
561
562 private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_1 =
563 Pattern.compile("^\\s*(\\d+)\\s*k(b)?\\s*$", Pattern.CASE_INSENSITIVE);
564
565 private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_2 =
566 Pattern.compile("^\\s*(\\d+)\\s*m(b)?\\s*$", Pattern.CASE_INSENSITIVE);
567
568 private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_3 =
569 Pattern.compile("^\\s*(\\d+)\\s*g(b)?\\s*$", Pattern.CASE_INSENSITIVE);
570
571 private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_4 =
572 Pattern.compile("^\\s*(\\d+)\\s*t(b)?\\s*$", Pattern.CASE_INSENSITIVE);
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603 protected static String parseJavadocMemory(String memory) {
604 if (memory == null) {
605 throw new NullPointerException("The memory cannot be null.");
606 }
607 if (memory.isEmpty()) {
608 throw new IllegalArgumentException("The memory cannot be empty.");
609 }
610
611 Matcher m0 = PARSE_JAVADOC_MEMORY_PATTERN_0.matcher(memory);
612 if (m0.matches()) {
613 return m0.group(1) + "m";
614 }
615
616 Matcher m1 = PARSE_JAVADOC_MEMORY_PATTERN_1.matcher(memory);
617 if (m1.matches()) {
618 return m1.group(1) + "k";
619 }
620
621 Matcher m2 = PARSE_JAVADOC_MEMORY_PATTERN_2.matcher(memory);
622 if (m2.matches()) {
623 return m2.group(1) + "m";
624 }
625
626 Matcher m3 = PARSE_JAVADOC_MEMORY_PATTERN_3.matcher(memory);
627 if (m3.matches()) {
628 return (Integer.parseInt(m3.group(1)) * 1024) + "m";
629 }
630
631 Matcher m4 = PARSE_JAVADOC_MEMORY_PATTERN_4.matcher(memory);
632 if (m4.matches()) {
633 return (Integer.parseInt(m4.group(1)) * 1024 * 1024) + "m";
634 }
635
636 throw new IllegalArgumentException("Could convert not to a memory size: " + memory);
637 }
638
639
640
641
642
643
644
645 protected static boolean validateEncoding(String charsetName) {
646 if (charsetName == null || charsetName.isEmpty()) {
647 return false;
648 }
649
650 try {
651 return Charset.isSupported(charsetName);
652 } catch (IllegalCharsetNameException e) {
653 return false;
654 }
655 }
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670 protected static List<String> getTagletClassNames(File jarFile)
671 throws IOException, ClassNotFoundException, NoClassDefFoundError {
672 List<String> classes = getClassNamesFromJar(jarFile);
673 URLClassLoader cl;
674
675
676 File tools = new File(System.getProperty("java.home"), "../lib/tools.jar");
677 if (tools.exists() && tools.isFile()) {
678 cl = new URLClassLoader(
679 new URL[] {jarFile.toURI().toURL(), tools.toURI().toURL()}, null);
680 } else {
681 cl = new URLClassLoader(new URL[] {jarFile.toURI().toURL()}, ClassLoader.getSystemClassLoader());
682 }
683
684 List<String> tagletClasses = new ArrayList<>();
685
686 Class<?> tagletClass;
687
688 try {
689 tagletClass = cl.loadClass("com.sun.tools.doclets.Taglet");
690 } catch (ClassNotFoundException e) {
691 tagletClass = cl.loadClass("jdk.javadoc.doclet.Taglet");
692 }
693
694 for (String s : classes) {
695 Class<?> c = cl.loadClass(s);
696
697 if (tagletClass.isAssignableFrom(c) && !Modifier.isAbstract(c.getModifiers())) {
698 tagletClasses.add(c.getName());
699 }
700 }
701
702 try {
703 cl.close();
704 } catch (IOException ex) {
705
706 }
707
708 return tagletClasses;
709 }
710
711
712
713
714
715
716
717
718
719 protected static void copyResource(URL url, File file) throws IOException {
720 if (file == null) {
721 throw new NullPointerException("The file can't be null.");
722 }
723 if (url == null) {
724 throw new NullPointerException("The url could not be null.");
725 }
726
727 Files.copy(url.openStream(), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
728 }
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749 protected static void invokeMaven(
750 Log log,
751 File localRepositoryDir,
752 File projectFile,
753 List<String> goals,
754 Properties properties,
755 File invokerLog,
756 File globalSettingsFile,
757 File userSettingsFile,
758 File globalToolchainsFile,
759 File userToolchainsFile)
760 throws MavenInvocationException {
761 if (projectFile == null) {
762 throw new IllegalArgumentException("projectFile should be not null.");
763 }
764 if (!projectFile.isFile()) {
765 throw new IllegalArgumentException(projectFile.getAbsolutePath() + " is not a file.");
766 }
767 if (goals == null || goals.isEmpty()) {
768 throw new IllegalArgumentException("goals should be not empty.");
769 }
770 if (localRepositoryDir == null || !localRepositoryDir.isDirectory()) {
771 throw new IllegalArgumentException(
772 "localRepositoryDir '" + localRepositoryDir + "' should be a directory.");
773 }
774
775 String mavenHome = getMavenHome(log);
776 if (mavenHome == null || mavenHome.isEmpty()) {
777 String msg = "Could NOT invoke Maven because no Maven Home is defined. You need to have set the M2_HOME "
778 + "system env variable or a maven.home Java system properties.";
779 if (log != null) {
780 log.error(msg);
781 } else {
782 System.err.println(msg);
783 }
784 return;
785 }
786
787 Invoker invoker = new DefaultInvoker();
788 invoker.setMavenHome(new File(mavenHome));
789 invoker.setLocalRepositoryDirectory(localRepositoryDir);
790
791 InvocationRequest request = new DefaultInvocationRequest();
792 request.setBaseDirectory(projectFile.getParentFile());
793 request.setPomFile(projectFile);
794 if (globalSettingsFile != null && globalSettingsFile.isFile()) {
795 request.setGlobalSettingsFile(globalSettingsFile);
796 }
797 if (userSettingsFile != null && userSettingsFile.isFile()) {
798 request.setUserSettingsFile(userSettingsFile);
799 }
800 if (globalToolchainsFile != null && globalToolchainsFile.isFile()) {
801 request.setGlobalToolchainsFile(globalToolchainsFile);
802 }
803 if (userToolchainsFile != null && userToolchainsFile.isFile()) {
804 request.setToolchainsFile(userToolchainsFile);
805 }
806 request.setBatchMode(true);
807 if (log != null) {
808 request.setDebug(log.isDebugEnabled());
809 } else {
810 request.setDebug(true);
811 }
812 request.addArgs(goals);
813 if (properties != null) {
814 request.setProperties(properties);
815 }
816 File javaHome = getJavaHome(log);
817 if (javaHome != null) {
818 request.setJavaHome(javaHome);
819 }
820
821 if (log != null && log.isDebugEnabled()) {
822 log.debug("Invoking Maven for the goals: " + goals + " with "
823 + (properties == null ? "no properties" : "properties=" + properties));
824 }
825 InvocationResult result = invoke(log, invoker, request, invokerLog, goals, properties, null);
826
827 if (result.getExitCode() != 0) {
828 String invokerLogContent = readFile(invokerLog, "UTF-8");
829
830
831 if (invokerLogContent != null
832 && (!invokerLogContent.contains("Scanning for projects...")
833 || invokerLogContent.contains(OutOfMemoryError.class.getName()))) {
834 if (log != null) {
835 log.error("Error occurred during initialization of VM, trying to use an empty MAVEN_OPTS...");
836
837 if (log.isDebugEnabled()) {
838 log.debug("Reinvoking Maven for the goals: " + goals + " with an empty MAVEN_OPTS...");
839 }
840 }
841 result = invoke(log, invoker, request, invokerLog, goals, properties, "");
842 }
843 }
844
845 if (result.getExitCode() != 0) {
846 String invokerLogContent = readFile(invokerLog, "UTF-8");
847
848
849 if (invokerLogContent != null
850 && (!invokerLogContent.contains("Scanning for projects...")
851 || invokerLogContent.contains(OutOfMemoryError.class.getName()))) {
852 throw new MavenInvocationException(ERROR_INIT_VM);
853 }
854
855 throw new MavenInvocationException(
856 "Error when invoking Maven, consult the invoker log file: " + invokerLog.getAbsolutePath());
857 }
858 }
859
860
861
862
863
864
865
866
867
868 protected static String readFile(final File javaFile, final String encoding) {
869 try {
870 return new String(Files.readAllBytes(javaFile.toPath()), Charset.forName(encoding));
871 } catch (IOException e) {
872 return null;
873 }
874 }
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891 protected static String[] splitPath(final String path) {
892 if (path == null) {
893 return null;
894 }
895
896 List<String> subpaths = new ArrayList<>();
897 PathTokenizer pathTokenizer = new PathTokenizer(path);
898 while (pathTokenizer.hasMoreTokens()) {
899 subpaths.add(pathTokenizer.nextToken());
900 }
901
902 return subpaths.toArray(new String[0]);
903 }
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921 protected static String unifyPathSeparator(final String path) {
922 if (path == null) {
923 return null;
924 }
925
926 return String.join(File.pathSeparator, splitPath(path));
927 }
928
929
930
931
932
933
934
935
936
937
938 private static List<String> getClassNamesFromJar(File jarFile) throws IOException {
939 if (jarFile == null || !jarFile.exists() || !jarFile.isFile()) {
940 throw new IOException("The jar '" + jarFile + "' doesn't exist or is not a file.");
941 }
942
943 List<String> classes = new ArrayList<>();
944 Pattern pattern = Pattern.compile("(?i)^(META-INF/versions/(?<v>[0-9]+)/)?(?<n>.+)[.]class$");
945 try (JarInputStream jarStream = new JarInputStream(Files.newInputStream(jarFile.toPath()))) {
946 for (JarEntry jarEntry = jarStream.getNextJarEntry();
947 jarEntry != null;
948 jarEntry = jarStream.getNextJarEntry()) {
949 Matcher matcher = pattern.matcher(jarEntry.getName());
950 if (matcher.matches()) {
951 String version = matcher.group("v");
952 if ((version == null || version.isEmpty()) || JavaVersion.JAVA_VERSION.isAtLeast(version)) {
953 String name = matcher.group("n");
954
955 classes.add(name.replaceAll("/", "\\."));
956 }
957 }
958
959 jarStream.closeEntry();
960 }
961 }
962
963 return classes;
964 }
965
966
967
968
969
970
971
972
973
974
975
976
977
978 private static InvocationResult invoke(
979 Log log,
980 Invoker invoker,
981 InvocationRequest request,
982 File invokerLog,
983 List<String> goals,
984 Properties properties,
985 String mavenOpts)
986 throws MavenInvocationException {
987 PrintStream ps;
988 OutputStream os = null;
989 if (invokerLog != null) {
990 if (log != null && log.isDebugEnabled()) {
991 log.debug("Using " + invokerLog.getAbsolutePath() + " to log the invoker");
992 }
993
994 try {
995 if (!invokerLog.exists()) {
996
997 invokerLog.getParentFile().mkdirs();
998 }
999 os = new FileOutputStream(invokerLog);
1000 ps = new PrintStream(os, true, "UTF-8");
1001 } catch (FileNotFoundException e) {
1002 if (log != null && log.isErrorEnabled()) {
1003 log.error("FileNotFoundException: " + e.getMessage() + ". Using System.out to log the invoker.");
1004 }
1005 ps = System.out;
1006 } catch (UnsupportedEncodingException e) {
1007 if (log != null && log.isErrorEnabled()) {
1008 log.error("UnsupportedEncodingException: " + e.getMessage()
1009 + ". Using System.out to log the invoker.");
1010 }
1011 ps = System.out;
1012 }
1013 } else {
1014 if (log != null && log.isDebugEnabled()) {
1015 log.debug("Using System.out to log the invoker.");
1016 }
1017
1018 ps = System.out;
1019 }
1020
1021 if (mavenOpts != null) {
1022 request.setMavenOpts(mavenOpts);
1023 }
1024
1025 InvocationOutputHandler outputHandler = new PrintStreamHandler(ps, false);
1026 request.setOutputHandler(outputHandler);
1027
1028 try (OutputStream closeMe = os) {
1029 outputHandler.consumeLine("Invoking Maven for the goals: " + goals + " with "
1030 + (properties == null ? "no properties" : "properties=" + properties));
1031 outputHandler.consumeLine("");
1032 outputHandler.consumeLine("M2_HOME=" + getMavenHome(log));
1033 outputHandler.consumeLine("MAVEN_OPTS=" + getMavenOpts(log));
1034 outputHandler.consumeLine("JAVA_HOME=" + getJavaHome(log));
1035 outputHandler.consumeLine("JAVA_OPTS=" + getJavaOpts(log));
1036 outputHandler.consumeLine("");
1037 return invoker.execute(request);
1038 } catch (IOException ioe) {
1039 throw new MavenInvocationException("IOException while consuming invocation output", ioe);
1040 }
1041 }
1042
1043
1044
1045
1046
1047
1048
1049 private static String getMavenHome(Log log) {
1050 String mavenHome = System.getProperty("maven.home");
1051
1052 File m2Home = new File(mavenHome);
1053 if (!m2Home.exists()) {
1054 if (log != null && log.isErrorEnabled()) {
1055 log.error("Cannot find Maven application directory. Specify 'maven.home' system property.");
1056 }
1057 }
1058
1059 return mavenHome;
1060 }
1061
1062
1063
1064
1065
1066
1067 private static String getMavenOpts(Log log) {
1068 return CommandLineUtils.getSystemEnvVars().getProperty("MAVEN_OPTS");
1069 }
1070
1071
1072
1073
1074
1075
1076
1077
1078 private static File getJavaHome(Log log) {
1079 File javaHome = null;
1080
1081 String javaHomeValue = CommandLineUtils.getSystemEnvVars().getProperty("JAVA_HOME");
1082
1083
1084 if (System.getProperty("maven.home") == null || javaHomeValue == null) {
1085
1086 if (SystemUtils.IS_OS_MAC_OSX || JavaVersion.JAVA_VERSION.isAtLeast("9")) {
1087 javaHome = SystemUtils.getJavaHome();
1088 } else {
1089 javaHome = new File(SystemUtils.getJavaHome(), "..");
1090 }
1091 }
1092
1093 if (javaHome == null || !javaHome.exists()) {
1094 javaHome = new File(javaHomeValue);
1095 }
1096
1097 if (javaHome == null || !javaHome.exists()) {
1098 if (log != null && log.isErrorEnabled()) {
1099 log.error("Cannot find Java application directory. Either specify 'java.home' system property, or "
1100 + "JAVA_HOME environment variable.");
1101 }
1102 }
1103
1104 return javaHome;
1105 }
1106
1107
1108
1109
1110
1111
1112 private static String getJavaOpts(Log log) {
1113 return CommandLineUtils.getSystemEnvVars().getProperty("JAVA_OPTS");
1114 }
1115
1116
1117
1118
1119
1120
1121
1122
1123 private static class PathTokenizer {
1124
1125
1126
1127 private StringTokenizer tokenizer;
1128
1129
1130
1131
1132 private String lookahead = null;
1133
1134
1135
1136
1137 private boolean dosStyleFilesystem;
1138
1139
1140
1141
1142
1143
1144 PathTokenizer(String path) {
1145 tokenizer = new StringTokenizer(path, ":;", false);
1146 dosStyleFilesystem = File.pathSeparatorChar == ';';
1147 }
1148
1149
1150
1151
1152
1153
1154
1155
1156 public boolean hasMoreTokens() {
1157 return lookahead != null || tokenizer.hasMoreTokens();
1158 }
1159
1160
1161
1162
1163
1164
1165
1166 public String nextToken() throws NoSuchElementException {
1167 String token;
1168 if (lookahead != null) {
1169 token = lookahead;
1170 lookahead = null;
1171 } else {
1172 token = tokenizer.nextToken().trim();
1173 }
1174
1175 if (token.length() == 1
1176 && Character.isLetter(token.charAt(0))
1177 && dosStyleFilesystem
1178 && tokenizer.hasMoreTokens()) {
1179
1180
1181 String nextToken = tokenizer.nextToken().trim();
1182 if (nextToken.startsWith("\\") || nextToken.startsWith("/")) {
1183
1184
1185
1186 token += ":" + nextToken;
1187 } else {
1188
1189 lookahead = nextToken;
1190 }
1191 }
1192 return token.trim();
1193 }
1194 }
1195
1196
1197
1198
1199
1200
1201
1202 protected static class JavadocOutputStreamConsumer extends CommandLineUtils.StringStreamConsumer {
1203 @Override
1204 public void consumeLine(String line) {
1205 if (!line.startsWith("Picked up ")) {
1206 super.consumeLine(line);
1207 }
1208 }
1209 }
1210
1211 static List<String> toList(String src) {
1212 return toList(src, null, null);
1213 }
1214
1215 static List<String> toList(String src, String elementPrefix, String elementSuffix) {
1216 if (src == null || src.isEmpty()) {
1217 return null;
1218 }
1219
1220 List<String> result = new ArrayList<>();
1221
1222 StringTokenizer st = new StringTokenizer(src, "[,:;]");
1223 StringBuilder sb = new StringBuilder(256);
1224 while (st.hasMoreTokens()) {
1225 sb.setLength(0);
1226 if (elementPrefix != null && !elementPrefix.isEmpty()) {
1227 sb.append(elementPrefix);
1228 }
1229
1230 sb.append(st.nextToken());
1231
1232 if (elementSuffix != null && !elementSuffix.isEmpty()) {
1233 sb.append(elementSuffix);
1234 }
1235
1236 result.add(sb.toString().trim());
1237 }
1238
1239 return result;
1240 }
1241
1242 static <T> List<T> toList(T[] multiple) {
1243 return toList(null, multiple);
1244 }
1245
1246 static <T> List<T> toList(T single, T[] multiple) {
1247 if (single == null && (multiple == null || multiple.length < 1)) {
1248 return null;
1249 }
1250
1251 List<T> result = new ArrayList<>();
1252 if (single != null) {
1253 result.add(single);
1254 }
1255
1256 if (multiple != null && multiple.length > 0) {
1257 result.addAll(Arrays.asList(multiple));
1258 }
1259
1260 return result;
1261 }
1262
1263
1264
1265
1266 @Deprecated
1267 public static String toRelative(File basedir, String absolutePath) {
1268 String relative;
1269
1270 absolutePath = absolutePath.replace('\\', '/');
1271 String basedirPath = basedir.getAbsolutePath().replace('\\', '/');
1272
1273 if (absolutePath.startsWith(basedirPath)) {
1274 relative = absolutePath.substring(basedirPath.length());
1275 if (relative.startsWith("/")) {
1276 relative = relative.substring(1);
1277 }
1278 if (relative.length() <= 0) {
1279 relative = ".";
1280 }
1281 } else {
1282 relative = absolutePath;
1283 }
1284
1285 return relative;
1286 }
1287
1288
1289
1290
1291
1292
1293 public static boolean isNotEmpty(final Collection<?> collection) {
1294 return collection != null && !collection.isEmpty();
1295 }
1296
1297
1298
1299
1300
1301
1302 public static boolean isEmpty(final Collection<?> collection) {
1303 return collection == null || collection.isEmpty();
1304 }
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315 protected static URL getRedirectUrl(URL url, Settings settings) throws IOException {
1316 String protocol = url.getProtocol();
1317 if (!"http".equals(protocol) && !"https".equals(protocol)) {
1318 return url;
1319 }
1320
1321 try (CloseableHttpClient httpClient = createHttpClient(settings, url)) {
1322 HttpClientContext httpContext = HttpClientContext.create();
1323 HttpGet httpMethod = new HttpGet(url.toString());
1324 HttpResponse response = httpClient.execute(httpMethod, httpContext);
1325 int status = response.getStatusLine().getStatusCode();
1326 if (status != HttpStatus.SC_OK) {
1327 throw new FileNotFoundException(
1328 "Unexpected HTTP status code " + status + " getting resource " + url.toExternalForm() + ".");
1329 }
1330
1331 List<URI> redirects = httpContext.getRedirectLocations();
1332
1333 if (isEmpty(redirects)) {
1334 return url;
1335 } else {
1336 URI last = redirects.get(redirects.size() - 1);
1337
1338
1339
1340 String truncate = "index.html";
1341 if (last.getPath().endsWith("/" + truncate)) {
1342 try {
1343 String fixedPath =
1344 last.getPath().substring(0, last.getPath().length() - truncate.length());
1345 last = new URI(
1346 last.getScheme(), last.getAuthority(), fixedPath, last.getQuery(), last.getFragment());
1347 } catch (URISyntaxException ex) {
1348
1349 }
1350 }
1351 return last.toURL();
1352 }
1353 }
1354 }
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369 protected static boolean isValidPackageList(URL url, Settings settings, boolean validateContent)
1370 throws IOException {
1371 if (url == null) {
1372 throw new IllegalArgumentException("The url is null");
1373 }
1374
1375 try (BufferedReader reader = getReader(url, settings)) {
1376 if (validateContent) {
1377 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
1378 if (!isValidPackageName(line)) {
1379 return false;
1380 }
1381 }
1382 }
1383 return true;
1384 }
1385 }
1386
1387 protected static boolean isValidElementList(URL url, Settings settings, boolean validateContent)
1388 throws IOException {
1389 if (url == null) {
1390 throw new IllegalArgumentException("The url is null");
1391 }
1392
1393 try (BufferedReader reader = getReader(url, settings)) {
1394 if (validateContent) {
1395 for (String line = reader.readLine(); line != null; line = reader.readLine()) {
1396 if (line.startsWith("module:")) {
1397 continue;
1398 }
1399
1400 if (!isValidPackageName(line)) {
1401 return false;
1402 }
1403 }
1404 }
1405 return true;
1406 }
1407 }
1408
1409 private static BufferedReader getReader(URL url, Settings settings) throws IOException {
1410 BufferedReader reader = null;
1411
1412 if ("file".equals(url.getProtocol())) {
1413
1414 reader = new BufferedReader(new InputStreamReader(url.openStream()));
1415 } else {
1416
1417 final CloseableHttpClient httpClient = createHttpClient(settings, url);
1418
1419 final HttpGet httpMethod = new HttpGet(url.toString());
1420
1421 HttpResponse response;
1422 HttpClientContext httpContext = HttpClientContext.create();
1423 try {
1424 response = httpClient.execute(httpMethod, httpContext);
1425 } catch (SocketTimeoutException e) {
1426
1427 response = httpClient.execute(httpMethod, httpContext);
1428 }
1429
1430 int status = response.getStatusLine().getStatusCode();
1431 if (status != HttpStatus.SC_OK) {
1432 throw new FileNotFoundException(
1433 "Unexpected HTTP status code " + status + " getting resource " + url.toExternalForm() + ".");
1434 } else {
1435 int pos = url.getPath().lastIndexOf('/');
1436 List<URI> redirects = httpContext.getRedirectLocations();
1437 if (pos >= 0 && isNotEmpty(redirects)) {
1438 URI location = redirects.get(redirects.size() - 1);
1439 String suffix = url.getPath().substring(pos);
1440
1441 if (!location.getPath().endsWith(suffix)) {
1442 throw new FileNotFoundException(url.toExternalForm() + " redirects to "
1443 + location.toURL().toExternalForm() + ".");
1444 }
1445 }
1446 }
1447
1448
1449 reader = new BufferedReader(
1450 new InputStreamReader(response.getEntity().getContent())) {
1451 @Override
1452 public void close() throws IOException {
1453 super.close();
1454
1455 if (httpMethod != null) {
1456 httpMethod.releaseConnection();
1457 }
1458 if (httpClient != null) {
1459 httpClient.close();
1460 }
1461 }
1462 };
1463 }
1464
1465 return reader;
1466 }
1467
1468 private static boolean isValidPackageName(String str) {
1469 if (str == null || str.isEmpty()) {
1470
1471 return true;
1472 }
1473
1474 int idx;
1475 while ((idx = str.indexOf('.')) != -1) {
1476 if (!isValidClassName(str.substring(0, idx))) {
1477 return false;
1478 }
1479
1480 str = str.substring(idx + 1);
1481 }
1482
1483 return isValidClassName(str);
1484 }
1485
1486 private static boolean isValidClassName(String str) {
1487 if ((str == null || str.isEmpty()) || !Character.isJavaIdentifierStart(str.charAt(0))) {
1488 return false;
1489 }
1490
1491 for (int i = str.length() - 1; i > 0; i--) {
1492 if (!Character.isJavaIdentifierPart(str.charAt(i))) {
1493 return false;
1494 }
1495 }
1496
1497 return true;
1498 }
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509 private static CloseableHttpClient createHttpClient(Settings settings, URL url) {
1510 HttpClientBuilder builder = HttpClients.custom();
1511
1512 Registry<ConnectionSocketFactory> csfRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
1513 .register("http", PlainConnectionSocketFactory.getSocketFactory())
1514 .register("https", SSLConnectionSocketFactory.getSystemSocketFactory())
1515 .build();
1516
1517 builder.setConnectionManager(new PoolingHttpClientConnectionManager(csfRegistry));
1518 builder.setDefaultRequestConfig(RequestConfig.custom()
1519 .setSocketTimeout(DEFAULT_TIMEOUT)
1520 .setConnectTimeout(DEFAULT_TIMEOUT)
1521 .setCircularRedirectsAllowed(true)
1522 .setCookieSpec(CookieSpecs.IGNORE_COOKIES)
1523 .build());
1524
1525
1526 builder.setUserAgent("Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)");
1527
1528
1529 builder.setDefaultHeaders(Collections.singletonList(new BasicHeader(HttpHeaders.ACCEPT, "*/*")));
1530
1531 if (settings != null && settings.getActiveProxy() != null) {
1532 Proxy activeProxy = settings.getActiveProxy();
1533 String nonProxyHosts = activeProxy.getNonProxyHosts();
1534 String activeProxyHost = activeProxy.getHost();
1535 if (activeProxyHost != null
1536 && !activeProxyHost.isEmpty()
1537 && (url == null || !isNonProxyHost(nonProxyHosts, url.getHost()))) {
1538 HttpHost proxy = new HttpHost(activeProxyHost, activeProxy.getPort());
1539 builder.setProxy(proxy);
1540
1541 String activeProxyUsername = activeProxy.getUsername();
1542 if (activeProxyUsername != null
1543 && !activeProxyUsername.isEmpty()
1544 && activeProxy.getPassword() != null) {
1545 Credentials credentials =
1546 new UsernamePasswordCredentials(activeProxyUsername, activeProxy.getPassword());
1547
1548 CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
1549 credentialsProvider.setCredentials(AuthScope.ANY, credentials);
1550 builder.setDefaultCredentialsProvider(credentialsProvider);
1551 }
1552 }
1553 }
1554 return builder.build();
1555 }
1556
1557 private static boolean isNonProxyHost(String nonProxyHosts, String targetHost) {
1558 if (nonProxyHosts == null) {
1559 return false;
1560 }
1561 if (targetHost == null) {
1562 targetHost = "";
1563 }
1564 StringTokenizer tokenizer = new StringTokenizer(nonProxyHosts, "|");
1565 while (tokenizer.hasMoreTokens()) {
1566 String pattern = tokenizer.nextToken();
1567 pattern = pattern.replaceAll("\\.", "\\\\.").replaceAll("\\*", ".*");
1568 if (targetHost.matches(pattern)) {
1569 return true;
1570 }
1571 }
1572 return false;
1573 }
1574
1575 static boolean equalsIgnoreCase(String value, String... strings) {
1576 for (String s : strings) {
1577 if (s.equalsIgnoreCase(value)) {
1578 return true;
1579 }
1580 }
1581 return false;
1582 }
1583
1584 static boolean equals(String value, String... strings) {
1585 for (String s : strings) {
1586 if (s.equals(value)) {
1587 return true;
1588 }
1589 }
1590 return false;
1591 }
1592 }