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