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