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