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.UnsupportedEncodingException;
33  import java.lang.reflect.Modifier;
34  import java.net.SocketTimeoutException;
35  import java.net.URL;
36  import java.net.URLClassLoader;
37  import java.util.ArrayList;
38  import java.util.Arrays;
39  import java.util.Collection;
40  import java.util.Iterator;
41  import java.util.List;
42  import java.util.Locale;
43  import java.util.NoSuchElementException;
44  import java.util.Properties;
45  import java.util.Set;
46  import java.util.StringTokenizer;
47  import java.util.jar.JarEntry;
48  import java.util.jar.JarInputStream;
49  import java.util.regex.Matcher;
50  import java.util.regex.Pattern;
51  import java.util.regex.PatternSyntaxException;
52  
53  import org.apache.commons.httpclient.Credentials;
54  import org.apache.commons.httpclient.HttpClient;
55  import org.apache.commons.httpclient.HttpStatus;
56  import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
57  import org.apache.commons.httpclient.UsernamePasswordCredentials;
58  import org.apache.commons.httpclient.auth.AuthScope;
59  import org.apache.commons.httpclient.methods.GetMethod;
60  import org.apache.commons.httpclient.params.HttpClientParams;
61  import org.apache.commons.httpclient.params.HttpMethodParams;
62  import org.apache.commons.lang.SystemUtils;
63  import org.apache.maven.artifact.Artifact;
64  import org.apache.maven.plugin.logging.Log;
65  import org.apache.maven.project.MavenProject;
66  import org.apache.maven.settings.Proxy;
67  import org.apache.maven.settings.Settings;
68  import org.apache.maven.shared.invoker.DefaultInvocationRequest;
69  import org.apache.maven.shared.invoker.DefaultInvoker;
70  import org.apache.maven.shared.invoker.InvocationOutputHandler;
71  import org.apache.maven.shared.invoker.InvocationRequest;
72  import org.apache.maven.shared.invoker.InvocationResult;
73  import org.apache.maven.shared.invoker.Invoker;
74  import org.apache.maven.shared.invoker.MavenInvocationException;
75  import org.apache.maven.shared.invoker.PrintStreamHandler;
76  import org.apache.maven.wagon.proxy.ProxyInfo;
77  import org.apache.maven.wagon.proxy.ProxyUtils;
78  import org.codehaus.plexus.util.FileUtils;
79  import org.codehaus.plexus.util.IOUtil;
80  import org.codehaus.plexus.util.Os;
81  import org.codehaus.plexus.util.StringUtils;
82  import org.codehaus.plexus.util.cli.CommandLineException;
83  import org.codehaus.plexus.util.cli.CommandLineUtils;
84  import org.codehaus.plexus.util.cli.Commandline;
85  
86  /**
87   * Set of utilities methods for Javadoc.
88   *
89   * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
90   * @version $Id: JavadocUtil.html 829396 2012-08-19 17:35:19Z hboutemy $
91   * @since 2.4
92   */
93  public class JavadocUtil
94  {
95      /** The default timeout used when fetching url, i.e. 2000. */
96      public static final int DEFAULT_TIMEOUT = 2000;
97  
98      /** Error message when VM could not be started using invoker. */
99      protected static final String ERROR_INIT_VM =
100         "Error occurred during initialization of VM, try to reduce the Java heap size for the MAVEN_OPTS " +
101         "environnement variable using -Xms:<size> and -Xmx:<size>.";
102 
103     /**
104      * Method that removes the invalid directories in the specified directories.
105      * <b>Note</b>: All elements in <code>dirs</code> could be an absolute or relative against the project's base
106      * directory <code>String</code> path.
107      *
108      * @param project the current Maven project not null
109      * @param dirs the list of <code>String</code> directories path that will be validated.
110      * @return a List of valid <code>String</code> directories absolute paths.
111      */
112     public static List<String> pruneDirs( MavenProject project, List<String> dirs )
113     {
114         List<String> pruned = new ArrayList<String>( dirs.size() );
115         for ( String dir : dirs )
116         {
117             if ( dir == null )
118             {
119                 continue;
120             }
121 
122             File directory = new File( dir );
123             if ( !directory.isAbsolute() )
124             {
125                 directory = new File( project.getBasedir(), directory.getPath() );
126             }
127 
128             if ( directory.isDirectory() && !pruned.contains( directory.getAbsolutePath() ) )
129             {
130                 pruned.add( directory.getAbsolutePath() );
131             }
132         }
133 
134         return pruned;
135     }
136 
137     /**
138      * Method that removes the invalid files in the specified files.
139      * <b>Note</b>: All elements in <code>files</code> should be an absolute <code>String</code> path.
140      *
141      * @param files the list of <code>String</code> files paths that will be validated.
142      * @return a List of valid <code>File</code> objects.
143      */
144     protected static List<String> pruneFiles( List<String> files )
145     {
146         List<String> pruned = new ArrayList<String>( files.size() );
147         for ( String f : files )
148         {
149             if ( !shouldPruneFile( f, pruned ) )
150             {
151                 pruned.add( f );
152             }
153         }
154  
155         return pruned;
156     }
157 
158     /**
159      * Determine whether a file should be excluded from the provided list of paths, based on whether
160      * it exists and is already present in the list.
161      */
162     public static boolean shouldPruneFile( String f, List<String> pruned )
163     {
164         if ( f != null )
165         {
166             File file = new File( f );
167             if ( file.isFile() && ( isEmpty( pruned ) || !pruned.contains( f ) ) )
168             {
169                 return false;
170             }
171         }
172         
173         return true;
174     }
175 
176     /**
177      * Method that gets all the source files to be excluded from the javadoc on the given
178      * source paths.
179      *
180      * @param sourcePaths      the path to the source files
181      * @param subpackagesList  list of subpackages to be included in the javadoc
182      * @param excludedPackages the package names to be excluded in the javadoc
183      * @return a List of the source files to be excluded in the generated javadoc
184      */
185     protected static List getExcludedNames( List sourcePaths, String[] subpackagesList, String[] excludedPackages )
186     {
187         List excludedNames = new ArrayList();
188         for ( Iterator i = sourcePaths.iterator(); i.hasNext(); )
189         {
190             String path = (String) i.next();
191             for ( int j = 0; j < subpackagesList.length; j++ )
192             {
193                 List excludes = getExcludedPackages( path, excludedPackages );
194                 excludedNames.addAll( excludes );
195             }
196         }
197 
198         return excludedNames;
199     }
200 
201     /**
202      * Copy from {@link org.apache.maven.project.MavenProject#getCompileArtifacts()}
203      * @param artifacts not null
204      * @return list of compile artifacts with compile scope
205      * @deprecated since 2.5, using {@link #getCompileArtifacts(Set, boolean)} instead of.
206      */
207     protected static List getCompileArtifacts( Set artifacts )
208     {
209         return getCompileArtifacts( artifacts, false );
210     }
211 
212     /**
213      * Copy from {@link org.apache.maven.project.MavenProject#getCompileArtifacts()}
214      * @param artifacts not null
215      * @param withTestScope flag to include or not the artifacts with test scope
216      * @return list of compile artifacts with or without test scope.
217      */
218     protected static List getCompileArtifacts( Set artifacts, boolean withTestScope )
219     {
220         List list = new ArrayList( artifacts.size() );
221 
222         for ( Iterator i = artifacts.iterator(); i.hasNext(); )
223         {
224             Artifact a = (Artifact) i.next();
225 
226             // TODO: classpath check doesn't belong here - that's the other method
227             if ( a.getArtifactHandler().isAddedToClasspath() )
228             {
229                 // TODO: let the scope handler deal with this
230                 if ( withTestScope )
231                 {
232                     if ( Artifact.SCOPE_COMPILE.equals( a.getScope() )
233                         || Artifact.SCOPE_PROVIDED.equals( a.getScope() )
234                         || Artifact.SCOPE_SYSTEM.equals( a.getScope() )
235                         || Artifact.SCOPE_TEST.equals( a.getScope() ) )
236                     {
237                         list.add( a );
238                     }
239                 }
240                 else
241                 {
242                     if ( Artifact.SCOPE_COMPILE.equals( a.getScope() ) || Artifact.SCOPE_PROVIDED.equals( a.getScope() )
243                         || Artifact.SCOPE_SYSTEM.equals( a.getScope() ) )
244                     {
245                         list.add( a );
246                     }
247                 }
248             }
249         }
250 
251         return list;
252     }
253 
254     /**
255      * Convenience method to wrap an argument value in single quotes (i.e. <code>'</code>). Intended for values
256      * which may contain whitespaces.
257      * <br/>
258      * To prevent javadoc error, the line separator (i.e. <code>\n</code>) are skipped.
259      *
260      * @param value the argument value.
261      * @return argument with quote
262      */
263     protected static String quotedArgument( String value )
264     {
265         String arg = value;
266 
267         if ( StringUtils.isNotEmpty( arg ) )
268         {
269             if ( arg.indexOf( "'" ) != -1 )
270             {
271                 arg = StringUtils.replace( arg, "'", "\\'" );
272             }
273             arg = "'" + arg + "'";
274 
275             // To prevent javadoc error
276             arg = StringUtils.replace( arg, "\n", " " );
277         }
278 
279         return arg;
280     }
281 
282     /**
283      * Convenience method to format a path argument so that it is properly interpreted by the javadoc tool. Intended
284      * for path values which may contain whitespaces.
285      *
286      * @param value the argument value.
287      * @return path argument with quote
288      */
289     protected static String quotedPathArgument( String value )
290     {
291         String path = value;
292 
293         if ( StringUtils.isNotEmpty( path ) )
294         {
295             path = path.replace( '\\', '/' );
296             if ( path.indexOf( "\'" ) != -1 )
297             {
298                 String split[] = path.split( "\'" );
299                 path = "";
300 
301                 for ( int i = 0; i < split.length; i++ )
302                 {
303                     if ( i != split.length - 1 )
304                     {
305                         path = path + split[i] + "\\'";
306                     }
307                     else
308                     {
309                         path = path + split[i];
310                     }
311                 }
312             }
313             path = "'" + path + "'";
314         }
315 
316         return path;
317     }
318 
319     /**
320      * Convenience method that copy all <code>doc-files</code> directories from <code>javadocDir</code>
321      * to the <code>outputDirectory</code>.
322      *
323      * @param outputDirectory the output directory
324      * @param javadocDir the javadoc directory
325      * @throws IOException if any
326      * @deprecated since 2.5, using {@link #copyJavadocResources(File, File, String)} instead of.
327      */
328     protected static void copyJavadocResources( File outputDirectory, File javadocDir )
329         throws IOException
330     {
331         copyJavadocResources( outputDirectory, javadocDir, null );
332     }
333 
334     /**
335      * Convenience method that copy all <code>doc-files</code> directories from <code>javadocDir</code>
336      * to the <code>outputDirectory</code>.
337      *
338      * @param outputDirectory the output directory
339      * @param javadocDir the javadoc directory
340      * @param excludedocfilessubdir the excludedocfilessubdir parameter
341      * @throws IOException if any
342      * @since 2.5
343      */
344     protected static void copyJavadocResources( File outputDirectory, File javadocDir, String excludedocfilessubdir )
345         throws IOException
346     {
347         if ( !javadocDir.isDirectory() )
348         {
349             return;
350         }
351 
352         List excludes = new ArrayList();
353         excludes.addAll( Arrays.asList( FileUtils.getDefaultExcludes() ) );
354 
355         if ( StringUtils.isNotEmpty( excludedocfilessubdir ) )
356         {
357             StringTokenizer st = new StringTokenizer( excludedocfilessubdir, ":" );
358             String current;
359             while ( st.hasMoreTokens() )
360             {
361                 current = st.nextToken();
362                 excludes.add( "**/" + current + "/**" );
363             }
364         }
365 
366         List docFiles =
367             FileUtils.getDirectoryNames( javadocDir, "resources,**/doc-files",
368                                          StringUtils.join( excludes.iterator(), "," ), false, true );
369         for ( Iterator it = docFiles.iterator(); it.hasNext(); )
370         {
371             String docFile = (String) it.next();
372 
373             File docFileOutput = new File( outputDirectory, docFile );
374             FileUtils.mkdir( docFileOutput.getAbsolutePath() );
375             FileUtils.copyDirectoryStructure( new File( javadocDir, docFile ), docFileOutput );
376             List files =
377                 FileUtils.getFileAndDirectoryNames( docFileOutput, StringUtils.join( excludes.iterator(), "," ),
378                                                     null, true, true, true, true );
379             for ( Iterator it2 = files.iterator(); it2.hasNext(); )
380             {
381                 File file = new File( it2.next().toString() );
382 
383                 if ( file.isDirectory() )
384                 {
385                     FileUtils.deleteDirectory( file );
386                 }
387                 else
388                 {
389                     file.delete();
390                 }
391             }
392         }
393     }
394 
395     /**
396      * Method that gets the files or classes that would be included in the javadocs using the subpackages
397      * parameter.
398      *
399      * @param sourceDirectory the directory where the source files are located
400      * @param fileList        the list of all files found in the sourceDirectory
401      * @param excludePackages package names to be excluded in the javadoc
402      * @return a StringBuffer that contains the appended file names of the files to be included in the javadoc
403      */
404     protected static List getIncludedFiles( File sourceDirectory, String[] fileList, String[] excludePackages )
405     {
406         List files = new ArrayList();
407 
408         for ( int j = 0; j < fileList.length; j++ )
409         {
410             boolean include = true;
411             for ( int k = 0; k < excludePackages.length && include; k++ )
412             {
413                 // handle wildcards (*) in the excludePackageNames
414                 String[] excludeName = excludePackages[k].split( "[*]" );
415 
416                 if ( excludeName.length == 0 )
417                 {
418                     continue;
419                 }
420 
421                 if ( excludeName.length > 1 )
422                 {
423                     int u = 0;
424                     while ( include && u < excludeName.length )
425                     {
426                         if ( !"".equals( excludeName[u].trim() ) && fileList[j].indexOf( excludeName[u] ) != -1 )
427                         {
428                             include = false;
429                         }
430                         u++;
431                     }
432                 }
433                 else
434                 {
435                     if ( fileList[j].startsWith( sourceDirectory.toString() + File.separatorChar + excludeName[0] ) )
436                     {
437                         if ( excludeName[0].endsWith( String.valueOf( File.separatorChar ) ) )
438                         {
439                             int i = fileList[j].lastIndexOf( File.separatorChar );
440                             String packageName = fileList[j].substring( 0, i + 1 );
441                             File currentPackage = new File( packageName );
442                             File excludedPackage = new File( sourceDirectory, excludeName[0] );
443                             if ( currentPackage.equals( excludedPackage )
444                                 && fileList[j].substring( i ).indexOf( ".java" ) != -1 )
445                             {
446                                 include = true;
447                             }
448                             else
449                             {
450                                 include = false;
451                             }
452                         }
453                         else
454                         {
455                             include = false;
456                         }
457                     }
458                 }
459             }
460 
461             if ( include )
462             {
463                 files.add( quotedPathArgument( fileList[j] ) );
464             }
465         }
466 
467         return files;
468     }
469 
470     /**
471      * Method that gets the complete package names (including subpackages) of the packages that were defined
472      * in the excludePackageNames parameter.
473      *
474      * @param sourceDirectory     the directory where the source files are located
475      * @param excludePackagenames package names to be excluded in the javadoc
476      * @return a List of the packagenames to be excluded
477      */
478     protected static List getExcludedPackages( String sourceDirectory, String[] excludePackagenames )
479     {
480         List files = new ArrayList();
481         for ( int i = 0; i < excludePackagenames.length; i++ )
482         {
483             String[] fileList = FileUtils.getFilesFromExtension( sourceDirectory, new String[] { "java" } );
484             for ( int j = 0; j < fileList.length; j++ )
485             {
486                 String[] excludeName = excludePackagenames[i].split( "[*]" );
487                 int u = 0;
488                 while ( u < excludeName.length )
489                 {
490                     if ( !"".equals( excludeName[u].trim() ) && fileList[j].indexOf( excludeName[u] ) != -1
491                         && sourceDirectory.indexOf( excludeName[u] ) == -1 )
492                     {
493                         files.add( fileList[j] );
494                     }
495                     u++;
496                 }
497             }
498         }
499 
500         List excluded = new ArrayList();
501         for ( Iterator it = files.iterator(); it.hasNext(); )
502         {
503             String file = (String) it.next();
504             int idx = file.lastIndexOf( File.separatorChar );
505             String tmpStr = file.substring( 0, idx );
506             tmpStr = tmpStr.replace( '\\', '/' );
507             String[] srcSplit = tmpStr.split( sourceDirectory.replace( '\\', '/' ) + '/' );
508             String excludedPackage = srcSplit[1].replace( '/', '.' );
509 
510             if ( !excluded.contains( excludedPackage ) )
511             {
512                 excluded.add( excludedPackage );
513             }
514         }
515 
516         return excluded;
517     }
518 
519     /**
520      * Convenience method that gets the files to be included in the javadoc.
521      *
522      * @param sourceDirectory the directory where the source files are located
523      * @param files the variable that contains the appended filenames of the files to be included in the javadoc
524      * @param excludePackages the packages to be excluded in the javadocs
525      */
526     protected static void addFilesFromSource( List files, File sourceDirectory, String[] excludePackages )
527     {
528         String[] fileList = FileUtils.getFilesFromExtension( sourceDirectory.getPath(), new String[] { "java" } );
529         if ( fileList != null && fileList.length != 0 )
530         {
531             List tmpFiles = getIncludedFiles( sourceDirectory, fileList, excludePackages );
532             files.addAll( tmpFiles );
533         }
534     }
535 
536     /**
537      * Call the Javadoc tool and parse its output to find its version, i.e.:
538      * <pre>
539      * javadoc.exe(or .sh) -J-version
540      * </pre>
541      *
542      * @param javadocExe not null file
543      * @return the javadoc version as float
544      * @throws IOException if javadocExe is null, doesn't exist or is not a file
545      * @throws CommandLineException if any
546      * @throws IllegalArgumentException if no output was found in the command line
547      * @throws PatternSyntaxException if the output contains a syntax error in the regular-expression pattern.
548      * @see #parseJavadocVersion(String)
549      */
550     protected static float getJavadocVersion( File javadocExe )
551         throws IOException, CommandLineException, IllegalArgumentException, PatternSyntaxException
552     {
553         if ( ( javadocExe == null ) || ( !javadocExe.exists() ) || ( !javadocExe.isFile() ) )
554         {
555             throw new IOException( "The javadoc executable '" + javadocExe + "' doesn't exist or is not a file. " );
556         }
557 
558         Commandline cmd = new Commandline();
559         cmd.setExecutable( javadocExe.getAbsolutePath() );
560         cmd.setWorkingDirectory( javadocExe.getParentFile() );
561         cmd.createArg().setValue( "-J-version" );
562 
563         CommandLineUtils.StringStreamConsumer out = new CommandLineUtils.StringStreamConsumer();
564         CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer();
565 
566         int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
567 
568         if ( exitCode != 0 )
569         {
570             StringBuffer msg = new StringBuffer( "Exit code: " + exitCode + " - " + err.getOutput() );
571             msg.append( '\n' );
572             msg.append( "Command line was:" + CommandLineUtils.toString( cmd.getCommandline() ) );
573             throw new CommandLineException( msg.toString() );
574         }
575 
576         if ( StringUtils.isNotEmpty( err.getOutput() ) )
577         {
578             return parseJavadocVersion( err.getOutput() );
579         }
580         else if ( StringUtils.isNotEmpty( out.getOutput() ) )
581         {
582             return parseJavadocVersion( out.getOutput() );
583         }
584 
585         throw new IllegalArgumentException( "No output found from the command line 'javadoc -J-version'" );
586     }
587 
588     /**
589      * Parse the output for 'javadoc -J-version' and return the javadoc version recognized.
590      * <br/>
591      * Here are some output for 'javadoc -J-version' depending the JDK used:
592      * <table>
593      * <tr>
594      *   <th>JDK</th>
595      *   <th>Output for 'javadoc -J-version'</th>
596      * </tr>
597      * <tr>
598      *   <td>Sun 1.4</td>
599      *   <td>java full version "1.4.2_12-b03"</td>
600      * </tr>
601      * <tr>
602      *   <td>Sun 1.5</td>
603      *   <td>java full version "1.5.0_07-164"</td>
604      * </tr>
605      * <tr>
606      *   <td>IBM 1.4</td>
607      *   <td>javadoc full version "J2RE 1.4.2 IBM Windows 32 build cn1420-20040626"</td>
608      * </tr>
609      * <tr>
610      *   <td>IBM 1.5 (French JVM)</td>
611      *   <td>javadoc version compl�te de "J2RE 1.5.0 IBM Windows 32 build pwi32pdev-20070426a"</td>
612      * </tr>
613      * <tr>
614      *   <td>FreeBSD 1.5</td>
615      *   <td>java full version "diablo-1.5.0-b01"</td>
616      * </tr>
617      * <tr>
618      *   <td>BEA jrockit 1.5</td>
619      *   <td>java full version "1.5.0_11-b03"</td>
620      * </tr>
621      * </table>
622      *
623      * @param output for 'javadoc -J-version'
624      * @return the version of the javadoc for the output.
625      * @throws PatternSyntaxException if the output doesn't match with the output pattern
626      * <tt>(?s).*?([0-9]+\\.[0-9]+)(\\.([0-9]+))?.*</tt>.
627      * @throws IllegalArgumentException if the output is null
628      */
629     protected static float parseJavadocVersion( String output )
630         throws IllegalArgumentException, PatternSyntaxException
631     {
632         if ( StringUtils.isEmpty( output ) )
633         {
634             throw new IllegalArgumentException( "The output could not be null." );
635         }
636 
637         Pattern pattern = Pattern.compile( "(?s).*?([0-9]+\\.[0-9]+)(\\.([0-9]+))?.*" );
638 
639         Matcher matcher = pattern.matcher( output );
640         if ( !matcher.matches() )
641         {
642             throw new PatternSyntaxException( "Unrecognized version of Javadoc: '" + output + "'", pattern.pattern(),
643                                               pattern.toString().length() - 1 );
644         }
645 
646         String version = matcher.group( 3 );
647         if ( version == null )
648         {
649             version = matcher.group( 1 );
650         }
651         else
652         {
653             version = matcher.group( 1 ) + version;
654         }
655 
656         return Float.parseFloat( version );
657     }
658 
659     /**
660      * Parse a memory string which be used in the JVM arguments <code>-Xms</code> or <code>-Xmx</code>.
661      * <br/>
662      * Here are some supported memory string depending the JDK used:
663      * <table>
664      * <tr>
665      *   <th>JDK</th>
666      *   <th>Memory argument support for <code>-Xms</code> or <code>-Xmx</code></th>
667      * </tr>
668      * <tr>
669      *   <td>SUN</td>
670      *   <td>1024k | 128m | 1g | 1t</td>
671      * </tr>
672      * <tr>
673      *   <td>IBM</td>
674      *   <td>1024k | 1024b | 128m | 128mb | 1g | 1gb</td>
675      * </tr>
676      * <tr>
677      *   <td>BEA</td>
678      *   <td>1024k | 1024kb | 128m | 128mb | 1g | 1gb</td>
679      * </tr>
680      * </table>
681      *
682      * @param memory the memory to be parsed, not null.
683      * @return the memory parsed with a supported unit. If no unit specified in the <code>memory</code> parameter,
684      * the default unit is <code>m</code>. The units <code>g | gb</code> or <code>t | tb</code> will be converted
685      * in <code>m</code>.
686      * @throws IllegalArgumentException if the <code>memory</code> parameter is null or doesn't match any pattern.
687      */
688     protected static String parseJavadocMemory( String memory )
689         throws IllegalArgumentException
690     {
691         if ( StringUtils.isEmpty( memory ) )
692         {
693             throw new IllegalArgumentException( "The memory could not be null." );
694         }
695 
696         Pattern p = Pattern.compile( "^\\s*(\\d+)\\s*?\\s*$" );
697         Matcher m = p.matcher( memory );
698         if ( m.matches() )
699         {
700             return m.group( 1 ) + "m";
701         }
702 
703         p = Pattern.compile( "^\\s*(\\d+)\\s*k(b)?\\s*$", Pattern.CASE_INSENSITIVE );
704         m = p.matcher( memory );
705         if ( m.matches() )
706         {
707             return m.group( 1 ) + "k";
708         }
709 
710         p = Pattern.compile( "^\\s*(\\d+)\\s*m(b)?\\s*$", Pattern.CASE_INSENSITIVE );
711         m = p.matcher( memory );
712         if ( m.matches() )
713         {
714             return m.group( 1 ) + "m";
715         }
716 
717         p = Pattern.compile( "^\\s*(\\d+)\\s*g(b)?\\s*$", Pattern.CASE_INSENSITIVE );
718         m = p.matcher( memory );
719         if ( m.matches() )
720         {
721             return ( Integer.parseInt( m.group( 1 ) ) * 1024 ) + "m";
722         }
723 
724         p = Pattern.compile( "^\\s*(\\d+)\\s*t(b)?\\s*$", Pattern.CASE_INSENSITIVE );
725         m = p.matcher( memory );
726         if ( m.matches() )
727         {
728             return ( Integer.parseInt( m.group( 1 ) ) * 1024 * 1024 ) + "m";
729         }
730 
731         throw new IllegalArgumentException( "Could convert not to a memory size: " + memory );
732     }
733 
734     /**
735      * Fetch an URL
736      *
737      * @param settings the user settings used to fetch the url with an active proxy, if defined.
738      * @param url the url to fetch
739      * @throws IOException if any
740      * @see #DEFAULT_TIMEOUT
741      */
742     protected static void fetchURL( Settings settings, URL url )
743         throws IOException
744     {
745         if ( url == null )
746         {
747             throw new IllegalArgumentException( "The url is null" );
748         }
749 
750         if ( "file".equals( url.getProtocol() ) )
751         {
752             InputStream in = null;
753             try
754             {
755                 in = url.openStream();
756             }
757             finally
758             {
759                 IOUtil.close( in );
760             }
761 
762             return;
763         }
764 
765         // http, https...
766         HttpClient httpClient = new HttpClient( new MultiThreadedHttpConnectionManager() );
767         httpClient.getHttpConnectionManager().getParams().setConnectionTimeout( DEFAULT_TIMEOUT );
768         httpClient.getHttpConnectionManager().getParams().setSoTimeout( DEFAULT_TIMEOUT );
769         httpClient.getParams().setBooleanParameter( HttpClientParams.ALLOW_CIRCULAR_REDIRECTS, true );
770 
771         // Some web servers don't allow the default user-agent sent by httpClient
772         httpClient.getParams().setParameter( HttpMethodParams.USER_AGENT,
773                                              "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" );
774 
775         if ( settings != null && settings.getActiveProxy() != null )
776         {
777             Proxy activeProxy = settings.getActiveProxy();
778 
779             ProxyInfo proxyInfo = new ProxyInfo();
780             proxyInfo.setNonProxyHosts( activeProxy.getNonProxyHosts() );
781 
782             if ( StringUtils.isNotEmpty( activeProxy.getHost() )
783                 && !ProxyUtils.validateNonProxyHosts( proxyInfo, url.getHost() ) )
784             {
785                 httpClient.getHostConfiguration().setProxy( activeProxy.getHost(), activeProxy.getPort() );
786 
787                 if ( StringUtils.isNotEmpty( activeProxy.getUsername() ) && activeProxy.getPassword() != null )
788                 {
789                     Credentials credentials =
790                         new UsernamePasswordCredentials( activeProxy.getUsername(), activeProxy.getPassword() );
791 
792                     httpClient.getState().setProxyCredentials( AuthScope.ANY, credentials );
793                 }
794             }
795         }
796 
797         GetMethod getMethod = new GetMethod( url.toString() );
798         try
799         {
800             int status;
801             try
802             {
803                 status = httpClient.executeMethod( getMethod );
804             }
805             catch ( SocketTimeoutException e )
806             {
807                 // could be a sporadic failure, one more retry before we give up
808                 status = httpClient.executeMethod( getMethod );
809             }
810 
811             if ( status != HttpStatus.SC_OK )
812             {
813                 throw new FileNotFoundException( url.toString() );
814             }
815         }
816         finally
817         {
818             getMethod.releaseConnection();
819         }
820     }
821 
822     /**
823      * Validate if a charset is supported on this platform.
824      *
825      * @param charsetName the charsetName to be check.
826      * @return <code>true</code> if the given charset is supported by the JVM, <code>false</code> otherwise.
827      */
828     protected static boolean validateEncoding( String charsetName )
829     {
830         if ( StringUtils.isEmpty( charsetName ) )
831         {
832             return false;
833         }
834 
835         OutputStream ost = new ByteArrayOutputStream();
836         OutputStreamWriter osw = null;
837         try
838         {
839             osw = new OutputStreamWriter( ost, charsetName );
840         }
841         catch ( UnsupportedEncodingException exc )
842         {
843             return false;
844         }
845         finally
846         {
847             IOUtil.close( osw );
848         }
849 
850         return true;
851     }
852 
853     /**
854      * For security reasons, if an active proxy is defined and needs an authentication by
855      * username/password, hide the proxy password in the command line.
856      *
857      * @param cmdLine a command line, not null
858      * @param settings the user settings
859      * @return the cmdline with '*' for the http.proxyPassword JVM property
860      */
861     protected static String hideProxyPassword( String cmdLine, Settings settings )
862     {
863         if ( cmdLine == null )
864         {
865             throw new IllegalArgumentException( "cmdLine could not be null" );
866         }
867 
868         if ( settings == null )
869         {
870             return cmdLine;
871         }
872 
873         Proxy activeProxy = settings.getActiveProxy();
874         if ( activeProxy != null && StringUtils.isNotEmpty( activeProxy.getHost() )
875             && StringUtils.isNotEmpty( activeProxy.getUsername() )
876             && StringUtils.isNotEmpty( activeProxy.getPassword() ) )
877         {
878             String pass = "-J-Dhttp.proxyPassword=\"" + activeProxy.getPassword() + "\"";
879             String hidepass =
880                 "-J-Dhttp.proxyPassword=\"" + StringUtils.repeat( "*", activeProxy.getPassword().length() ) + "\"";
881 
882             return StringUtils.replace( cmdLine, pass, hidepass );
883         }
884 
885         return cmdLine;
886     }
887 
888     /**
889      * Auto-detect the class names of the implementation of <code>com.sun.tools.doclets.Taglet</code> class from a
890      * given jar file.
891      * <br/>
892      * <b>Note</b>: <code>JAVA_HOME/lib/tools.jar</code> is a requirement to find
893      * <code>com.sun.tools.doclets.Taglet</code> class.
894      *
895      * @param jarFile not null
896      * @return the list of <code>com.sun.tools.doclets.Taglet</code> class names from a given jarFile.
897      * @throws IOException if jarFile is invalid or not found, or if the <code>JAVA_HOME/lib/tools.jar</code>
898      * is not found.
899      * @throws ClassNotFoundException if any
900      * @throws NoClassDefFoundError if any
901      */
902     protected static List getTagletClassNames( File jarFile )
903         throws IOException, ClassNotFoundException, NoClassDefFoundError
904     {
905         List classes = getClassNamesFromJar( jarFile );
906         ClassLoader cl;
907 
908         // Needed to find com.sun.tools.doclets.Taglet class
909         File tools = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
910         if ( tools.exists() && tools.isFile() )
911         {
912             cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL(), tools.toURI().toURL() }, null );
913         }
914         else
915         {
916             cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL() }, null );
917         }
918 
919         List tagletClasses = new ArrayList();
920 
921         Class tagletClass = cl.loadClass( "com.sun.tools.doclets.Taglet" );
922         for ( Iterator it = classes.iterator(); it.hasNext(); )
923         {
924             String s = (String) it.next();
925 
926             Class c = cl.loadClass( s );
927 
928             if ( tagletClass.isAssignableFrom( c ) && !Modifier.isAbstract( c.getModifiers() ) )
929             {
930                 tagletClasses.add( c.getName() );
931             }
932         }
933 
934         return tagletClasses;
935     }
936 
937     /**
938      * Copy the given url to the given file.
939      *
940      * @param url not null url
941      * @param file not null file where the url will be created
942      * @throws IOException if any
943      * @since 2.6
944      */
945     protected static void copyResource( URL url, File file )
946         throws IOException
947     {
948         if ( file == null )
949         {
950             throw new IOException( "The file " + file + " can't be null." );
951         }
952         if ( url == null )
953         {
954             throw new IOException( "The url " + url + " could not be null." );
955         }
956 
957         InputStream is = url.openStream();
958         if ( is == null )
959         {
960             throw new IOException( "The resource " + url + " doesn't exists." );
961         }
962 
963         if ( !file.getParentFile().exists() )
964         {
965             file.getParentFile().mkdirs();
966         }
967 
968         FileOutputStream os = null;
969         try
970         {
971             os = new FileOutputStream( file );
972 
973             IOUtil.copy( is, os );
974         }
975         finally
976         {
977             IOUtil.close( is );
978 
979             IOUtil.close( os );
980         }
981     }
982 
983     /**
984      * Invoke Maven for the given project file with a list of goals and properties, the output will be in the
985      * invokerlog file.
986      * <br/>
987      * <b>Note</b>: the Maven Home should be defined in the <code>maven.home</code> Java system property or defined in
988      * <code>M2_HOME</code> system env variables.
989      *
990      * @param log a logger could be null.
991      * @param localRepositoryDir the localRepository not null.
992      * @param projectFile a not null project file.
993      * @param goals a not null goals list.
994      * @param properties the properties for the goals, could be null.
995      * @param invokerLog the log file where the invoker will be written, if null using <code>System.out</code>.
996      * @throws MavenInvocationException if any
997      * @since 2.6
998      */
999     protected static void invokeMaven( Log log, File localRepositoryDir, File projectFile, List goals,
1000                                        Properties properties, File invokerLog )
1001         throws MavenInvocationException
1002     {
1003         if ( projectFile == null )
1004         {
1005             throw new IllegalArgumentException( "projectFile should be not null." );
1006         }
1007         if ( !projectFile.isFile() )
1008         {
1009             throw new IllegalArgumentException( projectFile.getAbsolutePath() + " is not a file." );
1010         }
1011         if ( goals == null || goals.size() == 0 )
1012         {
1013             throw new IllegalArgumentException( "goals should be not empty." );
1014         }
1015         if ( localRepositoryDir == null || !localRepositoryDir.isDirectory() )
1016         {
1017             throw new IllegalArgumentException( "localRepositoryDir '" + localRepositoryDir
1018                 + "' should be a directory." );
1019         }
1020 
1021         String mavenHome = getMavenHome( log );
1022         if ( StringUtils.isEmpty( mavenHome ) )
1023         {
1024             String msg =
1025                 "Could NOT invoke Maven because no Maven Home is defined. You need to have set the M2_HOME "
1026                     + "system env variable or a maven.home Java system properties.";
1027             if ( log != null )
1028             {
1029                 log.error( msg );
1030             }
1031             else
1032             {
1033                 System.err.println( msg );
1034             }
1035             return;
1036         }
1037 
1038         Invoker invoker = new DefaultInvoker();
1039         invoker.setMavenHome( new File( mavenHome ) );
1040         invoker.setLocalRepositoryDirectory( localRepositoryDir );
1041 
1042         InvocationRequest request = new DefaultInvocationRequest();
1043         request.setBaseDirectory( projectFile.getParentFile() );
1044         request.setPomFile( projectFile );
1045         if ( log != null )
1046         {
1047             request.setDebug( log.isDebugEnabled() );
1048         }
1049         else
1050         {
1051             request.setDebug( true );
1052         }
1053         request.setGoals( goals );
1054         if ( properties != null )
1055         {
1056             request.setProperties( properties );
1057         }
1058         File javaHome = getJavaHome( log );
1059         if ( javaHome != null )
1060         {
1061             request.setJavaHome( javaHome );
1062         }
1063 
1064         if ( log != null && log.isDebugEnabled() )
1065         {
1066             log.debug( "Invoking Maven for the goals: " + goals + " with "
1067                 + ( properties == null ? "no properties" : "properties=" + properties ) );
1068         }
1069         InvocationResult result = invoke( log, invoker, request, invokerLog, goals, properties, null );
1070 
1071         if ( result.getExitCode() != 0 )
1072         {
1073             String invokerLogContent = readFile( invokerLog, "UTF-8" );
1074 
1075             // see DefaultMaven
1076             if ( invokerLogContent != null && ( invokerLogContent.indexOf( "Scanning for projects..." ) == -1
1077                 || invokerLogContent.indexOf( OutOfMemoryError.class.getName() ) != -1 ) )
1078             {
1079                 if ( log != null )
1080                 {
1081                     log.error( "Error occurred during initialization of VM, trying to use an empty MAVEN_OPTS..." );
1082 
1083                     if ( log.isDebugEnabled() )
1084                     {
1085                         log.debug( "Reinvoking Maven for the goals: " + goals + " with an empty MAVEN_OPTS..." );
1086                     }
1087                 }
1088                 result = invoke( log, invoker, request, invokerLog, goals, properties, "" );
1089             }
1090         }
1091 
1092         if ( result.getExitCode() != 0 )
1093         {
1094             String invokerLogContent = readFile( invokerLog, "UTF-8" );
1095 
1096             // see DefaultMaven
1097             if ( invokerLogContent != null && ( invokerLogContent.indexOf( "Scanning for projects..." ) == -1
1098                 || invokerLogContent.indexOf( OutOfMemoryError.class.getName() ) != -1 ) )
1099             {
1100                 throw new MavenInvocationException( ERROR_INIT_VM );
1101             }
1102 
1103             throw new MavenInvocationException( "Error when invoking Maven, consult the invoker log file: "
1104                 + invokerLog.getAbsolutePath() );
1105         }
1106     }
1107 
1108     /**
1109      * Read the given file and return the content or null if an IOException occurs.
1110      *
1111      * @param javaFile not null
1112      * @param encoding could be null
1113      * @return the content with unified line separator of the given javaFile using the given encoding.
1114      * @see FileUtils#fileRead(File, String)
1115      * @since 2.6.1
1116      */
1117     protected static String readFile( final File javaFile, final String encoding )
1118     {
1119         try
1120         {
1121             return FileUtils.fileRead( javaFile, encoding );
1122         }
1123         catch (IOException e )
1124         {
1125             return null;
1126         }
1127     }
1128 
1129     /**
1130      * Split the given path with colon and semi-colon, to support Solaris and Windows path.
1131      * Examples:
1132      * <pre>
1133      * splitPath( "/home:/tmp" )     = ["/home", "/tmp"]
1134      * splitPath( "/home;/tmp" )     = ["/home", "/tmp"]
1135      * splitPath( "C:/home:C:/tmp" ) = ["C:/home", "C:/tmp"]
1136      * splitPath( "C:/home;C:/tmp" ) = ["C:/home", "C:/tmp"]
1137      * </pre>
1138      *
1139      * @param path which can contain multiple paths separated with a colon (<code>:</code>) or a
1140      * semi-colon (<code>;</code>), plateform independent. Could be null.
1141      * @return the path splitted by colon or semi-colon or <code>null</code> if path was <code>null</code>.
1142      * @since 2.6.1
1143      */
1144     protected static String[] splitPath( final String path )
1145     {
1146         if ( path == null )
1147         {
1148             return null;
1149         }
1150 
1151         List subpaths = new ArrayList();
1152         PathTokenizer pathTokenizer = new PathTokenizer( path );
1153         while ( pathTokenizer.hasMoreTokens() )
1154         {
1155             subpaths.add( pathTokenizer.nextToken() );
1156         }
1157 
1158         return (String[]) subpaths.toArray( new String[0] );
1159     }
1160 
1161     /**
1162      * Unify the given path with the current System path separator, to be plateform independent.
1163      * Examples:
1164      * <pre>
1165      * unifyPathSeparator( "/home:/tmp" ) = "/home:/tmp" (Solaris box)
1166      * unifyPathSeparator( "/home:/tmp" ) = "/home;/tmp" (Windows box)
1167      * </pre>
1168      *
1169      * @param path which can contain multiple paths by separating them with a colon (<code>:</code>) or a
1170      * semi-colon (<code>;</code>), plateform independent. Could be null.
1171      * @return the same path but separated with the current System path separator or <code>null</code> if path was
1172      * <code>null</code>.
1173      * @since 2.6.1
1174      * @see #splitPath(String)
1175      * @see File#pathSeparator
1176      */
1177     protected static String unifyPathSeparator( final String path )
1178     {
1179         if ( path == null )
1180         {
1181             return null;
1182         }
1183 
1184         return StringUtils.join( splitPath( path ), File.pathSeparator );
1185     }
1186 
1187     // ----------------------------------------------------------------------
1188     // private methods
1189     // ----------------------------------------------------------------------
1190 
1191     /**
1192      * @param jarFile not null
1193      * @return all class names from the given jar file.
1194      * @throws IOException if any or if the jarFile is null or doesn't exist.
1195      */
1196     private static List getClassNamesFromJar( File jarFile )
1197         throws IOException
1198     {
1199         if ( jarFile == null || !jarFile.exists() || !jarFile.isFile() )
1200         {
1201             throw new IOException( "The jar '" + jarFile + "' doesn't exist or is not a file." );
1202         }
1203 
1204         List classes = new ArrayList();
1205         JarInputStream jarStream = null;
1206 
1207         try
1208         {
1209             jarStream = new JarInputStream( new FileInputStream( jarFile ) );
1210             JarEntry jarEntry = jarStream.getNextJarEntry();
1211             while ( jarEntry != null )
1212             {
1213                 if ( jarEntry == null )
1214                 {
1215                     break;
1216                 }
1217 
1218                 if ( jarEntry.getName().toLowerCase( Locale.ENGLISH ).endsWith( ".class" ) )
1219                 {
1220                     String name = jarEntry.getName().substring( 0, jarEntry.getName().indexOf( "." ) );
1221 
1222                     classes.add( name.replaceAll( "/", "\\." ) );
1223                 }
1224 
1225                 jarStream.closeEntry();
1226                 jarEntry = jarStream.getNextJarEntry();
1227             }
1228         }
1229         finally
1230         {
1231             IOUtil.close( jarStream );
1232         }
1233 
1234         return classes;
1235     }
1236 
1237     /**
1238      * @param log could be null
1239      * @param invoker not null
1240      * @param request not null
1241      * @param invokerLog not null
1242      * @param goals not null
1243      * @param properties could be null
1244      * @param mavenOpts could be null
1245      * @return the invocation result
1246      * @throws MavenInvocationException if any
1247      * @since 2.6
1248      */
1249     private static InvocationResult invoke( Log log, Invoker invoker, InvocationRequest request, File invokerLog,
1250                                             List goals, Properties properties, String mavenOpts )
1251         throws MavenInvocationException
1252     {
1253         PrintStream ps;
1254         OutputStream os = null;
1255         if ( invokerLog != null )
1256         {
1257             if ( log != null && log.isDebugEnabled() )
1258             {
1259                 log.debug( "Using " + invokerLog.getAbsolutePath() + " to log the invoker" );
1260             }
1261 
1262             try
1263             {
1264                 if ( !invokerLog.exists() )
1265                 {
1266                     invokerLog.getParentFile().mkdirs();
1267                 }
1268                 os = new FileOutputStream( invokerLog );
1269                 ps = new PrintStream( os, true, "UTF-8" );
1270             }
1271             catch ( FileNotFoundException e )
1272             {
1273                 if ( log != null && log.isErrorEnabled() )
1274                 {
1275                     log.error( "FileNotFoundException: " + e.getMessage() + ". Using System.out to log the invoker." );
1276                 }
1277                 ps = System.out;
1278             }
1279             catch ( UnsupportedEncodingException e )
1280             {
1281                 if ( log != null && log.isErrorEnabled() )
1282                 {
1283                     log.error( "UnsupportedEncodingException: " + e.getMessage()
1284                         + ". Using System.out to log the invoker." );
1285                 }
1286                 ps = System.out;
1287             }
1288         }
1289         else
1290         {
1291             if ( log != null && log.isDebugEnabled() )
1292             {
1293                 log.debug( "Using System.out to log the invoker." );
1294             }
1295 
1296             ps = System.out;
1297         }
1298 
1299         if ( mavenOpts != null )
1300         {
1301             request.setMavenOpts( mavenOpts );
1302         }
1303 
1304         InvocationOutputHandler outputHandler = new PrintStreamHandler( ps, false );
1305         request.setOutputHandler( outputHandler );
1306 
1307         outputHandler.consumeLine( "Invoking Maven for the goals: " + goals + " with "
1308             + ( properties == null ? "no properties" : "properties=" + properties ) );
1309         outputHandler.consumeLine( "" );
1310         outputHandler.consumeLine( "M2_HOME=" + getMavenHome( log ) );
1311         outputHandler.consumeLine( "MAVEN_OPTS=" + getMavenOpts( log ) );
1312         outputHandler.consumeLine( "JAVA_HOME=" + getJavaHome( log ) );
1313         outputHandler.consumeLine( "JAVA_OPTS=" + getJavaOpts( log ) );
1314         outputHandler.consumeLine( "" );
1315 
1316         try
1317         {
1318             return invoker.execute( request );
1319         }
1320         finally
1321         {
1322             IOUtil.close( os );
1323             ps = null;
1324         }
1325     }
1326 
1327     /**
1328      * @param log a logger could be null
1329      * @return the Maven home defined in the <code>maven.home</code> system property or defined
1330      * in <code>M2_HOME</code> system env variables or null if never setted.
1331      * @since 2.6
1332      */
1333     private static String getMavenHome( Log log )
1334     {
1335         String mavenHome = System.getProperty( "maven.home" );
1336         if ( mavenHome == null )
1337         {
1338             try
1339             {
1340                 mavenHome = CommandLineUtils.getSystemEnvVars().getProperty( "M2_HOME" );
1341             }
1342             catch ( IOException e )
1343             {
1344                 if ( log != null && log.isDebugEnabled() )
1345                 {
1346                     log.debug( "IOException: " + e.getMessage() );
1347                 }
1348             }
1349         }
1350 
1351         File m2Home = new File( mavenHome );
1352         if ( !m2Home.exists() )
1353         {
1354             if ( log != null && log.isErrorEnabled() )
1355             {
1356                 log
1357                    .error( "Cannot find Maven application directory. Either specify \'maven.home\' system property, or "
1358                        + "M2_HOME environment variable." );
1359             }
1360         }
1361 
1362         return mavenHome;
1363     }
1364 
1365     /**
1366      * @param log a logger could be null
1367      * @return the <code>MAVEN_OPTS</code> env variable value
1368      * @since 2.6
1369      */
1370     private static String getMavenOpts( Log log )
1371     {
1372         String mavenOpts = null;
1373         try
1374         {
1375             mavenOpts = CommandLineUtils.getSystemEnvVars().getProperty( "MAVEN_OPTS" );
1376         }
1377         catch ( IOException e )
1378         {
1379             if ( log != null && log.isDebugEnabled() )
1380             {
1381                 log.debug( "IOException: " + e.getMessage() );
1382             }
1383         }
1384 
1385         return mavenOpts;
1386     }
1387 
1388     /**
1389      * @param log a logger could be null
1390      * @return the <code>JAVA_HOME</code> from System.getProperty( "java.home" )
1391      * By default, <code>System.getProperty( "java.home" ) = JRE_HOME</code> and <code>JRE_HOME</code>
1392      * should be in the <code>JDK_HOME</code>
1393      * @since 2.6
1394      */
1395     private static File getJavaHome( Log log )
1396     {
1397         File javaHome;
1398         if ( SystemUtils.IS_OS_MAC_OSX )
1399         {
1400             javaHome = SystemUtils.getJavaHome();
1401         }
1402         else
1403         {
1404             javaHome = new File( SystemUtils.getJavaHome(), ".." );
1405         }
1406 
1407         if ( javaHome == null || !javaHome.exists() )
1408         {
1409             try
1410             {
1411                 javaHome = new File( CommandLineUtils.getSystemEnvVars().getProperty( "JAVA_HOME" ) );
1412             }
1413             catch ( IOException e )
1414             {
1415                 if ( log != null && log.isDebugEnabled() )
1416                 {
1417                     log.debug( "IOException: " + e.getMessage() );
1418                 }
1419             }
1420         }
1421 
1422         if ( javaHome == null || !javaHome.exists() )
1423         {
1424             if ( log != null && log.isErrorEnabled() )
1425             {
1426                 log.error( "Cannot find Java application directory. Either specify \'java.home\' system property, or "
1427                     + "JAVA_HOME environment variable." );
1428             }
1429         }
1430 
1431         return javaHome;
1432     }
1433 
1434     /**
1435      * @param log a logger could be null
1436      * @return the <code>JAVA_OPTS</code> env variable value
1437      * @since 2.6
1438      */
1439     private static String getJavaOpts( Log log )
1440     {
1441         String javaOpts = null;
1442         try
1443         {
1444             javaOpts = CommandLineUtils.getSystemEnvVars().getProperty( "JAVA_OPTS" );
1445         }
1446         catch ( IOException e )
1447         {
1448             if ( log != null && log.isDebugEnabled() )
1449             {
1450                 log.debug( "IOException: " + e.getMessage() );
1451             }
1452         }
1453 
1454         return javaOpts;
1455     }
1456 
1457     /**
1458      * A Path tokenizer takes a path and returns the components that make up
1459      * that path.
1460      *
1461      * The path can use path separators of either ':' or ';' and file separators
1462      * of either '/' or '\'.
1463      *
1464      * @version revision 439418 taken on 2009-09-12 from Ant Project
1465      * (see http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/PathTokenizer.java)
1466      */
1467     private static class PathTokenizer
1468     {
1469         /**
1470          * A tokenizer to break the string up based on the ':' or ';' separators.
1471          */
1472         private StringTokenizer tokenizer;
1473 
1474         /**
1475          * A String which stores any path components which have been read ahead
1476          * due to DOS filesystem compensation.
1477          */
1478         private String lookahead = null;
1479 
1480         /**
1481          * A boolean that determines if we are running on Novell NetWare, which
1482          * exhibits slightly different path name characteristics (multi-character
1483          * volume / drive names)
1484          */
1485         private boolean onNetWare = Os.isFamily( "netware" );
1486 
1487         /**
1488          * Flag to indicate whether or not we are running on a platform with a
1489          * DOS style filesystem
1490          */
1491         private boolean dosStyleFilesystem;
1492 
1493         /**
1494          * Constructs a path tokenizer for the specified path.
1495          *
1496          * @param path The path to tokenize. Must not be <code>null</code>.
1497          */
1498         public PathTokenizer( String path )
1499         {
1500             if ( onNetWare )
1501             {
1502                 // For NetWare, use the boolean=true mode, so we can use delimiter
1503                 // information to make a better decision later.
1504                 tokenizer = new StringTokenizer( path, ":;", true );
1505             }
1506             else
1507             {
1508                 // on Windows and Unix, we can ignore delimiters and still have
1509                 // enough information to tokenize correctly.
1510                 tokenizer = new StringTokenizer( path, ":;", false );
1511             }
1512             dosStyleFilesystem = File.pathSeparatorChar == ';';
1513         }
1514 
1515         /**
1516          * Tests if there are more path elements available from this tokenizer's
1517          * path. If this method returns <code>true</code>, then a subsequent call
1518          * to nextToken will successfully return a token.
1519          *
1520          * @return <code>true</code> if and only if there is at least one token
1521          * in the string after the current position; <code>false</code> otherwise.
1522          */
1523         public boolean hasMoreTokens()
1524         {
1525             if ( lookahead != null )
1526             {
1527                 return true;
1528             }
1529 
1530             return tokenizer.hasMoreTokens();
1531         }
1532 
1533         /**
1534          * Returns the next path element from this tokenizer.
1535          *
1536          * @return the next path element from this tokenizer.
1537          *
1538          * @exception NoSuchElementException if there are no more elements in this
1539          *            tokenizer's path.
1540          */
1541         public String nextToken()
1542             throws NoSuchElementException
1543         {
1544             String token = null;
1545             if ( lookahead != null )
1546             {
1547                 token = lookahead;
1548                 lookahead = null;
1549             }
1550             else
1551             {
1552                 token = tokenizer.nextToken().trim();
1553             }
1554 
1555             if ( !onNetWare )
1556             {
1557                 if ( token.length() == 1 && Character.isLetter( token.charAt( 0 ) ) && dosStyleFilesystem
1558                     && tokenizer.hasMoreTokens() )
1559                 {
1560                     // we are on a dos style system so this path could be a drive
1561                     // spec. We look at the next token
1562                     String nextToken = tokenizer.nextToken().trim();
1563                     if ( nextToken.startsWith( "\\" ) || nextToken.startsWith( "/" ) )
1564                     {
1565                         // we know we are on a DOS style platform and the next path
1566                         // starts with a slash or backslash, so we know this is a
1567                         // drive spec
1568                         token += ":" + nextToken;
1569                     }
1570                     else
1571                     {
1572                         // store the token just read for next time
1573                         lookahead = nextToken;
1574                     }
1575                 }
1576             }
1577             else
1578             {
1579                 // we are on NetWare, tokenizing is handled a little differently,
1580                 // due to the fact that NetWare has multiple-character volume names.
1581                 if ( token.equals( File.pathSeparator ) || token.equals( ":" ) )
1582                 {
1583                     // ignore ";" and get the next token
1584                     token = tokenizer.nextToken().trim();
1585                 }
1586 
1587                 if ( tokenizer.hasMoreTokens() )
1588                 {
1589                     // this path could be a drive spec, so look at the next token
1590                     String nextToken = tokenizer.nextToken().trim();
1591 
1592                     // make sure we aren't going to get the path separator next
1593                     if ( !nextToken.equals( File.pathSeparator ) )
1594                     {
1595                         if ( nextToken.equals( ":" ) )
1596                         {
1597                             if ( !token.startsWith( "/" ) && !token.startsWith( "\\" ) && !token.startsWith( "." )
1598                                 && !token.startsWith( ".." ) )
1599                             {
1600                                 // it indeed is a drive spec, get the next bit
1601                                 String oneMore = tokenizer.nextToken().trim();
1602                                 if ( !oneMore.equals( File.pathSeparator ) )
1603                                 {
1604                                     token += ":" + oneMore;
1605                                 }
1606                                 else
1607                                 {
1608                                     token += ":";
1609                                     lookahead = oneMore;
1610                                 }
1611                             }
1612                             // implicit else: ignore the ':' since we have either a
1613                             // UNIX or a relative path
1614                         }
1615                         else
1616                         {
1617                             // store the token just read for next time
1618                             lookahead = nextToken;
1619                         }
1620                     }
1621                 }
1622             }
1623             return token;
1624         }
1625     }
1626     
1627     static List<String> toList( String src )
1628     {
1629         return toList( src, null, null );
1630     }
1631     
1632     static List<String> toList( String src, String elementPrefix, String elementSuffix )
1633     {
1634         if ( StringUtils.isEmpty( src ) )
1635         {
1636             return null;
1637         }
1638         
1639         List<String> result = new ArrayList<String>();
1640 
1641         StringTokenizer st = new StringTokenizer( src, "[,:;]" );
1642         StringBuilder sb = new StringBuilder( 256 );
1643         while ( st.hasMoreTokens() )
1644         {
1645             sb.setLength( 0 );
1646             if ( StringUtils.isNotEmpty( elementPrefix ) )
1647             {
1648                 sb.append( elementPrefix );
1649             }
1650             
1651             sb.append( st.nextToken() );
1652             
1653             if ( StringUtils.isNotEmpty( elementSuffix ) )
1654             {
1655                 sb.append( elementSuffix );
1656             }
1657             
1658             result.add( sb.toString() );
1659         }
1660         
1661         return result;
1662     }
1663     
1664     static <T> List<T> toList( T[] multiple )
1665     {
1666         return toList( null, multiple );
1667     }
1668     
1669     static <T> List<T> toList( T single, T[] multiple )
1670     {
1671         if ( single == null && ( multiple == null || multiple.length < 1 ) )
1672         {
1673             return null;
1674         }
1675         
1676         List<T> result = new ArrayList<T>();
1677         if ( single != null )
1678         {
1679             result.add( single );
1680         }
1681         
1682         if ( multiple != null && multiple.length > 0 )
1683         {
1684             result.addAll( Arrays.asList( multiple ) );
1685         }
1686         
1687         return result;
1688     }
1689     
1690     // TODO: move to plexus-utils or use something appropriate from there
1691     public static String toRelative( File basedir, String absolutePath )
1692     {
1693         String relative;
1694 
1695         absolutePath = absolutePath.replace( '\\', '/' );
1696         String basedirPath = basedir.getAbsolutePath().replace( '\\', '/' );
1697 
1698         if ( absolutePath.startsWith( basedirPath ) )
1699         {
1700             relative = absolutePath.substring( basedirPath.length() );
1701             if ( relative.startsWith( "/" ) )
1702             {
1703                 relative = relative.substring( 1 );
1704             }
1705             if ( relative.length() <= 0 )
1706             {
1707                 relative = ".";
1708             }
1709         }
1710         else
1711         {
1712             relative = absolutePath;
1713         }
1714 
1715         return relative;
1716     }
1717     
1718     /**
1719      * Convenience method to determine that a collection is not empty or null.
1720      */
1721     public static boolean isNotEmpty( final Collection<?> collection )
1722     {
1723         return collection != null && !collection.isEmpty();
1724     }
1725     
1726     /**
1727      * Convenience method to determine that a collection is empty or null.
1728      */
1729     public static boolean isEmpty( final Collection<?> collection )
1730     {
1731         return collection == null || collection.isEmpty();
1732     }
1733     
1734 }