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