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