View Javadoc

1   package org.apache.maven.plugin.javadoc;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.ByteArrayOutputStream;
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.InputStream;
29  import java.io.OutputStream;
30  import java.io.OutputStreamWriter;
31  import java.io.PrintStream;
32  import java.io.Reader;
33  import java.io.UnsupportedEncodingException;
34  import java.lang.reflect.Modifier;
35  import java.net.URL;
36  import java.net.URLClassLoader;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Iterator;
40  import java.util.List;
41  import java.util.Locale;
42  import java.util.Properties;
43  import java.util.Set;
44  import java.util.StringTokenizer;
45  import java.util.jar.JarEntry;
46  import java.util.jar.JarInputStream;
47  import java.util.regex.Matcher;
48  import java.util.regex.Pattern;
49  import java.util.regex.PatternSyntaxException;
50  
51  import org.apache.commons.httpclient.HttpClient;
52  import org.apache.commons.httpclient.UsernamePasswordCredentials;
53  import org.apache.commons.httpclient.auth.AuthScope;
54  import org.apache.commons.httpclient.methods.GetMethod;
55  import org.apache.commons.lang.SystemUtils;
56  import org.apache.maven.artifact.Artifact;
57  import org.apache.maven.plugin.logging.Log;
58  import org.apache.maven.project.MavenProject;
59  import org.apache.maven.settings.Proxy;
60  import org.apache.maven.settings.Settings;
61  import org.apache.maven.shared.invoker.DefaultInvocationRequest;
62  import org.apache.maven.shared.invoker.DefaultInvoker;
63  import org.apache.maven.shared.invoker.InvocationOutputHandler;
64  import org.apache.maven.shared.invoker.InvocationRequest;
65  import org.apache.maven.shared.invoker.InvocationResult;
66  import org.apache.maven.shared.invoker.Invoker;
67  import org.apache.maven.shared.invoker.MavenInvocationException;
68  import org.apache.maven.shared.invoker.PrintStreamHandler;
69  import org.codehaus.plexus.util.FileUtils;
70  import org.codehaus.plexus.util.IOUtil;
71  import org.codehaus.plexus.util.ReaderFactory;
72  import org.codehaus.plexus.util.StringUtils;
73  import org.codehaus.plexus.util.cli.CommandLineException;
74  import org.codehaus.plexus.util.cli.CommandLineUtils;
75  import org.codehaus.plexus.util.cli.Commandline;
76  
77  /**
78   * Set of utilities methods for Javadoc.
79   *
80   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
81   * @version $Id$
82   * @since 2.4
83   */
84  public class JavadocUtil
85  {
86      /** The default timeout used when fetching url, i.e. 2000. */
87      public static final int DEFAULT_TIMEOUT = 2000;
88  
89      /**
90       * Method that removes the invalid directories in the specified directories.
91       * <b>Note</b>: All elements in <code>dirs</code> could be an absolute or relative against the project's base
92       * directory <code>String</code> path.
93       *
94       * @param project the current Maven project not null
95       * @param dirs the list of <code>String</code> directories path that will be validated.
96       * @return a List of valid <code>String</code> directories absolute paths.
97       */
98      protected static List pruneDirs( MavenProject project, List dirs )
99      {
100         List pruned = new ArrayList( dirs.size() );
101         for ( Iterator i = dirs.iterator(); i.hasNext(); )
102         {
103             String dir = (String) i.next();
104 
105             if ( dir == null )
106             {
107                 continue;
108             }
109 
110             File directory = new File( dir );
111             if ( !directory.isAbsolute() )
112             {
113                 directory = new File( project.getBasedir(), directory.getPath() );
114             }
115 
116             if ( directory.isDirectory() && !pruned.contains( directory.getAbsolutePath() ) )
117             {
118                 pruned.add( directory.getAbsolutePath() );
119             }
120         }
121 
122         return pruned;
123     }
124 
125     /**
126      * Method that removes the invalid files in the specified files.
127      * <b>Note</b>: All elements in <code>files</code> should be an absolute <code>String</code> path.
128      *
129      * @param files the list of <code>String</code> files paths that will be validated.
130      * @return a List of valid <code>File</code> objects.
131      */
132     protected static List pruneFiles( List files )
133     {
134         List pruned = new ArrayList( files.size() );
135         for ( Iterator i = files.iterator(); i.hasNext(); )
136         {
137             String f = (String) i.next();
138 
139             if ( f == null )
140             {
141                 continue;
142             }
143 
144             File file = new File( f );
145             if ( file.isFile() && !pruned.contains( f ) )
146             {
147                 pruned.add( f );
148             }
149         }
150 
151         return pruned;
152     }
153 
154     /**
155      * Method that gets all the source files to be excluded from the javadoc on the given
156      * source paths.
157      *
158      * @param sourcePaths      the path to the source files
159      * @param subpackagesList  list of subpackages to be included in the javadoc
160      * @param excludedPackages the package names to be excluded in the javadoc
161      * @return a List of the source files to be excluded in the generated javadoc
162      */
163     protected static List getExcludedNames( List sourcePaths, String[] subpackagesList, String[] excludedPackages )
164     {
165         List excludedNames = new ArrayList();
166         for ( Iterator i = sourcePaths.iterator(); i.hasNext(); )
167         {
168             String path = (String) i.next();
169             for ( int j = 0; j < subpackagesList.length; j++ )
170             {
171                 List excludes = getExcludedPackages( path, excludedPackages );
172                 excludedNames.addAll( excludes );
173             }
174         }
175 
176         return excludedNames;
177     }
178 
179     /**
180      * Copy from {@link org.apache.maven.project.MavenProject#getCompileArtifacts()}
181      * @param artifacts not null
182      * @return list of compile artifacts with compile scope
183      * @deprecated since 2.5, using {@link #getCompileArtifacts(Set, boolean)} instead of.
184      */
185     protected static List getCompileArtifacts( Set artifacts )
186     {
187         return getCompileArtifacts( artifacts, false );
188     }
189 
190     /**
191      * Copy from {@link org.apache.maven.project.MavenProject#getCompileArtifacts()}
192      * @param artifacts not null
193      * @param withTestScope flag to include or not the artifacts with test scope
194      * @return list of compile artifacts with or without test scope.
195      */
196     protected static List getCompileArtifacts( Set artifacts, boolean withTestScope )
197     {
198         List list = new ArrayList( artifacts.size() );
199 
200         for ( Iterator i = artifacts.iterator(); i.hasNext(); )
201         {
202             Artifact a = (Artifact) i.next();
203 
204             // TODO: classpath check doesn't belong here - that's the other method
205             if ( a.getArtifactHandler().isAddedToClasspath() )
206             {
207                 // TODO: let the scope handler deal with this
208                 if ( withTestScope )
209                 {
210                     if ( Artifact.SCOPE_COMPILE.equals( a.getScope() )
211                         || Artifact.SCOPE_PROVIDED.equals( a.getScope() )
212                         || Artifact.SCOPE_SYSTEM.equals( a.getScope() )
213                         || Artifact.SCOPE_TEST.equals( a.getScope() ) )
214                     {
215                         list.add( a );
216                     }
217                 }
218                 else
219                 {
220                     if ( Artifact.SCOPE_COMPILE.equals( a.getScope() ) || Artifact.SCOPE_PROVIDED.equals( a.getScope() )
221                         || Artifact.SCOPE_SYSTEM.equals( a.getScope() ) )
222                     {
223                         list.add( a );
224                     }
225                 }
226             }
227         }
228 
229         return list;
230     }
231 
232     /**
233      * Convenience method to wrap an argument value in single quotes (i.e. <code>'</code>). Intended for values
234      * which may contain whitespaces.
235      * <br/>
236      * To prevent javadoc error, the line separator (i.e. <code>\n</code>) are skipped.
237      *
238      * @param value the argument value.
239      * @return argument with quote
240      */
241     protected static String quotedArgument( String value )
242     {
243         String arg = value;
244 
245         if ( StringUtils.isNotEmpty( arg ) )
246         {
247             if ( arg.indexOf( "'" ) != -1 )
248             {
249                 arg = StringUtils.replace( arg, "'", "\\'" );
250             }
251             arg = "'" + arg + "'";
252 
253             // To prevent javadoc error
254             arg = StringUtils.replace( arg, "\n", " " );
255         }
256 
257         return arg;
258     }
259 
260     /**
261      * Convenience method to format a path argument so that it is properly interpreted by the javadoc tool. Intended
262      * for path values which may contain whitespaces.
263      *
264      * @param value the argument value.
265      * @return path argument with quote
266      */
267     protected static String quotedPathArgument( String value )
268     {
269         String path = value;
270 
271         if ( StringUtils.isNotEmpty( path ) )
272         {
273             path = path.replace( '\\', '/' );
274             if ( path.indexOf( "\'" ) != -1 )
275             {
276                 String split[] = path.split( "\'" );
277                 path = "";
278 
279                 for ( int i = 0; i < split.length; i++ )
280                 {
281                     if ( i != split.length - 1 )
282                     {
283                         path = path + split[i] + "\\'";
284                     }
285                     else
286                     {
287                         path = path + split[i];
288                     }
289                 }
290             }
291             path = "'" + path + "'";
292         }
293 
294         return path;
295     }
296 
297     /**
298      * Convenience method that copy all <code>doc-files</code> directories from <code>javadocDir</code>
299      * to the <code>outputDirectory</code>.
300      *
301      * @param outputDirectory the output directory
302      * @param javadocDir the javadoc directory
303      * @throws IOException if any
304      * @deprecated since 2.5, using {@link #copyJavadocResources(File, File, String)} instead of.
305      */
306     protected static void copyJavadocResources( File outputDirectory, File javadocDir )
307         throws IOException
308     {
309         copyJavadocResources( outputDirectory, javadocDir, null );
310     }
311 
312     /**
313      * Convenience method that copy all <code>doc-files</code> directories from <code>javadocDir</code>
314      * to the <code>outputDirectory</code>.
315      *
316      * @param outputDirectory the output directory
317      * @param javadocDir the javadoc directory
318      * @param excludedocfilessubdir the excludedocfilessubdir parameter
319      * @throws IOException if any
320      * @since 2.5
321      */
322     protected static void copyJavadocResources( File outputDirectory, File javadocDir, String excludedocfilessubdir )
323         throws IOException
324     {
325         List excludes = new ArrayList();
326         excludes.addAll( Arrays.asList( FileUtils.getDefaultExcludes() ) );
327 
328         if ( StringUtils.isNotEmpty( excludedocfilessubdir ) )
329         {
330             StringTokenizer st = new StringTokenizer( excludedocfilessubdir, ":" );
331             String current;
332             while ( st.hasMoreTokens() )
333             {
334                 current = st.nextToken();
335                 excludes.add( "**/" + current + "/**" );
336             }
337         }
338 
339         if ( javadocDir.exists() && javadocDir.isDirectory() )
340         {
341             List docFiles =
342                 FileUtils.getDirectoryNames( javadocDir, "**/doc-files", StringUtils.join( excludes.iterator(),
343                                                                                            "," ), false, true );
344             for ( Iterator it = docFiles.iterator(); it.hasNext(); )
345             {
346                 String docFile = (String) it.next();
347 
348                 File docFileOutput = new File( outputDirectory, docFile );
349                 FileUtils.mkdir( docFileOutput.getAbsolutePath() );
350                 FileUtils.copyDirectoryStructure( new File( javadocDir, docFile ), docFileOutput );
351                 List files =
352                     FileUtils.getFileAndDirectoryNames( docFileOutput,
353                                                         StringUtils.join( excludes.iterator(), "," ), null, true,
354                                                         true, true, true );
355                 for ( Iterator it2 = files.iterator(); it2.hasNext(); )
356                 {
357                     File file = new File( it2.next().toString() );
358 
359                     if ( file.isDirectory() )
360                     {
361                         FileUtils.deleteDirectory( file );
362                     }
363                     else
364                     {
365                         file.delete();
366                     }
367                 }
368             }
369         }
370     }
371 
372     /**
373      * Method that gets the files or classes that would be included in the javadocs using the subpackages
374      * parameter.
375      *
376      * @param sourceDirectory the directory where the source files are located
377      * @param fileList        the list of all files found in the sourceDirectory
378      * @param excludePackages package names to be excluded in the javadoc
379      * @return a StringBuffer that contains the appended file names of the files to be included in the javadoc
380      */
381     protected static List getIncludedFiles( File sourceDirectory, String[] fileList, String[] excludePackages )
382     {
383         List files = new ArrayList();
384 
385         for ( int j = 0; j < fileList.length; j++ )
386         {
387             boolean include = true;
388             for ( int k = 0; k < excludePackages.length && include; k++ )
389             {
390                 // handle wildcards (*) in the excludePackageNames
391                 String[] excludeName = excludePackages[k].split( "[*]" );
392 
393                 if ( excludeName.length == 0 )
394                 {
395                     continue;
396                 }
397 
398                 if ( excludeName.length > 1 )
399                 {
400                     int u = 0;
401                     while ( include && u < excludeName.length )
402                     {
403                         if ( !"".equals( excludeName[u].trim() ) && fileList[j].indexOf( excludeName[u] ) != -1 )
404                         {
405                             include = false;
406                         }
407                         u++;
408                     }
409                 }
410                 else
411                 {
412                     if ( fileList[j].startsWith( sourceDirectory.toString() + File.separatorChar + excludeName[0] ) )
413                     {
414                         if ( excludeName[0].endsWith( String.valueOf( File.separatorChar ) ) )
415                         {
416                             int i = fileList[j].lastIndexOf( File.separatorChar );
417                             String packageName = fileList[j].substring( 0, i + 1 );
418                             File currentPackage = new File( packageName );
419                             File excludedPackage = new File( sourceDirectory, excludeName[0] );
420                             if ( currentPackage.equals( excludedPackage )
421                                 && fileList[j].substring( i ).indexOf( ".java" ) != -1 )
422                             {
423                                 include = true;
424                             }
425                             else
426                             {
427                                 include = false;
428                             }
429                         }
430                         else
431                         {
432                             include = false;
433                         }
434                     }
435                 }
436             }
437 
438             if ( include )
439             {
440                 files.add( quotedPathArgument( fileList[j] ) );
441             }
442         }
443 
444         return files;
445     }
446 
447     /**
448      * Method that gets the complete package names (including subpackages) of the packages that were defined
449      * in the excludePackageNames parameter.
450      *
451      * @param sourceDirectory     the directory where the source files are located
452      * @param excludePackagenames package names to be excluded in the javadoc
453      * @return a List of the packagenames to be excluded
454      */
455     protected static List getExcludedPackages( String sourceDirectory, String[] excludePackagenames )
456     {
457         List files = new ArrayList();
458         for ( int i = 0; i < excludePackagenames.length; i++ )
459         {
460             String[] fileList = FileUtils.getFilesFromExtension( sourceDirectory, new String[] { "java" } );
461             for ( int j = 0; j < fileList.length; j++ )
462             {
463                 String[] excludeName = excludePackagenames[i].split( "[*]" );
464                 int u = 0;
465                 while ( u < excludeName.length )
466                 {
467                     if ( !"".equals( excludeName[u].trim() ) && fileList[j].indexOf( excludeName[u] ) != -1
468                         && sourceDirectory.indexOf( excludeName[u] ) == -1 )
469                     {
470                         files.add( fileList[j] );
471                     }
472                     u++;
473                 }
474             }
475         }
476 
477         List excluded = new ArrayList();
478         for ( Iterator it = files.iterator(); it.hasNext(); )
479         {
480             String file = (String) it.next();
481             int idx = file.lastIndexOf( File.separatorChar );
482             String tmpStr = file.substring( 0, idx );
483             tmpStr = tmpStr.replace( '\\', '/' );
484             String[] srcSplit = tmpStr.split( sourceDirectory.replace( '\\', '/' ) + '/' );
485             String excludedPackage = srcSplit[1].replace( '/', '.' );
486 
487             if ( !excluded.contains( excludedPackage ) )
488             {
489                 excluded.add( excludedPackage );
490             }
491         }
492 
493         return excluded;
494     }
495 
496     /**
497      * Convenience method that gets the files to be included in the javadoc.
498      *
499      * @param sourceDirectory the directory where the source files are located
500      * @param files the variable that contains the appended filenames of the files to be included in the javadoc
501      * @param excludePackages the packages to be excluded in the javadocs
502      */
503     protected static void addFilesFromSource( List files, File sourceDirectory, String[] excludePackages )
504     {
505         String[] fileList = FileUtils.getFilesFromExtension( sourceDirectory.getPath(), new String[] { "java" } );
506         if ( fileList != null && fileList.length != 0 )
507         {
508             List tmpFiles = getIncludedFiles( sourceDirectory, fileList, excludePackages );
509             files.addAll( tmpFiles );
510         }
511     }
512 
513     /**
514      * Call the Javadoc tool and parse its output to find its version, i.e.:
515      * <pre>
516      * javadoc.exe(or .sh) -J-version
517      * </pre>
518      *
519      * @param javadocExe not null file
520      * @return the javadoc version as float
521      * @throws IOException if javadocExe is null, doesn't exist or is not a file
522      * @throws CommandLineException if any
523      * @throws IllegalArgumentException if no output was found in the command line
524      * @throws PatternSyntaxException if the output contains a syntax error in the regular-expression pattern.
525      * @see #parseJavadocVersion(String)
526      */
527     protected static float getJavadocVersion( File javadocExe )
528         throws IOException, CommandLineException, IllegalArgumentException, PatternSyntaxException
529     {
530         if ( ( javadocExe == null ) || ( !javadocExe.exists() ) || ( !javadocExe.isFile() ) )
531         {
532             throw new IOException( "The javadoc executable '" + javadocExe + "' doesn't exist or is not a file. " );
533         }
534 
535         Commandline cmd = new Commandline();
536         cmd.setExecutable( javadocExe.getAbsolutePath() );
537         cmd.setWorkingDirectory( javadocExe.getParentFile() );
538         cmd.createArg().setValue( "-J-version" );
539 
540         CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
541         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
542 
543         int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
544 
545         if ( exitCode != 0 )
546         {
547             StringBuffer msg = new StringBuffer( "Exit code: " + exitCode + " - " + err.getOutput() );
548             msg.append( '\n' );
549             msg.append( "Command line was:" + CommandLineUtils.toString( cmd.getCommandline() ) );
550             throw new CommandLineException( msg.toString() );
551         }
552 
553         if ( StringUtils.isNotEmpty( err.getOutput() ) )
554         {
555             return parseJavadocVersion( err.getOutput() );
556         }
557         else if ( StringUtils.isNotEmpty( out.getOutput() ) )
558         {
559             return parseJavadocVersion( out.getOutput() );
560         }
561 
562         throw new IllegalArgumentException( "No output found from the command line 'javadoc -J-version'" );
563     }
564 
565     /**
566      * Parse the output for 'javadoc -J-version' and return the javadoc version recognized.
567      * <br/>
568      * Here are some output for 'javadoc -J-version' depending the JDK used:
569      * <table>
570      * <tr>
571      *   <th>JDK</th>
572      *   <th>Output for 'javadoc -J-version'</th>
573      * </tr>
574      * <tr>
575      *   <td>Sun 1.4</td>
576      *   <td>java full version "1.4.2_12-b03"</td>
577      * </tr>
578      * <tr>
579      *   <td>Sun 1.5</td>
580      *   <td>java full version "1.5.0_07-164"</td>
581      * </tr>
582      * <tr>
583      *   <td>IBM 1.4</td>
584      *   <td>javadoc full version "J2RE 1.4.2 IBM Windows 32 build cn1420-20040626"</td>
585      * </tr>
586      * <tr>
587      *   <td>IBM 1.5 (French JVM)</td>
588      *   <td>javadoc version compl�te de "J2RE 1.5.0 IBM Windows 32 build pwi32pdev-20070426a"</td>
589      * </tr>
590      * <tr>
591      *   <td>FreeBSD 1.5</td>
592      *   <td>java full version "diablo-1.5.0-b01"</td>
593      * </tr>
594      * <tr>
595      *   <td>BEA jrockit 1.5</td>
596      *   <td>java full version "1.5.0_11-b03"</td>
597      * </tr>
598      * </table>
599      *
600      * @param output for 'javadoc -J-version'
601      * @return the version of the javadoc for the output.
602      * @throws PatternSyntaxException if the output doesn't match with the output pattern
603      * <tt>(?s).*?([0-9]+\\.[0-9]+)(\\.([0-9]+))?.*</tt>.
604      * @throws IllegalArgumentException if the output is null
605      */
606     protected static float parseJavadocVersion( String output )
607         throws IllegalArgumentException, PatternSyntaxException
608     {
609         if ( StringUtils.isEmpty( output ) )
610         {
611             throw new IllegalArgumentException( "The output could not be null." );
612         }
613 
614         Pattern pattern = Pattern.compile( "(?s).*?([0-9]+\\.[0-9]+)(\\.([0-9]+))?.*" );
615 
616         Matcher matcher = pattern.matcher( output );
617         if ( !matcher.matches() )
618         {
619             throw new PatternSyntaxException( "Unrecognized version of Javadoc: '" + output + "'", pattern.pattern(),
620                                               pattern.toString().length() - 1 );
621         }
622 
623         String version = matcher.group( 3 );
624         if ( version == null )
625         {
626             version = matcher.group( 1 );
627         }
628         else
629         {
630             version = matcher.group( 1 ) + version;
631         }
632 
633         return Float.parseFloat( version );
634     }
635 
636     /**
637      * Parse a memory string which be used in the JVM arguments <code>-Xms</code> or <code>-Xmx</code>.
638      * <br/>
639      * Here are some supported memory string depending the JDK used:
640      * <table>
641      * <tr>
642      *   <th>JDK</th>
643      *   <th>Memory argument support for <code>-Xms</code> or <code>-Xmx</code></th>
644      * </tr>
645      * <tr>
646      *   <td>SUN</td>
647      *   <td>1024k | 128m | 1g | 1t</td>
648      * </tr>
649      * <tr>
650      *   <td>IBM</td>
651      *   <td>1024k | 1024b | 128m | 128mb | 1g | 1gb</td>
652      * </tr>
653      * <tr>
654      *   <td>BEA</td>
655      *   <td>1024k | 1024kb | 128m | 128mb | 1g | 1gb</td>
656      * </tr>
657      * </table>
658      *
659      * @param memory the memory to be parsed, not null.
660      * @return the memory parsed with a supported unit. If no unit specified in the <code>memory</code> parameter,
661      * the default unit is <code>m</code>. The units <code>g | gb</code> or <code>t | tb</code> will be converted
662      * in <code>m</code>.
663      * @throws IllegalArgumentException if the <code>memory</code> parameter is null or doesn't match any pattern.
664      */
665     protected static String parseJavadocMemory( String memory )
666         throws IllegalArgumentException
667     {
668         if ( StringUtils.isEmpty( memory ) )
669         {
670             throw new IllegalArgumentException( "The memory could not be null." );
671         }
672 
673         Pattern p = Pattern.compile( "^\\s*(\\d+)\\s*?\\s*$" );
674         Matcher m = p.matcher( memory );
675         if ( m.matches() )
676         {
677             return m.group( 1 ) + "m";
678         }
679 
680         p = Pattern.compile( "^\\s*(\\d+)\\s*k(b)?\\s*$", Pattern.CASE_INSENSITIVE );
681         m = p.matcher( memory );
682         if ( m.matches() )
683         {
684             return m.group( 1 ) + "k";
685         }
686 
687         p = Pattern.compile( "^\\s*(\\d+)\\s*m(b)?\\s*$", Pattern.CASE_INSENSITIVE );
688         m = p.matcher( memory );
689         if ( m.matches() )
690         {
691             return m.group( 1 ) + "m";
692         }
693 
694         p = Pattern.compile( "^\\s*(\\d+)\\s*g(b)?\\s*$", Pattern.CASE_INSENSITIVE );
695         m = p.matcher( memory );
696         if ( m.matches() )
697         {
698             return ( Integer.parseInt( m.group( 1 ) ) * 1024 ) + "m";
699         }
700 
701         p = Pattern.compile( "^\\s*(\\d+)\\s*t(b)?\\s*$", Pattern.CASE_INSENSITIVE );
702         m = p.matcher( memory );
703         if ( m.matches() )
704         {
705             return ( Integer.parseInt( m.group( 1 ) ) * 1024 * 1024 ) + "m";
706         }
707 
708         throw new IllegalArgumentException( "Could convert not to a memory size: " + memory );
709     }
710 
711     /**
712      * Fetch an URL
713      *
714      * @param settings the user settings used to fetch the url with an active proxy, if defined.
715      * @param url the url to fetch
716      * @throws IOException if any
717      * @see #DEFAULT_TIMEOUT
718      */
719     protected static void fetchURL( Settings settings, URL url )
720         throws IOException
721     {
722         if ( url == null )
723         {
724             throw new IllegalArgumentException( "The url is null" );
725         }
726 
727         HttpClient httpClient = null;
728         if ( !"file".equals( url.getProtocol() ) )
729         {
730             httpClient = new HttpClient();
731             httpClient.getHttpConnectionManager().getParams().setConnectionTimeout( DEFAULT_TIMEOUT );
732             httpClient.getHttpConnectionManager().getParams().setSoTimeout( DEFAULT_TIMEOUT );
733 
734             if ( settings != null )
735             {
736                 Proxy activeProxy = settings.getActiveProxy();
737 
738                 if ( activeProxy != null )
739                 {
740                     String proxyHost = settings.getActiveProxy().getHost();
741                     int proxyPort = settings.getActiveProxy().getPort();
742 
743                     String proxyUser = settings.getActiveProxy().getUsername();
744                     String proxyPass = settings.getActiveProxy().getPassword();
745 
746                     if ( StringUtils.isNotEmpty( proxyHost ) )
747                     {
748                         httpClient.getHostConfiguration().setProxy( proxyHost, proxyPort );
749                     }
750 
751                     if ( StringUtils.isNotEmpty( proxyUser ) )
752                     {
753                         AuthScope authScope =
754                             new AuthScope( AuthScope.ANY_HOST, AuthScope.ANY_PORT, AuthScope.ANY_REALM,
755                                            AuthScope.ANY_SCHEME );
756                         UsernamePasswordCredentials usernamePasswordCredentials =
757                             new UsernamePasswordCredentials( proxyUser, proxyPass );
758                         httpClient.getState().setProxyCredentials( authScope, usernamePasswordCredentials );
759                     }
760                 }
761             }
762         }
763 
764         InputStream in = null;
765         try
766         {
767             if ( httpClient != null )
768             {
769                 GetMethod getMethod = new GetMethod( url.toString() );
770 
771                 try
772                 {
773                     int status = httpClient.executeMethod( getMethod );
774                     if ( status != 200 )
775                     {
776                         throw new FileNotFoundException( url.toString() );
777                     }
778                 }
779                 finally
780                 {
781                     getMethod.releaseConnection();
782                 }
783             }
784             else
785             {
786                 in = url.openStream();
787             }
788         }
789         finally
790         {
791             IOUtil.close( in );
792         }
793     }
794 
795     /**
796      * Validate if a charset is supported on this platform.
797      *
798      * @param charsetName the charsetName to be check.
799      * @return <code>true</code> if the charset is supported by the JVM, <code>false</code> otherwise.
800      */
801     protected static boolean validateEncoding( String charsetName )
802     {
803         if ( StringUtils.isEmpty( charsetName ) )
804         {
805             return false;
806         }
807 
808         OutputStream ost = new ByteArrayOutputStream();
809         OutputStreamWriter osw = null;
810         try
811         {
812             osw = new OutputStreamWriter( ost, charsetName );
813         }
814         catch ( UnsupportedEncodingException exc )
815         {
816             return false;
817         }
818         finally
819         {
820             IOUtil.close( osw );
821         }
822         return true;
823     }
824 
825     /**
826      * For security reasons, if an active proxy is defined and needs an authentication by
827      * username/password, hide the proxy password in the command line.
828      *
829      * @param cmdLine a command line, not null
830      * @param settings the user settings
831      * @return the cmdline with '*' for the http.proxyPassword JVM property
832      */
833     protected static String hideProxyPassword( String cmdLine, Settings settings )
834     {
835         if ( cmdLine == null )
836         {
837             throw new IllegalArgumentException( "cmdLine could not be null" );
838         }
839 
840         if ( settings == null )
841         {
842             return cmdLine;
843         }
844 
845         Proxy activeProxy = settings.getActiveProxy();
846         if ( activeProxy != null && StringUtils.isNotEmpty( activeProxy.getHost() )
847             && StringUtils.isNotEmpty( activeProxy.getUsername() )
848             && StringUtils.isNotEmpty( activeProxy.getPassword() ) )
849         {
850             String pass = "-J-Dhttp.proxyPassword=\"" + activeProxy.getPassword() + "\"";
851             String hidepass =
852                 "-J-Dhttp.proxyPassword=\"" + StringUtils.repeat( "*", activeProxy.getPassword().length() ) + "\"";
853 
854             return StringUtils.replace( cmdLine, pass, hidepass );
855         }
856 
857         return cmdLine;
858     }
859 
860     /**
861      * Auto-detect the class names of the implementation of <code>com.sun.tools.doclets.Taglet</code> class from a
862      * given jar file.
863      * <br/>
864      * <b>Note</b>: <code>JAVA_HOME/lib/tools.jar</code> is a requirement to find
865      * <code>com.sun.tools.doclets.Taglet</code> class.
866      *
867      * @param jarFile not null
868      * @return the list of <code>com.sun.tools.doclets.Taglet</code> class names from a given jarFile.
869      * @throws IOException if jarFile is invalid or not found, or if the <code>JAVA_HOME/lib/tools.jar</code>
870      * is not found.
871      * @throws ClassNotFoundException if any
872      * @throws NoClassDefFoundError if any
873      */
874     protected static List getTagletClassNames( File jarFile )
875         throws IOException, ClassNotFoundException, NoClassDefFoundError
876     {
877         List classes = getClassNamesFromJar( jarFile );
878         ClassLoader cl;
879 
880         // Needed to find com.sun.tools.doclets.Taglet class
881         File tools = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
882         if ( tools.exists() && tools.isFile() )
883         {
884             cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL(), tools.toURI().toURL() }, null );
885         }
886         else
887         {
888             cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL() }, null );
889         }
890 
891         List tagletClasses = new ArrayList();
892 
893         Class tagletClass = cl.loadClass( "com.sun.tools.doclets.Taglet" );
894         for ( Iterator it = classes.iterator(); it.hasNext(); )
895         {
896             String s = (String) it.next();
897 
898             Class c = cl.loadClass( s );
899 
900             if ( tagletClass.isAssignableFrom( c ) && !Modifier.isAbstract( c.getModifiers() ) )
901             {
902                 tagletClasses.add( c.getName() );
903             }
904         }
905 
906         return tagletClasses;
907     }
908 
909     /**
910      * Copy the given url to the given file.
911      *
912      * @param url not null url
913      * @param file not null file where the url will be created
914      * @throws IOException if any
915      * @since 2.6
916      */
917     protected static void copyResource( URL url, File file )
918         throws IOException
919     {
920         if ( file == null )
921         {
922             throw new IOException( "The file " + file + " can't be null." );
923         }
924         if ( url == null )
925         {
926             throw new IOException( "The url " + url + " could not be null." );
927         }
928 
929         InputStream is = url.openStream();
930         if ( is == null )
931         {
932             throw new IOException( "The resource " + url + " doesn't exists." );
933         }
934 
935         if ( !file.getParentFile().exists() )
936         {
937             file.getParentFile().mkdirs();
938         }
939 
940         FileOutputStream os = null;
941         try
942         {
943             os = new FileOutputStream( file );
944 
945             IOUtil.copy( is, os );
946         }
947         finally
948         {
949             IOUtil.close( is );
950 
951             IOUtil.close( os );
952         }
953     }
954 
955     /**
956      * Invoke Maven for the given project file with a list of goals and properties, the output will be in the
957      * invokerlog file.
958      * <br/>
959      * <b>Note</b>: the Maven Home should be defined in the <code>maven.home</code> Java system property or defined in
960      * <code>M2_HOME</code> system env variables.
961      *
962      * @param log a logger could be null.
963      * @param localRepositoryDir the localRepository not null.
964      * @param projectFile a not null project file.
965      * @param goals a not null goals list.
966      * @param properties the properties for the goals, could be null.
967      * @param invokerLog the log file where the invoker will be written, if null using <code>System.out</code>.
968      * @since 2.6
969      */
970     protected static void invokeMaven( Log log, File localRepositoryDir, File projectFile, List goals,
971                                        Properties properties, File invokerLog )
972     {
973         if ( projectFile == null )
974         {
975             throw new IllegalArgumentException( "projectFile should be not null." );
976         }
977         if ( !projectFile.isFile() )
978         {
979             throw new IllegalArgumentException( projectFile.getAbsolutePath() + " is not a file." );
980         }
981         if ( goals == null || goals.size() == 0 )
982         {
983             throw new IllegalArgumentException( "goals should be not empty." );
984         }
985         if ( localRepositoryDir == null || !localRepositoryDir.isDirectory() )
986         {
987             throw new IllegalArgumentException( "localRepositoryDir '" + localRepositoryDir
988                 + "' should be a directory." );
989         }
990 
991         String mavenHome = getMavenHome( log );
992         if ( StringUtils.isEmpty( mavenHome ) )
993         {
994             String msg =
995                 "Could NOT invoke Maven because no Maven Home is defined. You need to have set the M2_HOME "
996                     + "system env variable or a maven.home Java system properties.";
997             if ( log != null )
998             {
999                 log.error( msg );
1000             }
1001             else
1002             {
1003                 System.err.println( msg );
1004             }
1005             return;
1006         }
1007 
1008         Invoker invoker = new DefaultInvoker();
1009         invoker.setMavenHome( new File( mavenHome ) );
1010         invoker.setLocalRepositoryDirectory( localRepositoryDir );
1011 
1012         InvocationRequest request = new DefaultInvocationRequest();
1013         request.setBaseDirectory( projectFile.getParentFile() );
1014         request.setPomFile( projectFile );
1015         if ( log != null )
1016         {
1017             request.setDebug( log.isDebugEnabled() );
1018         }
1019         else
1020         {
1021             request.setDebug( true );
1022         }
1023         request.setGoals( goals );
1024         if ( properties != null )
1025         {
1026             request.setProperties( properties );
1027         }
1028         File javaHome = getJavaHome( log );
1029         if ( javaHome != null )
1030         {
1031             request.setJavaHome( javaHome );
1032         }
1033 
1034         InvocationResult result;
1035         try
1036         {
1037             if ( log != null )
1038             {
1039                 log.debug( "Invoking Maven for the goals: " + goals + " with properties=" + properties );
1040             }
1041             result = invoke( log, invoker, request, invokerLog, goals, properties, null );
1042         }
1043         catch ( MavenInvocationException e )
1044         {
1045             if ( log != null )
1046             {
1047                 if ( log.isDebugEnabled() )
1048                 {
1049                     log.error( "MavenInvocationException: " + e.getMessage(), e );
1050                 }
1051                 else
1052                 {
1053                     log.error( "MavenInvocationException: " + e.getMessage() );
1054                 }
1055                 log.error( "Error when invoking Maven, consult the invoker log." );
1056             }
1057             return;
1058         }
1059 
1060         String invokerLogContent = null;
1061         Reader reader = null;
1062         try
1063         {
1064             reader = ReaderFactory.newReader( invokerLog, "UTF-8" );
1065             invokerLogContent = IOUtil.toString( reader );
1066         }
1067         catch ( IOException e )
1068         {
1069             if ( log != null )
1070             {
1071                 log.error( "IOException: " + e.getMessage() );
1072             }
1073         }
1074         finally
1075         {
1076             IOUtil.close( reader );
1077         }
1078 
1079         if ( invokerLogContent != null
1080             && invokerLogContent.indexOf( "Error occurred during initialization of VM" ) != -1 )
1081         {
1082             if ( log != null )
1083             {
1084                 log.info( "Error occurred during initialization of VM, try to use an empty MAVEN_OPTS." );
1085 
1086                 log.debug( "Reinvoking Maven for the goals: " + goals + " with an empty MAVEN_OPTS" );
1087             }
1088             try
1089             {
1090                 result = invoke( log, invoker, request, invokerLog, goals, properties, "" );
1091             }
1092             catch ( MavenInvocationException e )
1093             {
1094                 if ( log != null )
1095                 {
1096                     if ( log.isDebugEnabled() )
1097                     {
1098                         log.error( "MavenInvocationException: " + e.getMessage(), e );
1099                     }
1100                     else
1101                     {
1102                         log.error( "MavenInvocationException: " + e.getMessage() );
1103                     }
1104                     log.error( "Error when reinvoking Maven, consult the invoker log." );
1105                 }
1106                 return;
1107             }
1108         }
1109 
1110         if ( result.getExitCode() != 0 )
1111         {
1112             if ( log != null )
1113             {
1114                 log.error( "Error when invoking Maven, consult the invoker log file: "
1115                     + invokerLog.getAbsolutePath() );
1116             }
1117         }
1118     }
1119 
1120     // ----------------------------------------------------------------------
1121     // private methods
1122     // ----------------------------------------------------------------------
1123 
1124     /**
1125      * @param jarFile not null
1126      * @return all class names from the given jar file.
1127      * @throws IOException if any or if the jarFile is null or doesn't exist.
1128      */
1129     private static List getClassNamesFromJar( File jarFile )
1130         throws IOException
1131     {
1132         if ( jarFile == null || !jarFile.exists() || !jarFile.isFile() )
1133         {
1134             throw new IOException( "The jar '" + jarFile + "' doesn't exist or is not a file." );
1135         }
1136 
1137         List classes = new ArrayList();
1138         JarInputStream jarStream = null;
1139 
1140         try
1141         {
1142             jarStream = new JarInputStream( new FileInputStream( jarFile ) );
1143             JarEntry jarEntry = jarStream.getNextJarEntry();
1144             while ( jarEntry != null )
1145             {
1146                 if ( jarEntry == null )
1147                 {
1148                     break;
1149                 }
1150 
1151                 if ( jarEntry.getName().toLowerCase( Locale.ENGLISH ).endsWith( ".class" ) )
1152                 {
1153                     String name = jarEntry.getName().substring( 0, jarEntry.getName().indexOf( "." ) );
1154 
1155                     classes.add( name.replaceAll( "/", "\\." ) );
1156                 }
1157 
1158                 jarStream.closeEntry();
1159                 jarEntry = jarStream.getNextJarEntry();
1160             }
1161         }
1162         finally
1163         {
1164             IOUtil.close( jarStream );
1165         }
1166 
1167         return classes;
1168     }
1169 
1170     /**
1171      * @param log could be null
1172      * @param invoker not null
1173      * @param request not null
1174      * @param invokerLog not null
1175      * @param goals not null
1176      * @param properties could be null
1177      * @param mavenOpts could be null
1178      * @return the invocation result
1179      * @throws MavenInvocationException if any
1180      * @since 2.6
1181      */
1182     private static InvocationResult invoke( Log log, Invoker invoker, InvocationRequest request, File invokerLog,
1183                                             List goals, Properties properties, String mavenOpts )
1184         throws MavenInvocationException
1185     {
1186         PrintStream ps;
1187         OutputStream os = null;
1188         if ( invokerLog != null )
1189         {
1190             log.debug( "Using " + invokerLog.getAbsolutePath() + " to log the invoker" );
1191 
1192             try
1193             {
1194                 if ( !invokerLog.exists() )
1195                 {
1196                     invokerLog.getParentFile().mkdirs();
1197                 }
1198                 os = new FileOutputStream( invokerLog );
1199                 ps = new PrintStream( os, true, "UTF-8" );
1200             }
1201             catch ( FileNotFoundException e )
1202             {
1203                 if ( log != null )
1204                 {
1205                     log.error( "FileNotFoundException: " + e.getMessage() + ". Using System.out to log the invoker." );
1206                 }
1207                 ps = System.out;
1208             }
1209             catch ( UnsupportedEncodingException e )
1210             {
1211                 if ( log != null )
1212                 {
1213                     log.error( "UnsupportedEncodingException: " + e.getMessage()
1214                         + ". Using System.out to log the invoker." );
1215                 }
1216                 ps = System.out;
1217             }
1218         }
1219         else
1220         {
1221             log.debug( "Using System.out to log the invoker." );
1222 
1223             ps = System.out;
1224         }
1225 
1226         if ( mavenOpts != null )
1227         {
1228             request.setMavenOpts( mavenOpts );
1229         }
1230 
1231         InvocationOutputHandler outputHandler = new PrintStreamHandler( ps, false );
1232         request.setOutputHandler( outputHandler );
1233 
1234         outputHandler.consumeLine( "Invoking Maven for the goals: " + goals + " with properties=" + properties );
1235         outputHandler.consumeLine( "" );
1236         outputHandler.consumeLine( "M2_HOME=" + getMavenHome( log ) );
1237         outputHandler.consumeLine( "MAVEN_OPTS=" + getMavenOpts( log ) );
1238         outputHandler.consumeLine( "JAVA_HOME=" + getJavaHome( log ) );
1239         outputHandler.consumeLine( "JAVA_OPTS=" + getJavaOpts( log ) );
1240         outputHandler.consumeLine( "" );
1241 
1242         try
1243         {
1244             return invoker.execute( request );
1245         }
1246         finally
1247         {
1248             IOUtil.close( os );
1249             ps = null;
1250         }
1251     }
1252 
1253     /**
1254      * @param log a logger could be null
1255      * @return the Maven home defined in the <code>maven.home</code> system property or defined
1256      * in <code>M2_HOME</code> system env variables or null if never setted.
1257      * @since 2.6
1258      */
1259     private static String getMavenHome( Log log )
1260     {
1261         String mavenHome = System.getProperty( "maven.home" );
1262         if ( mavenHome == null )
1263         {
1264             try
1265             {
1266                 mavenHome = CommandLineUtils.getSystemEnvVars().getProperty( "M2_HOME" );
1267             }
1268             catch ( IOException e )
1269             {
1270                 if ( log != null )
1271                 {
1272                     log.debug( "IOException: " + e.getMessage() );
1273                 }
1274             }
1275         }
1276 
1277         File m2Home = new File( mavenHome );
1278         if ( !m2Home.exists() )
1279         {
1280             if ( log != null )
1281             {
1282                 log
1283                    .error( "Cannot find Maven application directory. Either specify \'maven.home\' system property, or "
1284                        + "M2_HOME environment variable." );
1285             }
1286         }
1287 
1288         return mavenHome;
1289     }
1290 
1291     /**
1292      * @param log a logger could be null
1293      * @return the <code>MAVEN_OPTS</code> env variable value
1294      * @since 2.6
1295      */
1296     private static String getMavenOpts( Log log )
1297     {
1298         String mavenOpts = null;
1299         try
1300         {
1301             mavenOpts = CommandLineUtils.getSystemEnvVars().getProperty( "MAVEN_OPTS" );
1302         }
1303         catch ( IOException e )
1304         {
1305             if ( log != null )
1306             {
1307                 log.debug( "IOException: " + e.getMessage() );
1308             }
1309         }
1310 
1311         return mavenOpts;
1312     }
1313 
1314     /**
1315      * @param log a logger could be null
1316      * @return the <code>JAVA_HOME</code> from System.getProperty( "java.home" )
1317      * By default, <code>System.getProperty( "java.home" ) = JRE_HOME</code> and <code>JRE_HOME</code>
1318      * should be in the <code>JDK_HOME</code>
1319      * @since 2.6
1320      */
1321     private static File getJavaHome( Log log )
1322     {
1323         File javaHome;
1324         if ( SystemUtils.IS_OS_MAC_OSX )
1325         {
1326             javaHome = SystemUtils.getJavaHome();
1327         }
1328         else
1329         {
1330             javaHome = new File( SystemUtils.getJavaHome(), ".." );
1331         }
1332 
1333         if ( javaHome == null || !javaHome.exists() )
1334         {
1335             try
1336             {
1337                 javaHome = new File( CommandLineUtils.getSystemEnvVars().getProperty( "JAVA_HOME" ) );
1338             }
1339             catch ( IOException e )
1340             {
1341                 if ( log != null )
1342                 {
1343                     log.debug( "IOException: " + e.getMessage() );
1344                 }
1345             }
1346         }
1347 
1348         if ( javaHome == null || !javaHome.exists() )
1349         {
1350             if ( log != null )
1351             {
1352                 log.error( "Cannot find Java application directory. Either specify \'java.home\' system property, or "
1353                     + "JAVA_HOME environment variable." );
1354             }
1355         }
1356 
1357         return javaHome;
1358     }
1359 
1360     /**
1361      * @param log a logger could be null
1362      * @return the <code>JAVA_OPTS</code> env variable value
1363      * @since 2.6
1364      */
1365     private static String getJavaOpts( Log log )
1366     {
1367         String javaOpts = null;
1368         try
1369         {
1370             javaOpts = CommandLineUtils.getSystemEnvVars().getProperty( "JAVA_OPTS" );
1371         }
1372         catch ( IOException e )
1373         {
1374             if ( log != null )
1375             {
1376                 log.debug( "IOException: " + e.getMessage() );
1377             }
1378         }
1379 
1380         return javaOpts;
1381     }
1382 }