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