You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2019/01/04 17:15:45 UTC

[maven-javadoc-plugin] 01/01: [MJAVADOC-527] detectLinks may pass invalid URLs to javadoc(1)

This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch MJAVADOC-527
in repository https://gitbox.apache.org/repos/asf/maven-javadoc-plugin.git

commit 9b30b188359408e99f513edb8e7f54bce4f1d997
Author: dedabob <de...@users.noreply.github.com>
AuthorDate: Mon Jun 11 11:28:31 2018 +0200

    [MJAVADOC-527] detectLinks may pass invalid URLs to javadoc(1)
    
    This closes #4
---
 .../apache/maven/plugins/javadoc/JavadocUtil.java  | 3693 ++++++++++----------
 .../plugins/javadoc/AbstractJavadocMojoTest.java   |  169 +-
 2 files changed, 1945 insertions(+), 1917 deletions(-)

diff --git a/src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java b/src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java
index e2f9b26..3a3d55b 100644
--- a/src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java
+++ b/src/main/java/org/apache/maven/plugins/javadoc/JavadocUtil.java
@@ -1,1838 +1,1855 @@
-package org.apache.maven.plugins.javadoc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.http.HttpHeaders;
-import org.apache.http.HttpHost;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.auth.AuthScope;
-import org.apache.http.auth.Credentials;
-import org.apache.http.auth.UsernamePasswordCredentials;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.params.ClientPNames;
-import org.apache.http.client.params.CookiePolicy;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.conn.params.ConnRoutePNames;
-import org.apache.http.impl.client.DefaultHttpClient;
-import org.apache.http.impl.conn.PoolingClientConnectionManager;
-import org.apache.http.message.BasicHeader;
-import org.apache.http.params.CoreConnectionPNames;
-import org.apache.http.params.CoreProtocolPNames;
-import org.apache.maven.plugin.logging.Log;
-import org.apache.maven.project.MavenProject;
-import org.apache.maven.settings.Proxy;
-import org.apache.maven.settings.Settings;
-import org.apache.maven.shared.invoker.DefaultInvocationRequest;
-import org.apache.maven.shared.invoker.DefaultInvoker;
-import org.apache.maven.shared.invoker.InvocationOutputHandler;
-import org.apache.maven.shared.invoker.InvocationRequest;
-import org.apache.maven.shared.invoker.InvocationResult;
-import org.apache.maven.shared.invoker.Invoker;
-import org.apache.maven.shared.invoker.MavenInvocationException;
-import org.apache.maven.shared.invoker.PrintStreamHandler;
-import org.apache.maven.wagon.proxy.ProxyInfo;
-import org.apache.maven.wagon.proxy.ProxyUtils;
-import org.codehaus.plexus.languages.java.version.JavaVersion;
-import org.codehaus.plexus.util.DirectoryScanner;
-import org.codehaus.plexus.util.FileUtils;
-import org.codehaus.plexus.util.IOUtil;
-import org.codehaus.plexus.util.Os;
-import org.codehaus.plexus.util.StringUtils;
-import org.codehaus.plexus.util.cli.CommandLineException;
-import org.codehaus.plexus.util.cli.CommandLineUtils;
-import org.codehaus.plexus.util.cli.Commandline;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Modifier;
-import java.net.SocketTimeoutException;
-import java.net.URI;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.nio.charset.Charset;
-import java.nio.charset.IllegalCharsetNameException;
-import java.nio.file.FileVisitResult;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.SimpleFileVisitor;
-import java.nio.file.attribute.BasicFileAttributes;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Locale;
-import java.util.NoSuchElementException;
-import java.util.Properties;
-import java.util.Set;
-import java.util.StringTokenizer;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * Set of utilities methods for Javadoc.
- *
- * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
- * @since 2.4
- */
-public class JavadocUtil
-{
-    /** The default timeout used when fetching url, i.e. 2000. */
-    public static final int DEFAULT_TIMEOUT = 2000;
-
-    /** Error message when VM could not be started using invoker. */
-    protected static final String ERROR_INIT_VM =
-        "Error occurred during initialization of VM, try to reduce the Java heap size for the MAVEN_OPTS "
-            + "environment variable using -Xms:<size> and -Xmx:<size>.";
-
-    /**
-     * Method that removes the invalid directories in the specified directories. <b>Note</b>: All elements in
-     * <code>dirs</code> could be an absolute or relative against the project's base directory <code>String</code> path.
-     *
-     * @param project the current Maven project not null
-     * @param dirs the collection of <code>String</code> directories path that will be validated.
-     * @return a List of valid <code>String</code> directories absolute paths.
-     */
-    public static Collection<Path> pruneDirs( MavenProject project, Collection<String> dirs )
-    {
-        final Path projectBasedir = project.getBasedir().toPath();
-        
-        Set<Path> pruned = new LinkedHashSet<>( dirs.size() );
-        for ( String dir : dirs )
-        {
-            if ( dir == null )
-            {
-                continue;
-            }
-
-            Path directory = projectBasedir.resolve( dir );
-
-            if ( Files.isDirectory( directory ) )
-            {
-                pruned.add( directory.toAbsolutePath() );
-            }
-        }
-
-        return pruned;
-    }
-
-    /**
-     * Method that removes the invalid files in the specified files. <b>Note</b>: All elements in <code>files</code>
-     * should be an absolute <code>String</code> path.
-     *
-     * @param files the list of <code>String</code> files paths that will be validated.
-     * @return a List of valid <code>File</code> objects.
-     */
-    protected static List<String> pruneFiles( Collection<String> files )
-    {
-        List<String> pruned = new ArrayList<>( files.size() );
-        for ( String f : files )
-        {
-            if ( !shouldPruneFile( f, pruned ) )
-            {
-                pruned.add( f );
-            }
-        }
-
-        return pruned;
-    }
-
-    /**
-     * Determine whether a file should be excluded from the provided list of paths, based on whether it exists and is
-     * already present in the list.
-     * 
-     * @param f The files.
-     * @param pruned The list of pruned files..
-     * @return true if the file could be pruned false otherwise.
-     */
-    public static boolean shouldPruneFile( String f, List<String> pruned )
-    {
-        if ( f != null )
-        {
-            File file = new File( f );
-            if ( file.isFile() && ( isEmpty( pruned ) || !pruned.contains( f ) ) )
-            {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Method that gets all the source files to be excluded from the javadoc on the given source paths.
-     *
-     * @param sourcePaths the path to the source files
-     * @param excludedPackages the package names to be excluded in the javadoc
-     * @return a List of the packages to be excluded in the generated javadoc
-     */
-    protected static List<String> getExcludedPackages( Collection<Path> sourcePaths,
-                                                       Collection<String> excludedPackages )
-    {
-        List<String> excludedNames = new ArrayList<>();
-        for ( Path sourcePath : sourcePaths )
-        {
-            excludedNames.addAll( getExcludedPackages( sourcePath, excludedPackages ) );
-        }
-
-        return excludedNames;
-    }
-
-    /**
-     * Convenience method to wrap an argument value in single quotes (i.e. <code>'</code>). Intended for values which
-     * may contain whitespaces. <br>
-     * To prevent javadoc error, the line separator (i.e. <code>\n</code>) are skipped.
-     *
-     * @param value the argument value.
-     * @return argument with quote
-     */
-    protected static String quotedArgument( String value )
-    {
-        String arg = value;
-
-        if ( StringUtils.isNotEmpty( arg ) )
-        {
-            if ( arg.contains( "'" ) )
-            {
-                arg = StringUtils.replace( arg, "'", "\\'" );
-            }
-            arg = "'" + arg + "'";
-
-            // To prevent javadoc error
-            arg = StringUtils.replace( arg, "\n", " " );
-        }
-
-        return arg;
-    }
-
-    /**
-     * Convenience method to format a path argument so that it is properly interpreted by the javadoc tool. Intended for
-     * path values which may contain whitespaces.
-     *
-     * @param value the argument value.
-     * @return path argument with quote
-     */
-    protected static String quotedPathArgument( String value )
-    {
-        String path = value;
-
-        if ( StringUtils.isNotEmpty( path ) )
-        {
-            path = path.replace( '\\', '/' );
-            if ( path.contains( "\'" ) )
-            {
-                String split[] = path.split( "\'" );
-                path = "";
-
-                for ( int i = 0; i < split.length; i++ )
-                {
-                    if ( i != split.length - 1 )
-                    {
-                        path = path + split[i] + "\\'";
-                    }
-                    else
-                    {
-                        path = path + split[i];
-                    }
-                }
-            }
-            path = "'" + path + "'";
-        }
-
-        return path;
-    }
-
-    /**
-     * Convenience method that copy all <code>doc-files</code> directories from <code>javadocDir</code> to the
-     * <code>outputDirectory</code>.
-     *
-     * @param outputDirectory the output directory
-     * @param javadocDir the javadoc directory
-     * @param excludedocfilessubdir the excludedocfilessubdir parameter
-     * @throws IOException if any
-     * @since 2.5
-     */
-    protected static void copyJavadocResources( File outputDirectory, File javadocDir, String excludedocfilessubdir )
-        throws IOException
-    {
-        if ( !javadocDir.isDirectory() )
-        {
-            return;
-        }
-
-        List<String> excludes = new ArrayList<>( Arrays.asList( FileUtils.getDefaultExcludes() ) );
-
-        if ( StringUtils.isNotEmpty( excludedocfilessubdir ) )
-        {
-            StringTokenizer st = new StringTokenizer( excludedocfilessubdir, ":" );
-            String current;
-            while ( st.hasMoreTokens() )
-            {
-                current = st.nextToken();
-                excludes.add( "**/" + current + "/**" );
-            }
-        }
-
-        List<String> docFiles =
-            FileUtils.getDirectoryNames( javadocDir, "resources,**/doc-files",
-                                         StringUtils.join( excludes.iterator(), "," ), false, true );
-        for ( String docFile : docFiles )
-        {
-            File docFileOutput = new File( outputDirectory, docFile );
-            FileUtils.mkdir( docFileOutput.getAbsolutePath() );
-            FileUtils.copyDirectoryStructure( new File( javadocDir, docFile ), docFileOutput );
-            List<String> files =
-                FileUtils.getFileAndDirectoryNames( docFileOutput, StringUtils.join( excludes.iterator(), "," ), null,
-                                                    true, true, true, true );
-            for ( String filename : files )
-            {
-                File file = new File( filename );
-
-                if ( file.isDirectory() )
-                {
-                    FileUtils.deleteDirectory( file );
-                }
-                else
-                {
-                    file.delete();
-                }
-            }
-        }
-    }
-
-    /**
-     * Method that gets the files or classes that would be included in the javadocs using the subpackages parameter.
-     *
-     * @param sourceDirectory the directory where the source files are located
-     * @param fileList the list of all relative files found in the sourceDirectory
-     * @param excludePackages package names to be excluded in the javadoc
-     * @return a StringBuilder that contains the appended file names of the files to be included in the javadoc
-     */
-    protected static List<String> getIncludedFiles( File sourceDirectory, String[] fileList,
-                                                    Collection<String> excludePackages )
-    {
-        List<String> files = new ArrayList<>();
-        
-        List<Pattern> excludePackagePatterns = new ArrayList<>( excludePackages.size() );
-        for ( String excludePackage :  excludePackages )
-        {
-            excludePackagePatterns.add( Pattern.compile( excludePackage.replace( '.', File.separatorChar )
-                                                                       .replace( "\\", "\\\\" )
-                                                                       .replace( "*", ".+" )
-                                                                       .concat( "[\\\\/][^\\\\/]+\\.java" )
-                                                                                ) );
-        }
-
-        for ( String file : fileList )
-        {
-            boolean excluded = false;
-            for ( Pattern excludePackagePattern :  excludePackagePatterns )
-            {
-                if ( excludePackagePattern.matcher( file ).matches() )
-                {
-                    excluded = true;
-                    break;
-                }
-            }
-            
-            if ( !excluded )
-            {
-                files.add( file );
-            }
-        }
-
-        return files;
-    }
-
-    /**
-     * Method that gets the complete package names (including subpackages) of the packages that were defined in the
-     * excludePackageNames parameter.
-     *
-     * @param sourceDirectory the directory where the source files are located
-     * @param excludePackagenames package names to be excluded in the javadoc
-     * @return a List of the packagenames to be excluded
-     */
-    protected static Collection<String> getExcludedPackages( final Path sourceDirectory,
-                                                             Collection<String> excludePackagenames )
-    {
-        final String regexFileSeparator = File.separator.replace( "\\", "\\\\" );
-        
-        final Collection<String> fileList = new ArrayList<>();
-        
-        try
-        {
-            Files.walkFileTree( sourceDirectory, new SimpleFileVisitor<Path>()
-            {
-                @Override
-                public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
-                    throws IOException
-                {
-                    if ( file.getFileName().toString().endsWith( ".java" ) )
-                    {
-                        fileList.add( sourceDirectory.relativize( file.getParent() ).toString() );
-                    }
-                    return FileVisitResult.CONTINUE;
-                }
-            } );
-        }
-        catch ( IOException e )
-        {
-            // noop
-        }
-
-        List<String> files = new ArrayList<>();
-        for ( String excludePackagename : excludePackagenames )
-        {
-            // Usage of wildcard was bad specified and bad implemented, i.e. using String.contains() 
-            //   without respecting surrounding context
-            // Following implementation should match requirements as defined in the examples:  
-            // - A wildcard at the beginning should match 1 or more folders
-            // - Any other wildcard must match exactly one folder
-            Pattern p = Pattern.compile( excludePackagename.replace( ".", regexFileSeparator )
-                                                           .replaceFirst( "^\\*", ".+" )
-                                                           .replace( "*", "[^" + regexFileSeparator + "]+" ) );
-            
-            for ( String aFileList : fileList )
-            {
-                if ( p.matcher( aFileList ).matches() )
-                {
-                    files.add( aFileList.replace( File.separatorChar, '.' ) );
-                }
-            }
-        }
-
-        return files;
-    }
-
-    /**
-     * Convenience method that gets the files to be included in the javadoc.
-     *
-     * @param sourceDirectory the directory where the source files are located
-     * @param excludePackages the packages to be excluded in the javadocs
-     * @param sourceFileIncludes files to include.
-     * @param sourceFileExcludes files to exclude.
-     */
-    protected static List<String> getFilesFromSource( File sourceDirectory, List<String> sourceFileIncludes,
-                                                      List<String> sourceFileExcludes,
-                                                      Collection<String> excludePackages )
-    {
-        DirectoryScanner ds = new DirectoryScanner();
-        if ( sourceFileIncludes == null )
-        {
-            sourceFileIncludes = Collections.singletonList( "**/*.java" );
-        }
-        ds.setIncludes( sourceFileIncludes.toArray( new String[sourceFileIncludes.size()] ) );
-        if ( sourceFileExcludes != null && sourceFileExcludes.size() > 0 )
-        {
-            ds.setExcludes( sourceFileExcludes.toArray( new String[sourceFileExcludes.size()] ) );
-        }
-        ds.setBasedir( sourceDirectory );
-        ds.scan();
-
-        String[] fileList = ds.getIncludedFiles();
-
-        List<String> files = new ArrayList<>();
-        if ( fileList.length != 0 )
-        {
-            for ( String includedFile : getIncludedFiles( sourceDirectory, fileList, excludePackages ) )
-            {
-                files.add( includedFile );
-            }
-        }
-
-        return files;
-    }
-
-    /**
-     * Call the Javadoc tool and parse its output to find its version, i.e.:
-     * 
-     * <pre>
-     * javadoc.exe( or.sh ) - J - version
-     * </pre>
-     *
-     * @param javadocExe not null file
-     * @return the javadoc version as float
-     * @throws IOException if javadocExe is null, doesn't exist or is not a file
-     * @throws CommandLineException if any
-     * @throws IllegalArgumentException if no output was found in the command line
-     * @throws PatternSyntaxException if the output contains a syntax error in the regular-expression pattern.
-     * @see #extractJavadocVersion(String)
-     */
-    protected static JavaVersion getJavadocVersion( File javadocExe )
-        throws IOException, CommandLineException, IllegalArgumentException
-    {
-        if ( ( javadocExe == null ) || ( !javadocExe.exists() ) || ( !javadocExe.isFile() ) )
-        {
-            throw new IOException( "The javadoc executable '" + javadocExe + "' doesn't exist or is not a file. " );
-        }
-
-        Commandline cmd = new Commandline();
-        cmd.setExecutable( javadocExe.getAbsolutePath() );
-        cmd.setWorkingDirectory( javadocExe.getParentFile() );
-        cmd.createArg().setValue( "-J-version" );
-
-        CommandLineUtils.StringStreamConsumer out = new JavadocOutputStreamConsumer();
-        CommandLineUtils.StringStreamConsumer err = new JavadocOutputStreamConsumer();
-
-        int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
-
-        if ( exitCode != 0 )
-        {
-            StringBuilder msg = new StringBuilder( "Exit code: " + exitCode + " - " + err.getOutput() );
-            msg.append( '\n' );
-            msg.append( "Command line was:" + CommandLineUtils.toString( cmd.getCommandline() ) );
-            throw new CommandLineException( msg.toString() );
-        }
-
-        if ( StringUtils.isNotEmpty( err.getOutput() ) )
-        {
-            return JavaVersion.parse( extractJavadocVersion( err.getOutput() ) );
-        }
-        else if ( StringUtils.isNotEmpty( out.getOutput() ) )
-        {
-            return JavaVersion.parse( extractJavadocVersion( out.getOutput() ) );
-        }
-
-        throw new IllegalArgumentException( "No output found from the command line 'javadoc -J-version'" );
-    }
-
-    /**
-     * Parse the output for 'javadoc -J-version' and return the javadoc version recognized. <br>
-     * Here are some output for 'javadoc -J-version' depending the JDK used:
-     * <table summary="Output for 'javadoc -J-version' per JDK">
-     * <tr>
-     * <th>JDK</th>
-     * <th>Output for 'javadoc -J-version'</th>
-     * </tr>
-     * <tr>
-     * <td>Sun 1.4</td>
-     * <td>java full version "1.4.2_12-b03"</td>
-     * </tr>
-     * <tr>
-     * <td>Sun 1.5</td>
-     * <td>java full version "1.5.0_07-164"</td>
-     * </tr>
-     * <tr>
-     * <td>IBM 1.4</td>
-     * <td>javadoc full version "J2RE 1.4.2 IBM Windows 32 build cn1420-20040626"</td>
-     * </tr>
-     * <tr>
-     * <td>IBM 1.5 (French JVM)</td>
-     * <td>javadoc version complète de "J2RE 1.5.0 IBM Windows 32 build pwi32pdev-20070426a"</td>
-     * </tr>
-     * <tr>
-     * <td>FreeBSD 1.5</td>
-     * <td>java full version "diablo-1.5.0-b01"</td>
-     * </tr>
-     * <tr>
-     * <td>BEA jrockit 1.5</td>
-     * <td>java full version "1.5.0_11-b03"</td>
-     * </tr>
-     * </table>
-     *
-     * @param output for 'javadoc -J-version'
-     * @return the version of the javadoc for the output, only digits and dots
-     * @throws PatternSyntaxException if the output doesn't match with the output pattern
-     *             <tt>(?s).*?[^a-zA-Z]([0-9]+\\.?[0-9]*)(\\.([0-9]+))?.*</tt>.
-     * @throws IllegalArgumentException if the output is null
-     */
-    protected static String extractJavadocVersion( String output )
-        throws IllegalArgumentException
-    {
-        if ( StringUtils.isEmpty( output ) )
-        {
-            throw new IllegalArgumentException( "The output could not be null." );
-        }
-
-        Pattern pattern = Pattern.compile( "(?s).*?[^a-zA-Z](([0-9]+\\.?[0-9]*)(\\.[0-9]+)?).*" );
-
-        Matcher matcher = pattern.matcher( output );
-        if ( !matcher.matches() )
-        {
-            throw new PatternSyntaxException( "Unrecognized version of Javadoc: '" + output + "'", pattern.pattern(),
-                                              pattern.toString().length() - 1 );
-        }
-
-        return matcher.group( 1 );
-    }
-
-    /**
-     * Parse a memory string which be used in the JVM arguments <code>-Xms</code> or <code>-Xmx</code>. <br>
-     * Here are some supported memory string depending the JDK used:
-     * <table summary="Memory argument support per JDK">
-     * <tr>
-     * <th>JDK</th>
-     * <th>Memory argument support for <code>-Xms</code> or <code>-Xmx</code></th>
-     * </tr>
-     * <tr>
-     * <td>SUN</td>
-     * <td>1024k | 128m | 1g | 1t</td>
-     * </tr>
-     * <tr>
-     * <td>IBM</td>
-     * <td>1024k | 1024b | 128m | 128mb | 1g | 1gb</td>
-     * </tr>
-     * <tr>
-     * <td>BEA</td>
-     * <td>1024k | 1024kb | 128m | 128mb | 1g | 1gb</td>
-     * </tr>
-     * </table>
-     *
-     * @param memory the memory to be parsed, not null.
-     * @return the memory parsed with a supported unit. If no unit specified in the <code>memory</code> parameter, the
-     *         default unit is <code>m</code>. The units <code>g | gb</code> or <code>t | tb</code> will be converted in
-     *         <code>m</code>.
-     * @throws IllegalArgumentException if the <code>memory</code> parameter is null or doesn't match any pattern.
-     */
-    protected static String parseJavadocMemory( String memory )
-        throws IllegalArgumentException
-    {
-        if ( StringUtils.isEmpty( memory ) )
-        {
-            throw new IllegalArgumentException( "The memory could not be null." );
-        }
-
-        Pattern p = Pattern.compile( "^\\s*(\\d+)\\s*?\\s*$" );
-        Matcher m = p.matcher( memory );
-        if ( m.matches() )
-        {
-            return m.group( 1 ) + "m";
-        }
-
-        p = Pattern.compile( "^\\s*(\\d+)\\s*k(b)?\\s*$", Pattern.CASE_INSENSITIVE );
-        m = p.matcher( memory );
-        if ( m.matches() )
-        {
-            return m.group( 1 ) + "k";
-        }
-
-        p = Pattern.compile( "^\\s*(\\d+)\\s*m(b)?\\s*$", Pattern.CASE_INSENSITIVE );
-        m = p.matcher( memory );
-        if ( m.matches() )
-        {
-            return m.group( 1 ) + "m";
-        }
-
-        p = Pattern.compile( "^\\s*(\\d+)\\s*g(b)?\\s*$", Pattern.CASE_INSENSITIVE );
-        m = p.matcher( memory );
-        if ( m.matches() )
-        {
-            return ( Integer.parseInt( m.group( 1 ) ) * 1024 ) + "m";
-        }
-
-        p = Pattern.compile( "^\\s*(\\d+)\\s*t(b)?\\s*$", Pattern.CASE_INSENSITIVE );
-        m = p.matcher( memory );
-        if ( m.matches() )
-        {
-            return ( Integer.parseInt( m.group( 1 ) ) * 1024 * 1024 ) + "m";
-        }
-
-        throw new IllegalArgumentException( "Could convert not to a memory size: " + memory );
-    }
-
-    /**
-     * Validate if a charset is supported on this platform.
-     *
-     * @param charsetName the charsetName to be check.
-     * @return <code>true</code> if the given charset is supported by the JVM, <code>false</code> otherwise.
-     */
-    protected static boolean validateEncoding( String charsetName )
-    {
-        if ( StringUtils.isEmpty( charsetName ) )
-        {
-            return false;
-        }
-
-        try
-        {
-            return Charset.isSupported( charsetName );
-        }
-        catch ( IllegalCharsetNameException e )
-        {
-            return false;
-        }
-    }
-
-    /**
-     * For security reasons, if an active proxy is defined and needs an authentication by username/password, hide the
-     * proxy password in the command line.
-     *
-     * @param cmdLine a command line, not null
-     * @param settings the user settings
-     * @return the cmdline with '*' for the http.proxyPassword JVM property
-     */
-    protected static String hideProxyPassword( String cmdLine, Settings settings )
-    {
-        if ( cmdLine == null )
-        {
-            throw new IllegalArgumentException( "cmdLine could not be null" );
-        }
-
-        if ( settings == null )
-        {
-            return cmdLine;
-        }
-
-        Proxy activeProxy = settings.getActiveProxy();
-        if ( activeProxy != null && StringUtils.isNotEmpty( activeProxy.getHost() )
-            && StringUtils.isNotEmpty( activeProxy.getUsername() )
-            && StringUtils.isNotEmpty( activeProxy.getPassword() ) )
-        {
-            String pass = "-J-Dhttp.proxyPassword=\"" + activeProxy.getPassword() + "\"";
-            String hidepass =
-                "-J-Dhttp.proxyPassword=\"" + StringUtils.repeat( "*", activeProxy.getPassword().length() ) + "\"";
-
-            return StringUtils.replace( cmdLine, pass, hidepass );
-        }
-
-        return cmdLine;
-    }
-
-    /**
-     * Auto-detect the class names of the implementation of <code>com.sun.tools.doclets.Taglet</code> class from a given
-     * jar file. <br>
-     * <b>Note</b>: <code>JAVA_HOME/lib/tools.jar</code> is a requirement to find
-     * <code>com.sun.tools.doclets.Taglet</code> class.
-     *
-     * @param jarFile not null
-     * @return the list of <code>com.sun.tools.doclets.Taglet</code> class names from a given jarFile.
-     * @throws IOException if jarFile is invalid or not found, or if the <code>JAVA_HOME/lib/tools.jar</code> is not
-     *             found.
-     * @throws ClassNotFoundException if any
-     * @throws NoClassDefFoundError if any
-     */
-    protected static List<String> getTagletClassNames( File jarFile )
-        throws IOException, ClassNotFoundException, NoClassDefFoundError
-    {
-        List<String> classes = getClassNamesFromJar( jarFile );
-        ClassLoader cl;
-
-        // Needed to find com.sun.tools.doclets.Taglet class
-        File tools = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
-        if ( tools.exists() && tools.isFile() )
-        {
-            cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL(), tools.toURI().toURL() }, null );
-        }
-        else
-        {
-            cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL() }, ClassLoader.getSystemClassLoader() );
-        }
-
-        List<String> tagletClasses = new ArrayList<>();
-
-        Class<?> tagletClass;
-
-        try
-        {
-            tagletClass = cl.loadClass( "com.sun.tools.doclets.Taglet" );
-        }
-        catch ( ClassNotFoundException e )
-        {
-            tagletClass = cl.loadClass( "jdk.javadoc.doclet.Taglet" );
-        }
-
-        for ( String s : classes )
-        {
-            Class<?> c = cl.loadClass( s );
-
-            if ( tagletClass.isAssignableFrom( c ) && !Modifier.isAbstract( c.getModifiers() ) )
-            {
-                tagletClasses.add( c.getName() );
-            }
-        }
-
-        return tagletClasses;
-    }
-
-    /**
-     * Copy the given url to the given file.
-     *
-     * @param url not null url
-     * @param file not null file where the url will be created
-     * @throws IOException if any
-     * @since 2.6
-     */
-    protected static void copyResource( URL url, File file )
-        throws IOException
-    {
-        if ( file == null )
-        {
-            throw new IOException( "The file can't be null." );
-        }
-        if ( url == null )
-        {
-            throw new IOException( "The url could not be null." );
-        }
-
-        FileUtils.copyURLToFile( url, file );
-    }
-
-    /**
-     * Invoke Maven for the given project file with a list of goals and properties, the output will be in the invokerlog
-     * file. <br>
-     * <b>Note</b>: the Maven Home should be defined in the <code>maven.home</code> Java system property or defined in
-     * <code>M2_HOME</code> system env variables.
-     *
-     * @param log a logger could be null.
-     * @param localRepositoryDir the localRepository not null.
-     * @param projectFile a not null project file.
-     * @param goals a not null goals list.
-     * @param properties the properties for the goals, could be null.
-     * @param invokerLog the log file where the invoker will be written, if null using <code>System.out</code>.
-     * @throws MavenInvocationException if any
-     * @since 2.6
-     */
-    protected static void invokeMaven( Log log, File localRepositoryDir, File projectFile, List<String> goals,
-                                       Properties properties, File invokerLog )
-        throws MavenInvocationException
-    {
-        if ( projectFile == null )
-        {
-            throw new IllegalArgumentException( "projectFile should be not null." );
-        }
-        if ( !projectFile.isFile() )
-        {
-            throw new IllegalArgumentException( projectFile.getAbsolutePath() + " is not a file." );
-        }
-        if ( goals == null || goals.size() == 0 )
-        {
-            throw new IllegalArgumentException( "goals should be not empty." );
-        }
-        if ( localRepositoryDir == null || !localRepositoryDir.isDirectory() )
-        {
-            throw new IllegalArgumentException( "localRepositoryDir '" + localRepositoryDir
-                + "' should be a directory." );
-        }
-
-        String mavenHome = getMavenHome( log );
-        if ( StringUtils.isEmpty( mavenHome ) )
-        {
-            String msg = "Could NOT invoke Maven because no Maven Home is defined. You need to have set the M2_HOME "
-                + "system env variable or a maven.home Java system properties.";
-            if ( log != null )
-            {
-                log.error( msg );
-            }
-            else
-            {
-                System.err.println( msg );
-            }
-            return;
-        }
-
-        Invoker invoker = new DefaultInvoker();
-        invoker.setMavenHome( new File( mavenHome ) );
-        invoker.setLocalRepositoryDirectory( localRepositoryDir );
-
-        InvocationRequest request = new DefaultInvocationRequest();
-        request.setBaseDirectory( projectFile.getParentFile() );
-        request.setPomFile( projectFile );
-        request.setBatchMode( true );
-        if ( log != null )
-        {
-            request.setDebug( log.isDebugEnabled() );
-        }
-        else
-        {
-            request.setDebug( true );
-        }
-        request.setGoals( goals );
-        if ( properties != null )
-        {
-            request.setProperties( properties );
-        }
-        File javaHome = getJavaHome( log );
-        if ( javaHome != null )
-        {
-            request.setJavaHome( javaHome );
-        }
-
-        if ( log != null && log.isDebugEnabled() )
-        {
-            log.debug( "Invoking Maven for the goals: " + goals + " with "
-                + ( properties == null ? "no properties" : "properties=" + properties ) );
-        }
-        InvocationResult result = invoke( log, invoker, request, invokerLog, goals, properties, null );
-
-        if ( result.getExitCode() != 0 )
-        {
-            String invokerLogContent = readFile( invokerLog, "UTF-8" );
-
-            // see DefaultMaven
-            if ( invokerLogContent != null && ( !invokerLogContent.contains( "Scanning for projects..." )
-                || invokerLogContent.contains( OutOfMemoryError.class.getName() ) ) )
-            {
-                if ( log != null )
-                {
-                    log.error( "Error occurred during initialization of VM, trying to use an empty MAVEN_OPTS..." );
-
-                    if ( log.isDebugEnabled() )
-                    {
-                        log.debug( "Reinvoking Maven for the goals: " + goals + " with an empty MAVEN_OPTS..." );
-                    }
-                }
-                result = invoke( log, invoker, request, invokerLog, goals, properties, "" );
-            }
-        }
-
-        if ( result.getExitCode() != 0 )
-        {
-            String invokerLogContent = readFile( invokerLog, "UTF-8" );
-
-            // see DefaultMaven
-            if ( invokerLogContent != null && ( !invokerLogContent.contains( "Scanning for projects..." )
-                || invokerLogContent.contains( OutOfMemoryError.class.getName() ) ) )
-            {
-                throw new MavenInvocationException( ERROR_INIT_VM );
-            }
-
-            throw new MavenInvocationException( "Error when invoking Maven, consult the invoker log file: "
-                + invokerLog.getAbsolutePath() );
-        }
-    }
-
-    /**
-     * Read the given file and return the content or null if an IOException occurs.
-     *
-     * @param javaFile not null
-     * @param encoding could be null
-     * @return the content with unified line separator of the given javaFile using the given encoding.
-     * @see FileUtils#fileRead(File, String)
-     * @since 2.6.1
-     */
-    protected static String readFile( final File javaFile, final String encoding )
-    {
-        try
-        {
-            return FileUtils.fileRead( javaFile, encoding );
-        }
-        catch ( IOException e )
-        {
-            return null;
-        }
-    }
-
-    /**
-     * Split the given path with colon and semi-colon, to support Solaris and Windows path. Examples:
-     * 
-     * <pre>
-     * splitPath( "/home:/tmp" )     = ["/home", "/tmp"]
-     * splitPath( "/home;/tmp" )     = ["/home", "/tmp"]
-     * splitPath( "C:/home:C:/tmp" ) = ["C:/home", "C:/tmp"]
-     * splitPath( "C:/home;C:/tmp" ) = ["C:/home", "C:/tmp"]
-     * </pre>
-     *
-     * @param path which can contain multiple paths separated with a colon (<code>:</code>) or a semi-colon
-     *            (<code>;</code>), platform independent. Could be null.
-     * @return the path splitted by colon or semi-colon or <code>null</code> if path was <code>null</code>.
-     * @since 2.6.1
-     */
-    protected static String[] splitPath( final String path )
-    {
-        if ( path == null )
-        {
-            return null;
-        }
-
-        List<String> subpaths = new ArrayList<>();
-        PathTokenizer pathTokenizer = new PathTokenizer( path );
-        while ( pathTokenizer.hasMoreTokens() )
-        {
-            subpaths.add( pathTokenizer.nextToken() );
-        }
-
-        return subpaths.toArray( new String[subpaths.size()] );
-    }
-
-    /**
-     * Unify the given path with the current System path separator, to be platform independent. Examples:
-     * 
-     * <pre>
-     * unifyPathSeparator( "/home:/tmp" ) = "/home:/tmp" (Solaris box)
-     * unifyPathSeparator( "/home:/tmp" ) = "/home;/tmp" (Windows box)
-     * </pre>
-     *
-     * @param path which can contain multiple paths by separating them with a colon (<code>:</code>) or a semi-colon
-     *            (<code>;</code>), platform independent. Could be null.
-     * @return the same path but separated with the current System path separator or <code>null</code> if path was
-     *         <code>null</code>.
-     * @since 2.6.1
-     * @see #splitPath(String)
-     * @see File#pathSeparator
-     */
-    protected static String unifyPathSeparator( final String path )
-    {
-        if ( path == null )
-        {
-            return null;
-        }
-
-        return StringUtils.join( splitPath( path ), File.pathSeparator );
-    }
-
-    // ----------------------------------------------------------------------
-    // private methods
-    // ----------------------------------------------------------------------
-
-    /**
-     * @param jarFile not null
-     * @return all class names from the given jar file.
-     * @throws IOException if any or if the jarFile is null or doesn't exist.
-     */
-    private static List<String> getClassNamesFromJar( File jarFile )
-        throws IOException
-    {
-        if ( jarFile == null || !jarFile.exists() || !jarFile.isFile() )
-        {
-            throw new IOException( "The jar '" + jarFile + "' doesn't exist or is not a file." );
-        }
-
-        List<String> classes = new ArrayList<>();
-        try ( JarInputStream jarStream = new JarInputStream( new FileInputStream( jarFile ) ) )
-        {
-            for ( JarEntry jarEntry = jarStream.getNextJarEntry(); jarEntry != null; jarEntry =
-                jarStream.getNextJarEntry() )
-            {
-                if ( jarEntry.getName().toLowerCase( Locale.ENGLISH ).endsWith( ".class" ) )
-                {
-                    String name = jarEntry.getName().substring( 0, jarEntry.getName().indexOf( "." ) );
-
-                    classes.add( name.replaceAll( "/", "\\." ) );
-                }
-
-                jarStream.closeEntry();
-            }
-        }
-
-        return classes;
-    }
-
-    /**
-     * @param log could be null
-     * @param invoker not null
-     * @param request not null
-     * @param invokerLog not null
-     * @param goals not null
-     * @param properties could be null
-     * @param mavenOpts could be null
-     * @return the invocation result
-     * @throws MavenInvocationException if any
-     * @since 2.6
-     */
-    private static InvocationResult invoke( Log log, Invoker invoker, InvocationRequest request, File invokerLog,
-                                            List<String> goals, Properties properties, String mavenOpts )
-        throws MavenInvocationException
-    {
-        PrintStream ps;
-        OutputStream os = null;
-        if ( invokerLog != null )
-        {
-            if ( log != null && log.isDebugEnabled() )
-            {
-                log.debug( "Using " + invokerLog.getAbsolutePath() + " to log the invoker" );
-            }
-
-            try
-            {
-                if ( !invokerLog.exists() )
-                {
-                    // noinspection ResultOfMethodCallIgnored
-                    invokerLog.getParentFile().mkdirs();
-                }
-                os = new FileOutputStream( invokerLog );
-                ps = new PrintStream( os, true, "UTF-8" );
-            }
-            catch ( FileNotFoundException e )
-            {
-                if ( log != null && log.isErrorEnabled() )
-                {
-                    log.error( "FileNotFoundException: " + e.getMessage() + ". Using System.out to log the invoker." );
-                }
-                ps = System.out;
-            }
-            catch ( UnsupportedEncodingException e )
-            {
-                if ( log != null && log.isErrorEnabled() )
-                {
-                    log.error( "UnsupportedEncodingException: " + e.getMessage()
-                        + ". Using System.out to log the invoker." );
-                }
-                ps = System.out;
-            }
-        }
-        else
-        {
-            if ( log != null && log.isDebugEnabled() )
-            {
-                log.debug( "Using System.out to log the invoker." );
-            }
-
-            ps = System.out;
-        }
-
-        if ( mavenOpts != null )
-        {
-            request.setMavenOpts( mavenOpts );
-        }
-
-        InvocationOutputHandler outputHandler = new PrintStreamHandler( ps, false );
-        request.setOutputHandler( outputHandler );
-
-        outputHandler.consumeLine( "Invoking Maven for the goals: " + goals + " with "
-            + ( properties == null ? "no properties" : "properties=" + properties ) );
-        outputHandler.consumeLine( "" );
-        outputHandler.consumeLine( "M2_HOME=" + getMavenHome( log ) );
-        outputHandler.consumeLine( "MAVEN_OPTS=" + getMavenOpts( log ) );
-        outputHandler.consumeLine( "JAVA_HOME=" + getJavaHome( log ) );
-        outputHandler.consumeLine( "JAVA_OPTS=" + getJavaOpts( log ) );
-        outputHandler.consumeLine( "" );
-
-        try
-        {
-            return invoker.execute( request );
-        }
-        finally
-        {
-            IOUtil.close( os );
-        }
-    }
-
-    /**
-     * @param log a logger could be null
-     * @return the Maven home defined in the <code>maven.home</code> system property or defined in <code>M2_HOME</code>
-     *         system env variables or null if never set.
-     * @since 2.6
-     */
-    private static String getMavenHome( Log log )
-    {
-        String mavenHome = System.getProperty( "maven.home" );
-        if ( mavenHome == null )
-        {
-            try
-            {
-                mavenHome = CommandLineUtils.getSystemEnvVars().getProperty( "M2_HOME" );
-            }
-            catch ( IOException e )
-            {
-                if ( log != null && log.isDebugEnabled() )
-                {
-                    log.debug( "IOException: " + e.getMessage() );
-                }
-            }
-        }
-
-        File m2Home = new File( mavenHome );
-        if ( !m2Home.exists() )
-        {
-            if ( log != null && log.isErrorEnabled() )
-            {
-                log.error( "Cannot find Maven application directory. Either specify \'maven.home\' system property, or "
-                    + "M2_HOME environment variable." );
-            }
-        }
-
-        return mavenHome;
-    }
-
-    /**
-     * @param log a logger could be null
-     * @return the <code>MAVEN_OPTS</code> env variable value
-     * @since 2.6
-     */
-    private static String getMavenOpts( Log log )
-    {
-        String mavenOpts = null;
-        try
-        {
-            mavenOpts = CommandLineUtils.getSystemEnvVars().getProperty( "MAVEN_OPTS" );
-        }
-        catch ( IOException e )
-        {
-            if ( log != null && log.isDebugEnabled() )
-            {
-                log.debug( "IOException: " + e.getMessage() );
-            }
-        }
-
-        return mavenOpts;
-    }
-
-    /**
-     * @param log a logger could be null
-     * @return the <code>JAVA_HOME</code> from System.getProperty( "java.home" ) By default,
-     *         <code>System.getProperty( "java.home" ) = JRE_HOME</code> and <code>JRE_HOME</code> should be in the
-     *         <code>JDK_HOME</code>
-     * @since 2.6
-     */
-    private static File getJavaHome( Log log )
-    {
-        File javaHome = null;
-
-        String javaHomeValue = null;
-        try
-        {
-            javaHomeValue = CommandLineUtils.getSystemEnvVars().getProperty( "JAVA_HOME" );
-        }
-        catch ( IOException e )
-        {
-            if ( log != null && log.isDebugEnabled() )
-            {
-                log.debug( "IOException: " + e.getMessage() );
-            }
-        }
-
-        // if maven.home is set, we can assume JAVA_HOME must be used for testing
-        if ( System.getProperty( "maven.home" ) == null || javaHomeValue == null )
-        {
-            // JEP220 (Java9) restructured the JRE/JDK runtime image
-            if ( SystemUtils.IS_OS_MAC_OSX || JavaVersion.JAVA_VERSION.isAtLeast( "9" ) )
-            {
-                javaHome = SystemUtils.getJavaHome();
-            }
-            else
-            {
-                javaHome = new File( SystemUtils.getJavaHome(), ".." );
-            }
-        }
-
-        if ( javaHome == null || !javaHome.exists() )
-        {
-            javaHome = new File( javaHomeValue );
-        }
-
-        if ( javaHome == null || !javaHome.exists() )
-        {
-            if ( log != null && log.isErrorEnabled() )
-            {
-                log.error( "Cannot find Java application directory. Either specify \'java.home\' system property, or "
-                    + "JAVA_HOME environment variable." );
-            }
-        }
-
-        return javaHome;
-    }
-
-    /**
-     * @param log a logger could be null
-     * @return the <code>JAVA_OPTS</code> env variable value
-     * @since 2.6
-     */
-    private static String getJavaOpts( Log log )
-    {
-        String javaOpts = null;
-        try
-        {
-            javaOpts = CommandLineUtils.getSystemEnvVars().getProperty( "JAVA_OPTS" );
-        }
-        catch ( IOException e )
-        {
-            if ( log != null && log.isDebugEnabled() )
-            {
-                log.debug( "IOException: " + e.getMessage() );
-            }
-        }
-
-        return javaOpts;
-    }
-
-    /**
-     * A Path tokenizer takes a path and returns the components that make up that path. The path can use path separators
-     * of either ':' or ';' and file separators of either '/' or '\'.
-     *
-     * @version revision 439418 taken on 2009-09-12 from Ant Project (see
-     *          http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/PathTokenizer.java)
-     */
-    private static class PathTokenizer
-    {
-        /**
-         * A tokenizer to break the string up based on the ':' or ';' separators.
-         */
-        private StringTokenizer tokenizer;
-
-        /**
-         * A String which stores any path components which have been read ahead due to DOS filesystem compensation.
-         */
-        private String lookahead = null;
-
-        /**
-         * A boolean that determines if we are running on Novell NetWare, which exhibits slightly different path name
-         * characteristics (multi-character volume / drive names)
-         */
-        private boolean onNetWare = Os.isFamily( "netware" );
-
-        /**
-         * Flag to indicate whether or not we are running on a platform with a DOS style filesystem
-         */
-        private boolean dosStyleFilesystem;
-
-        /**
-         * Constructs a path tokenizer for the specified path.
-         *
-         * @param path The path to tokenize. Must not be <code>null</code>.
-         */
-        PathTokenizer( String path )
-        {
-            if ( onNetWare )
-            {
-                // For NetWare, use the boolean=true mode, so we can use delimiter
-                // information to make a better decision later.
-                tokenizer = new StringTokenizer( path, ":;", true );
-            }
-            else
-            {
-                // on Windows and Unix, we can ignore delimiters and still have
-                // enough information to tokenize correctly.
-                tokenizer = new StringTokenizer( path, ":;", false );
-            }
-            dosStyleFilesystem = File.pathSeparatorChar == ';';
-        }
-
-        /**
-         * Tests if there are more path elements available from this tokenizer's path. If this method returns
-         * <code>true</code>, then a subsequent call to nextToken will successfully return a token.
-         *
-         * @return <code>true</code> if and only if there is at least one token in the string after the current
-         *         position; <code>false</code> otherwise.
-         */
-        public boolean hasMoreTokens()
-        {
-            return lookahead != null || tokenizer.hasMoreTokens();
-
-        }
-
-        /**
-         * Returns the next path element from this tokenizer.
-         *
-         * @return the next path element from this tokenizer.
-         * @exception NoSuchElementException if there are no more elements in this tokenizer's path.
-         */
-        public String nextToken()
-            throws NoSuchElementException
-        {
-            String token;
-            if ( lookahead != null )
-            {
-                token = lookahead;
-                lookahead = null;
-            }
-            else
-            {
-                token = tokenizer.nextToken().trim();
-            }
-
-            if ( !onNetWare )
-            {
-                if ( token.length() == 1 && Character.isLetter( token.charAt( 0 ) ) && dosStyleFilesystem
-                    && tokenizer.hasMoreTokens() )
-                {
-                    // we are on a dos style system so this path could be a drive
-                    // spec. We look at the next token
-                    String nextToken = tokenizer.nextToken().trim();
-                    if ( nextToken.startsWith( "\\" ) || nextToken.startsWith( "/" ) )
-                    {
-                        // we know we are on a DOS style platform and the next path
-                        // starts with a slash or backslash, so we know this is a
-                        // drive spec
-                        token += ":" + nextToken;
-                    }
-                    else
-                    {
-                        // store the token just read for next time
-                        lookahead = nextToken;
-                    }
-                }
-            }
-            else
-            {
-                // we are on NetWare, tokenizing is handled a little differently,
-                // due to the fact that NetWare has multiple-character volume names.
-                if ( token.equals( File.pathSeparator ) || token.equals( ":" ) )
-                {
-                    // ignore ";" and get the next token
-                    token = tokenizer.nextToken().trim();
-                }
-
-                if ( tokenizer.hasMoreTokens() )
-                {
-                    // this path could be a drive spec, so look at the next token
-                    String nextToken = tokenizer.nextToken().trim();
-
-                    // make sure we aren't going to get the path separator next
-                    if ( !nextToken.equals( File.pathSeparator ) )
-                    {
-                        if ( nextToken.equals( ":" ) )
-                        {
-                            if ( !token.startsWith( "/" ) && !token.startsWith( "\\" ) && !token.startsWith( "." )
-                                && !token.startsWith( ".." ) )
-                            {
-                                // it indeed is a drive spec, get the next bit
-                                String oneMore = tokenizer.nextToken().trim();
-                                if ( !oneMore.equals( File.pathSeparator ) )
-                                {
-                                    token += ":" + oneMore;
-                                }
-                                else
-                                {
-                                    token += ":";
-                                    lookahead = oneMore;
-                                }
-                            }
-                            // implicit else: ignore the ':' since we have either a
-                            // UNIX or a relative path
-                        }
-                        else
-                        {
-                            // store the token just read for next time
-                            lookahead = nextToken;
-                        }
-                    }
-                }
-            }
-            return token;
-        }
-    }
-
-    /**
-     * Ignores line like 'Picked up JAVA_TOOL_OPTIONS: ...' as can happen on CI servers.
-     * 
-     * @author Robert Scholte
-     * @since 3.0.1
-     */
-    protected static class JavadocOutputStreamConsumer
-        extends CommandLineUtils.StringStreamConsumer
-    {
-        @Override
-        public void consumeLine( String line )
-        {
-            if ( !line.startsWith( "Picked up " ) )
-            {
-                super.consumeLine( line );
-            }
-        }
-    }
-
-    static List<String> toList( String src )
-    {
-        return toList( src, null, null );
-    }
-
-    static List<String> toList( String src, String elementPrefix, String elementSuffix )
-    {
-        if ( StringUtils.isEmpty( src ) )
-        {
-            return null;
-        }
-
-        List<String> result = new ArrayList<>();
-
-        StringTokenizer st = new StringTokenizer( src, "[,:;]" );
-        StringBuilder sb = new StringBuilder( 256 );
-        while ( st.hasMoreTokens() )
-        {
-            sb.setLength( 0 );
-            if ( StringUtils.isNotEmpty( elementPrefix ) )
-            {
-                sb.append( elementPrefix );
-            }
-
-            sb.append( st.nextToken() );
-
-            if ( StringUtils.isNotEmpty( elementSuffix ) )
-            {
-                sb.append( elementSuffix );
-            }
-
-            result.add( sb.toString() );
-        }
-
-        return result;
-    }
-
-    static <T> List<T> toList( T[] multiple )
-    {
-        return toList( null, multiple );
-    }
-
-    static <T> List<T> toList( T single, T[] multiple )
-    {
-        if ( single == null && ( multiple == null || multiple.length < 1 ) )
-        {
-            return null;
-        }
-
-        List<T> result = new ArrayList<>();
-        if ( single != null )
-        {
-            result.add( single );
-        }
-
-        if ( multiple != null && multiple.length > 0 )
-        {
-            result.addAll( Arrays.asList( multiple ) );
-        }
-
-        return result;
-    }
-
-    // TODO: move to plexus-utils or use something appropriate from there
-    public static String toRelative( File basedir, String absolutePath )
-    {
-        String relative;
-
-        absolutePath = absolutePath.replace( '\\', '/' );
-        String basedirPath = basedir.getAbsolutePath().replace( '\\', '/' );
-
-        if ( absolutePath.startsWith( basedirPath ) )
-        {
-            relative = absolutePath.substring( basedirPath.length() );
-            if ( relative.startsWith( "/" ) )
-            {
-                relative = relative.substring( 1 );
-            }
-            if ( relative.length() <= 0 )
-            {
-                relative = ".";
-            }
-        }
-        else
-        {
-            relative = absolutePath;
-        }
-
-        return relative;
-    }
-
-    /**
-     * Convenience method to determine that a collection is not empty or null.
-     * @param collection the collection to verify
-     * @return {@code true} if not {@code null} and not empty, otherwise {@code false}
-     */
-    public static boolean isNotEmpty( final Collection<?> collection )
-    {
-        return collection != null && !collection.isEmpty();
-    }
-
-    /**
-     * Convenience method to determine that a collection is empty or null.
-     * @param collection the collection to verify
-     * @return {@code true} if {@code null} or empty, otherwise {@code false}
-     */
-    public static boolean isEmpty( final Collection<?> collection )
-    {
-        return collection == null || collection.isEmpty();
-    }
-
-    /**
-     * Execute an Http request at the given URL, follows redirects, and returns the last redirect locations. For URLs
-     * that aren't http/https, this does nothing and simply returns the given URL unchanged.
-     *
-     * @param url URL.
-     * @param settings Maven settings.
-     * @return Last redirect location.
-     * @throws IOException if there was an error during the Http request.
-     */
-    protected static URL getRedirectUrl( URL url, Settings settings )
-        throws IOException
-    {
-        String protocol = url.getProtocol();
-        if ( !"http".equals( protocol ) && !"https".equals( protocol ) )
-        {
-            return url;
-        }
-        HttpClient httpClient = null;
-        try
-        {
-            httpClient = createHttpClient( settings, url );
-            HttpClientContext httpContext = HttpClientContext.create();
-            HttpGet httpMethod = new HttpGet( url.toString() );
-            HttpResponse response = httpClient.execute( httpMethod, httpContext );
-            int status = response.getStatusLine().getStatusCode();
-            if ( status != HttpStatus.SC_OK )
-            {
-                throw new FileNotFoundException( "Unexpected HTTP status code " + status + " getting resource "
-                    + url.toExternalForm() + "." );
-            }
-
-            List<URI> redirects = httpContext.getRedirectLocations();
-            return isEmpty( redirects ) ? url : redirects.get( redirects.size() - 1 ).toURL();
-        }
-        finally
-        {
-            if ( httpClient != null )
-            {
-                httpClient.getConnectionManager().shutdown();
-            }
-        }
-    }
-
-    /**
-     * Validates an <code>URL</code> to point to a valid <code>package-list</code> resource.
-     *
-     * @param url The URL to validate.
-     * @param settings The user settings used to configure the connection to the URL or {@code null}.
-     * @param validateContent <code>true</code> to validate the content of the <code>package-list</code> resource;
-     *            <code>false</code> to only check the existence of the <code>package-list</code> resource.
-     * @return <code>true</code> if <code>url</code> points to a valid <code>package-list</code> resource;
-     *         <code>false</code> else.
-     * @throws IOException if reading the resource fails.
-     * @see #createHttpClient(org.apache.maven.settings.Settings, java.net.URL)
-     * @since 2.8
-     */
-    protected static boolean isValidPackageList( URL url, Settings settings, boolean validateContent )
-        throws IOException
-    {
-        if ( url == null )
-        {
-            throw new IllegalArgumentException( "The url is null" );
-        }
-
-        try ( BufferedReader reader = getReader( url, settings ) )
-        {
-            if ( validateContent )
-            {
-                for ( String line = reader.readLine(); line != null; line = reader.readLine() )
-                {
-                    if ( !isValidPackageName( line ) )
-                    {
-                        return false;
-                    }
-                }
-            }
-            return true;
-        }
-    }
-    
-    protected static boolean isValidElementList( URL url, Settings settings, boolean validateContent )
-                    throws IOException
-    {
-        if ( url == null )
-        {
-            throw new IllegalArgumentException( "The url is null" );
-        }
-
-        try ( BufferedReader reader = getReader( url, settings ) )
-        {
-            if ( validateContent )
-            {
-                for ( String line = reader.readLine(); line != null; line = reader.readLine() )
-                {
-                    if ( line.startsWith( "module:" ) )
-                    {
-                        continue;
-                    }
-                        
-                    if ( !isValidPackageName( line ) )
-                    {
-                        return false;
-                    }
-                }
-            }
-            return true;
-        }
-    }
-    
-    private static BufferedReader getReader( URL url, Settings settings ) throws IOException
-    {
-        BufferedReader reader = null;
-        
-        if ( "file".equals( url.getProtocol() ) )
-        {
-            // Intentionally using the platform default encoding here since this is what Javadoc uses internally.
-            reader = new BufferedReader( new InputStreamReader( url.openStream() ) );
-        }
-        else
-        {
-            // http, https...
-            final HttpClient httpClient = createHttpClient( settings, url );
-
-            final HttpGet httpMethod = new HttpGet( url.toString() );
-            
-            HttpResponse response;
-            try
-            {
-                response = httpClient.execute( httpMethod );
-            }
-            catch ( SocketTimeoutException e )
-            {
-                // could be a sporadic failure, one more retry before we give up
-                response = httpClient.execute( httpMethod );
-            }
-
-            int status = response.getStatusLine().getStatusCode();
-            if ( status != HttpStatus.SC_OK )
-            {
-                throw new FileNotFoundException( "Unexpected HTTP status code " + status + " getting resource "
-                    + url.toExternalForm() + "." );
-            }
-
-            // Intentionally using the platform default encoding here since this is what Javadoc uses internally.
-            reader = new BufferedReader( new InputStreamReader( response.getEntity().getContent() ) ) 
-            {
-                @Override
-                public void close()
-                    throws IOException
-                {
-                    super.close();
-                    
-                    if ( httpMethod != null )
-                    {
-                        httpMethod.releaseConnection();
-                    }
-                    if ( httpClient != null )
-                    {
-                        httpClient.getConnectionManager().shutdown();
-                    }
-                }
-            };
-        }
-        
-        return reader;
-    }
-
-    private static boolean isValidPackageName( String str )
-    {
-        if ( StringUtils.isEmpty( str ) )
-        {
-            // unnamed package is valid (even if bad practice :) )
-            return true;
-        }
-
-        int idx;
-        while ( ( idx = str.indexOf( '.' ) ) != -1 )
-        {
-            if ( !isValidClassName( str.substring( 0, idx ) ) )
-            {
-                return false;
-            }
-
-            str = str.substring( idx + 1 );
-        }
-
-        return isValidClassName( str );
-    }
-
-    private static boolean isValidClassName( String str )
-    {
-        if ( StringUtils.isEmpty( str ) || !Character.isJavaIdentifierStart( str.charAt( 0 ) ) )
-        {
-            return false;
-        }
-
-        for ( int i = str.length() - 1; i > 0; i-- )
-        {
-            if ( !Character.isJavaIdentifierPart( str.charAt( i ) ) )
-            {
-                return false;
-            }
-        }
-
-        return true;
-    }
-
-    /**
-     * Creates a new {@code HttpClient} instance.
-     *
-     * @param settings The settings to use for setting up the client or {@code null}.
-     * @param url The {@code URL} to use for setting up the client or {@code null}.
-     * @return A new {@code HttpClient} instance.
-     * @see #DEFAULT_TIMEOUT
-     * @since 2.8
-     */
-    private static HttpClient createHttpClient( Settings settings, URL url )
-    {
-        DefaultHttpClient httpClient = new DefaultHttpClient( new PoolingClientConnectionManager() );
-        httpClient.getParams().setIntParameter( CoreConnectionPNames.SO_TIMEOUT, DEFAULT_TIMEOUT );
-        httpClient.getParams().setIntParameter( CoreConnectionPNames.CONNECTION_TIMEOUT, DEFAULT_TIMEOUT );
-        httpClient.getParams().setBooleanParameter( ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true );
-
-        // Some web servers don't allow the default user-agent sent by httpClient
-        httpClient.getParams().setParameter( CoreProtocolPNames.USER_AGENT,
-                                             "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" );
-
-        // Some server reject requests that do not have an Accept header
-        httpClient.getParams().setParameter( ClientPNames.DEFAULT_HEADERS,
-                                             Arrays.asList( new BasicHeader( HttpHeaders.ACCEPT, "*/*" ) ) );
-
-        httpClient.getParams().setParameter( ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY );
-
-        if ( settings != null && settings.getActiveProxy() != null )
-        {
-            Proxy activeProxy = settings.getActiveProxy();
-
-            ProxyInfo proxyInfo = new ProxyInfo();
-            proxyInfo.setNonProxyHosts( activeProxy.getNonProxyHosts() );
-
-            if ( StringUtils.isNotEmpty( activeProxy.getHost() )
-                && ( url == null || !ProxyUtils.validateNonProxyHosts( proxyInfo, url.getHost() ) ) )
-            {
-                HttpHost proxy = new HttpHost( activeProxy.getHost(), activeProxy.getPort() );
-                httpClient.getParams().setParameter( ConnRoutePNames.DEFAULT_PROXY, proxy );
-
-                if ( StringUtils.isNotEmpty( activeProxy.getUsername() ) && activeProxy.getPassword() != null )
-                {
-                    Credentials credentials =
-                        new UsernamePasswordCredentials( activeProxy.getUsername(), activeProxy.getPassword() );
-
-                    httpClient.getCredentialsProvider().setCredentials( AuthScope.ANY, credentials );
-                }
-            }
-        }
-
-        return httpClient;
-    }
-
-    static boolean equalsIgnoreCase( String value, String... strings )
-    {
-        for ( String s : strings )
-        {
-            if ( s.equalsIgnoreCase( value ) )
-            {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static boolean equals( String value, String... strings )
-    {
-        for ( String s : strings )
-        {
-            if ( s.equals( value ) )
-            {
-                return true;
-            }
-        }
-        return false;
-    }
-}
+package org.apache.maven.plugins.javadoc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.client.params.CookiePolicy;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.conn.params.ConnRoutePNames;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.PoolingClientConnectionManager;
+import org.apache.http.message.BasicHeader;
+import org.apache.http.params.CoreConnectionPNames;
+import org.apache.http.params.CoreProtocolPNames;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.project.MavenProject;
+import org.apache.maven.settings.Proxy;
+import org.apache.maven.settings.Settings;
+import org.apache.maven.shared.invoker.DefaultInvocationRequest;
+import org.apache.maven.shared.invoker.DefaultInvoker;
+import org.apache.maven.shared.invoker.InvocationOutputHandler;
+import org.apache.maven.shared.invoker.InvocationRequest;
+import org.apache.maven.shared.invoker.InvocationResult;
+import org.apache.maven.shared.invoker.Invoker;
+import org.apache.maven.shared.invoker.MavenInvocationException;
+import org.apache.maven.shared.invoker.PrintStreamHandler;
+import org.apache.maven.wagon.proxy.ProxyInfo;
+import org.apache.maven.wagon.proxy.ProxyUtils;
+import org.codehaus.plexus.languages.java.version.JavaVersion;
+import org.codehaus.plexus.util.DirectoryScanner;
+import org.codehaus.plexus.util.FileUtils;
+import org.codehaus.plexus.util.IOUtil;
+import org.codehaus.plexus.util.Os;
+import org.codehaus.plexus.util.StringUtils;
+import org.codehaus.plexus.util.cli.CommandLineException;
+import org.codehaus.plexus.util.cli.CommandLineUtils;
+import org.codehaus.plexus.util.cli.Commandline;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Modifier;
+import java.net.SocketTimeoutException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.Charset;
+import java.nio.charset.IllegalCharsetNameException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+import java.util.Properties;
+import java.util.Set;
+import java.util.StringTokenizer;
+import java.util.jar.JarEntry;
+import java.util.jar.JarInputStream;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Set of utilities methods for Javadoc.
+ *
+ * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
+ * @since 2.4
+ */
+public class JavadocUtil
+{
+    /** The default timeout used when fetching url, i.e. 2000. */
+    public static final int DEFAULT_TIMEOUT = 2000;
+
+    /** Error message when VM could not be started using invoker. */
+    protected static final String ERROR_INIT_VM =
+        "Error occurred during initialization of VM, try to reduce the Java heap size for the MAVEN_OPTS "
+            + "environment variable using -Xms:<size> and -Xmx:<size>.";
+
+    /**
+     * Method that removes the invalid directories in the specified directories. <b>Note</b>: All elements in
+     * <code>dirs</code> could be an absolute or relative against the project's base directory <code>String</code> path.
+     *
+     * @param project the current Maven project not null
+     * @param dirs the collection of <code>String</code> directories path that will be validated.
+     * @return a List of valid <code>String</code> directories absolute paths.
+     */
+    public static Collection<Path> pruneDirs( MavenProject project, Collection<String> dirs )
+    {
+        final Path projectBasedir = project.getBasedir().toPath();
+
+        Set<Path> pruned = new LinkedHashSet<>( dirs.size() );
+        for ( String dir : dirs )
+        {
+            if ( dir == null )
+            {
+                continue;
+            }
+
+            Path directory = projectBasedir.resolve( dir );
+
+            if ( Files.isDirectory( directory ) )
+            {
+                pruned.add( directory.toAbsolutePath() );
+            }
+        }
+
+        return pruned;
+    }
+
+    /**
+     * Method that removes the invalid files in the specified files. <b>Note</b>: All elements in <code>files</code>
+     * should be an absolute <code>String</code> path.
+     *
+     * @param files the list of <code>String</code> files paths that will be validated.
+     * @return a List of valid <code>File</code> objects.
+     */
+    protected static List<String> pruneFiles( Collection<String> files )
+    {
+        List<String> pruned = new ArrayList<>( files.size() );
+        for ( String f : files )
+        {
+            if ( !shouldPruneFile( f, pruned ) )
+            {
+                pruned.add( f );
+            }
+        }
+
+        return pruned;
+    }
+
+    /**
+     * Determine whether a file should be excluded from the provided list of paths, based on whether it exists and is
+     * already present in the list.
+     *
+     * @param f The files.
+     * @param pruned The list of pruned files..
+     * @return true if the file could be pruned false otherwise.
+     */
+    public static boolean shouldPruneFile( String f, List<String> pruned )
+    {
+        if ( f != null )
+        {
+            File file = new File( f );
+            if ( file.isFile() && ( isEmpty( pruned ) || !pruned.contains( f ) ) )
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Method that gets all the source files to be excluded from the javadoc on the given source paths.
+     *
+     * @param sourcePaths the path to the source files
+     * @param excludedPackages the package names to be excluded in the javadoc
+     * @return a List of the packages to be excluded in the generated javadoc
+     */
+    protected static List<String> getExcludedPackages( Collection<Path> sourcePaths,
+                                                       Collection<String> excludedPackages )
+    {
+        List<String> excludedNames = new ArrayList<>();
+        for ( Path sourcePath : sourcePaths )
+        {
+            excludedNames.addAll( getExcludedPackages( sourcePath, excludedPackages ) );
+        }
+
+        return excludedNames;
+    }
+
+    /**
+     * Convenience method to wrap an argument value in single quotes (i.e. <code>'</code>). Intended for values which
+     * may contain whitespaces. <br>
+     * To prevent javadoc error, the line separator (i.e. <code>\n</code>) are skipped.
+     *
+     * @param value the argument value.
+     * @return argument with quote
+     */
+    protected static String quotedArgument( String value )
+    {
+        String arg = value;
+
+        if ( StringUtils.isNotEmpty( arg ) )
+        {
+            if ( arg.contains( "'" ) )
+            {
+                arg = StringUtils.replace( arg, "'", "\\'" );
+            }
+            arg = "'" + arg + "'";
+
+            // To prevent javadoc error
+            arg = StringUtils.replace( arg, "\n", " " );
+        }
+
+        return arg;
+    }
+
+    /**
+     * Convenience method to format a path argument so that it is properly interpreted by the javadoc tool. Intended for
+     * path values which may contain whitespaces.
+     *
+     * @param value the argument value.
+     * @return path argument with quote
+     */
+    protected static String quotedPathArgument( String value )
+    {
+        String path = value;
+
+        if ( StringUtils.isNotEmpty( path ) )
+        {
+            path = path.replace( '\\', '/' );
+            if ( path.contains( "\'" ) )
+            {
+                String split[] = path.split( "\'" );
+                path = "";
+
+                for ( int i = 0; i < split.length; i++ )
+                {
+                    if ( i != split.length - 1 )
+                    {
+                        path = path + split[i] + "\\'";
+                    }
+                    else
+                    {
+                        path = path + split[i];
+                    }
+                }
+            }
+            path = "'" + path + "'";
+        }
+
+        return path;
+    }
+
+    /**
+     * Convenience method that copy all <code>doc-files</code> directories from <code>javadocDir</code> to the
+     * <code>outputDirectory</code>.
+     *
+     * @param outputDirectory the output directory
+     * @param javadocDir the javadoc directory
+     * @param excludedocfilessubdir the excludedocfilessubdir parameter
+     * @throws IOException if any
+     * @since 2.5
+     */
+    protected static void copyJavadocResources( File outputDirectory, File javadocDir, String excludedocfilessubdir )
+        throws IOException
+    {
+        if ( !javadocDir.isDirectory() )
+        {
+            return;
+        }
+
+        List<String> excludes = new ArrayList<>( Arrays.asList( FileUtils.getDefaultExcludes() ) );
+
+        if ( StringUtils.isNotEmpty( excludedocfilessubdir ) )
+        {
+            StringTokenizer st = new StringTokenizer( excludedocfilessubdir, ":" );
+            String current;
+            while ( st.hasMoreTokens() )
+            {
+                current = st.nextToken();
+                excludes.add( "**/" + current + "/**" );
+            }
+        }
+
+        List<String> docFiles =
+            FileUtils.getDirectoryNames( javadocDir, "resources,**/doc-files",
+                                         StringUtils.join( excludes.iterator(), "," ), false, true );
+        for ( String docFile : docFiles )
+        {
+            File docFileOutput = new File( outputDirectory, docFile );
+            FileUtils.mkdir( docFileOutput.getAbsolutePath() );
+            FileUtils.copyDirectoryStructure( new File( javadocDir, docFile ), docFileOutput );
+            List<String> files =
+                FileUtils.getFileAndDirectoryNames( docFileOutput, StringUtils.join( excludes.iterator(), "," ), null,
+                                                    true, true, true, true );
+            for ( String filename : files )
+            {
+                File file = new File( filename );
+
+                if ( file.isDirectory() )
+                {
+                    FileUtils.deleteDirectory( file );
+                }
+                else
+                {
+                    file.delete();
+                }
+            }
+        }
+    }
+
+    /**
+     * Method that gets the files or classes that would be included in the javadocs using the subpackages parameter.
+     *
+     * @param sourceDirectory the directory where the source files are located
+     * @param fileList the list of all relative files found in the sourceDirectory
+     * @param excludePackages package names to be excluded in the javadoc
+     * @return a StringBuilder that contains the appended file names of the files to be included in the javadoc
+     */
+    protected static List<String> getIncludedFiles( File sourceDirectory, String[] fileList,
+                                                    Collection<String> excludePackages )
+    {
+        List<String> files = new ArrayList<>();
+
+        List<Pattern> excludePackagePatterns = new ArrayList<>( excludePackages.size() );
+        for ( String excludePackage :  excludePackages )
+        {
+            excludePackagePatterns.add( Pattern.compile( excludePackage.replace( '.', File.separatorChar )
+                                                                       .replace( "\\", "\\\\" )
+                                                                       .replace( "*", ".+" )
+                                                                       .concat( "[\\\\/][^\\\\/]+\\.java" )
+                                                                                ) );
+        }
+
+        for ( String file : fileList )
+        {
+            boolean excluded = false;
+            for ( Pattern excludePackagePattern :  excludePackagePatterns )
+            {
+                if ( excludePackagePattern.matcher( file ).matches() )
+                {
+                    excluded = true;
+                    break;
+                }
+            }
+
+            if ( !excluded )
+            {
+                files.add( file );
+            }
+        }
+
+        return files;
+    }
+
+    /**
+     * Method that gets the complete package names (including subpackages) of the packages that were defined in the
+     * excludePackageNames parameter.
+     *
+     * @param sourceDirectory the directory where the source files are located
+     * @param excludePackagenames package names to be excluded in the javadoc
+     * @return a List of the packagenames to be excluded
+     */
+    protected static Collection<String> getExcludedPackages( final Path sourceDirectory,
+                                                             Collection<String> excludePackagenames )
+    {
+        final String regexFileSeparator = File.separator.replace( "\\", "\\\\" );
+
+        final Collection<String> fileList = new ArrayList<>();
+
+        try
+        {
+            Files.walkFileTree( sourceDirectory, new SimpleFileVisitor<Path>()
+            {
+                @Override
+                public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
+                    throws IOException
+                {
+                    if ( file.getFileName().toString().endsWith( ".java" ) )
+                    {
+                        fileList.add( sourceDirectory.relativize( file.getParent() ).toString() );
+                    }
+                    return FileVisitResult.CONTINUE;
+                }
+            } );
+        }
+        catch ( IOException e )
+        {
+            // noop
+        }
+
+        List<String> files = new ArrayList<>();
+        for ( String excludePackagename : excludePackagenames )
+        {
+            // Usage of wildcard was bad specified and bad implemented, i.e. using String.contains()
+            //   without respecting surrounding context
+            // Following implementation should match requirements as defined in the examples:
+            // - A wildcard at the beginning should match 1 or more folders
+            // - Any other wildcard must match exactly one folder
+            Pattern p = Pattern.compile( excludePackagename.replace( ".", regexFileSeparator )
+                                                           .replaceFirst( "^\\*", ".+" )
+                                                           .replace( "*", "[^" + regexFileSeparator + "]+" ) );
+
+            for ( String aFileList : fileList )
+            {
+                if ( p.matcher( aFileList ).matches() )
+                {
+                    files.add( aFileList.replace( File.separatorChar, '.' ) );
+                }
+            }
+        }
+
+        return files;
+    }
+
+    /**
+     * Convenience method that gets the files to be included in the javadoc.
+     *
+     * @param sourceDirectory the directory where the source files are located
+     * @param excludePackages the packages to be excluded in the javadocs
+     * @param sourceFileIncludes files to include.
+     * @param sourceFileExcludes files to exclude.
+     */
+    protected static List<String> getFilesFromSource( File sourceDirectory, List<String> sourceFileIncludes,
+                                                      List<String> sourceFileExcludes,
+                                                      Collection<String> excludePackages )
+    {
+        DirectoryScanner ds = new DirectoryScanner();
+        if ( sourceFileIncludes == null )
+        {
+            sourceFileIncludes = Collections.singletonList( "**/*.java" );
+        }
+        ds.setIncludes( sourceFileIncludes.toArray( new String[sourceFileIncludes.size()] ) );
+        if ( sourceFileExcludes != null && sourceFileExcludes.size() > 0 )
+        {
+            ds.setExcludes( sourceFileExcludes.toArray( new String[sourceFileExcludes.size()] ) );
+        }
+        ds.setBasedir( sourceDirectory );
+        ds.scan();
+
+        String[] fileList = ds.getIncludedFiles();
+
+        List<String> files = new ArrayList<>();
+        if ( fileList.length != 0 )
+        {
+            for ( String includedFile : getIncludedFiles( sourceDirectory, fileList, excludePackages ) )
+            {
+                files.add( includedFile );
+            }
+        }
+
+        return files;
+    }
+
+    /**
+     * Call the Javadoc tool and parse its output to find its version, i.e.:
+     *
+     * <pre>
+     * javadoc.exe( or.sh ) - J - version
+     * </pre>
+     *
+     * @param javadocExe not null file
+     * @return the javadoc version as float
+     * @throws IOException if javadocExe is null, doesn't exist or is not a file
+     * @throws CommandLineException if any
+     * @throws IllegalArgumentException if no output was found in the command line
+     * @throws PatternSyntaxException if the output contains a syntax error in the regular-expression pattern.
+     * @see #extractJavadocVersion(String)
+     */
+    protected static JavaVersion getJavadocVersion( File javadocExe )
+        throws IOException, CommandLineException, IllegalArgumentException
+    {
+        if ( ( javadocExe == null ) || ( !javadocExe.exists() ) || ( !javadocExe.isFile() ) )
+        {
+            throw new IOException( "The javadoc executable '" + javadocExe + "' doesn't exist or is not a file. " );
+        }
+
+        Commandline cmd = new Commandline();
+        cmd.setExecutable( javadocExe.getAbsolutePath() );
+        cmd.setWorkingDirectory( javadocExe.getParentFile() );
+        cmd.createArg().setValue( "-J-version" );
+
+        CommandLineUtils.StringStreamConsumer out = new JavadocOutputStreamConsumer();
+        CommandLineUtils.StringStreamConsumer err = new JavadocOutputStreamConsumer();
+
+        int exitCode = CommandLineUtils.executeCommandLine( cmd, out, err );
+
+        if ( exitCode != 0 )
+        {
+            StringBuilder msg = new StringBuilder( "Exit code: " + exitCode + " - " + err.getOutput() );
+            msg.append( '\n' );
+            msg.append( "Command line was:" + CommandLineUtils.toString( cmd.getCommandline() ) );
+            throw new CommandLineException( msg.toString() );
+        }
+
+        if ( StringUtils.isNotEmpty( err.getOutput() ) )
+        {
+            return JavaVersion.parse( extractJavadocVersion( err.getOutput() ) );
+        }
+        else if ( StringUtils.isNotEmpty( out.getOutput() ) )
+        {
+            return JavaVersion.parse( extractJavadocVersion( out.getOutput() ) );
+        }
+
+        throw new IllegalArgumentException( "No output found from the command line 'javadoc -J-version'" );
+    }
+
+    /**
+     * Parse the output for 'javadoc -J-version' and return the javadoc version recognized. <br>
+     * Here are some output for 'javadoc -J-version' depending the JDK used:
+     * <table summary="Output for 'javadoc -J-version' per JDK">
+     * <tr>
+     * <th>JDK</th>
+     * <th>Output for 'javadoc -J-version'</th>
+     * </tr>
+     * <tr>
+     * <td>Sun 1.4</td>
+     * <td>java full version "1.4.2_12-b03"</td>
+     * </tr>
+     * <tr>
+     * <td>Sun 1.5</td>
+     * <td>java full version "1.5.0_07-164"</td>
+     * </tr>
+     * <tr>
+     * <td>IBM 1.4</td>
+     * <td>javadoc full version "J2RE 1.4.2 IBM Windows 32 build cn1420-20040626"</td>
+     * </tr>
+     * <tr>
+     * <td>IBM 1.5 (French JVM)</td>
+     * <td>javadoc version complète de "J2RE 1.5.0 IBM Windows 32 build pwi32pdev-20070426a"</td>
+     * </tr>
+     * <tr>
+     * <td>FreeBSD 1.5</td>
+     * <td>java full version "diablo-1.5.0-b01"</td>
+     * </tr>
+     * <tr>
+     * <td>BEA jrockit 1.5</td>
+     * <td>java full version "1.5.0_11-b03"</td>
+     * </tr>
+     * </table>
+     *
+     * @param output for 'javadoc -J-version'
+     * @return the version of the javadoc for the output, only digits and dots
+     * @throws PatternSyntaxException if the output doesn't match with the output pattern
+     *             <tt>(?s).*?[^a-zA-Z]([0-9]+\\.?[0-9]*)(\\.([0-9]+))?.*</tt>.
+     * @throws IllegalArgumentException if the output is null
+     */
+    protected static String extractJavadocVersion( String output )
+        throws IllegalArgumentException
+    {
+        if ( StringUtils.isEmpty( output ) )
+        {
+            throw new IllegalArgumentException( "The output could not be null." );
+        }
+
+        Pattern pattern = Pattern.compile( "(?s).*?[^a-zA-Z](([0-9]+\\.?[0-9]*)(\\.[0-9]+)?).*" );
+
+        Matcher matcher = pattern.matcher( output );
+        if ( !matcher.matches() )
+        {
+            throw new PatternSyntaxException( "Unrecognized version of Javadoc: '" + output + "'", pattern.pattern(),
+                                              pattern.toString().length() - 1 );
+        }
+
+        return matcher.group( 1 );
+    }
+
+    /**
+     * Parse a memory string which be used in the JVM arguments <code>-Xms</code> or <code>-Xmx</code>. <br>
+     * Here are some supported memory string depending the JDK used:
+     * <table summary="Memory argument support per JDK">
+     * <tr>
+     * <th>JDK</th>
+     * <th>Memory argument support for <code>-Xms</code> or <code>-Xmx</code></th>
+     * </tr>
+     * <tr>
+     * <td>SUN</td>
+     * <td>1024k | 128m | 1g | 1t</td>
+     * </tr>
+     * <tr>
+     * <td>IBM</td>
+     * <td>1024k | 1024b | 128m | 128mb | 1g | 1gb</td>
+     * </tr>
+     * <tr>
+     * <td>BEA</td>
+     * <td>1024k | 1024kb | 128m | 128mb | 1g | 1gb</td>
+     * </tr>
+     * </table>
+     *
+     * @param memory the memory to be parsed, not null.
+     * @return the memory parsed with a supported unit. If no unit specified in the <code>memory</code> parameter, the
+     *         default unit is <code>m</code>. The units <code>g | gb</code> or <code>t | tb</code> will be converted in
+     *         <code>m</code>.
+     * @throws IllegalArgumentException if the <code>memory</code> parameter is null or doesn't match any pattern.
+     */
+    protected static String parseJavadocMemory( String memory )
+        throws IllegalArgumentException
+    {
+        if ( StringUtils.isEmpty( memory ) )
+        {
+            throw new IllegalArgumentException( "The memory could not be null." );
+        }
+
+        Pattern p = Pattern.compile( "^\\s*(\\d+)\\s*?\\s*$" );
+        Matcher m = p.matcher( memory );
+        if ( m.matches() )
+        {
+            return m.group( 1 ) + "m";
+        }
+
+        p = Pattern.compile( "^\\s*(\\d+)\\s*k(b)?\\s*$", Pattern.CASE_INSENSITIVE );
+        m = p.matcher( memory );
+        if ( m.matches() )
+        {
+            return m.group( 1 ) + "k";
+        }
+
+        p = Pattern.compile( "^\\s*(\\d+)\\s*m(b)?\\s*$", Pattern.CASE_INSENSITIVE );
+        m = p.matcher( memory );
+        if ( m.matches() )
+        {
+            return m.group( 1 ) + "m";
+        }
+
+        p = Pattern.compile( "^\\s*(\\d+)\\s*g(b)?\\s*$", Pattern.CASE_INSENSITIVE );
+        m = p.matcher( memory );
+        if ( m.matches() )
+        {
+            return ( Integer.parseInt( m.group( 1 ) ) * 1024 ) + "m";
+        }
+
+        p = Pattern.compile( "^\\s*(\\d+)\\s*t(b)?\\s*$", Pattern.CASE_INSENSITIVE );
+        m = p.matcher( memory );
+        if ( m.matches() )
+        {
+            return ( Integer.parseInt( m.group( 1 ) ) * 1024 * 1024 ) + "m";
+        }
+
+        throw new IllegalArgumentException( "Could convert not to a memory size: " + memory );
+    }
+
+    /**
+     * Validate if a charset is supported on this platform.
+     *
+     * @param charsetName the charsetName to be check.
+     * @return <code>true</code> if the given charset is supported by the JVM, <code>false</code> otherwise.
+     */
+    protected static boolean validateEncoding( String charsetName )
+    {
+        if ( StringUtils.isEmpty( charsetName ) )
+        {
+            return false;
+        }
+
+        try
+        {
+            return Charset.isSupported( charsetName );
+        }
+        catch ( IllegalCharsetNameException e )
+        {
+            return false;
+        }
+    }
+
+    /**
+     * For security reasons, if an active proxy is defined and needs an authentication by username/password, hide the
+     * proxy password in the command line.
+     *
+     * @param cmdLine a command line, not null
+     * @param settings the user settings
+     * @return the cmdline with '*' for the http.proxyPassword JVM property
+     */
+    protected static String hideProxyPassword( String cmdLine, Settings settings )
+    {
+        if ( cmdLine == null )
+        {
+            throw new IllegalArgumentException( "cmdLine could not be null" );
+        }
+
+        if ( settings == null )
+        {
+            return cmdLine;
+        }
+
+        Proxy activeProxy = settings.getActiveProxy();
+        if ( activeProxy != null && StringUtils.isNotEmpty( activeProxy.getHost() )
+            && StringUtils.isNotEmpty( activeProxy.getUsername() )
+            && StringUtils.isNotEmpty( activeProxy.getPassword() ) )
+        {
+            String pass = "-J-Dhttp.proxyPassword=\"" + activeProxy.getPassword() + "\"";
+            String hidepass =
+                "-J-Dhttp.proxyPassword=\"" + StringUtils.repeat( "*", activeProxy.getPassword().length() ) + "\"";
+
+            return StringUtils.replace( cmdLine, pass, hidepass );
+        }
+
+        return cmdLine;
+    }
+
+    /**
+     * Auto-detect the class names of the implementation of <code>com.sun.tools.doclets.Taglet</code> class from a given
+     * jar file. <br>
+     * <b>Note</b>: <code>JAVA_HOME/lib/tools.jar</code> is a requirement to find
+     * <code>com.sun.tools.doclets.Taglet</code> class.
+     *
+     * @param jarFile not null
+     * @return the list of <code>com.sun.tools.doclets.Taglet</code> class names from a given jarFile.
+     * @throws IOException if jarFile is invalid or not found, or if the <code>JAVA_HOME/lib/tools.jar</code> is not
+     *             found.
+     * @throws ClassNotFoundException if any
+     * @throws NoClassDefFoundError if any
+     */
+    protected static List<String> getTagletClassNames( File jarFile )
+        throws IOException, ClassNotFoundException, NoClassDefFoundError
+    {
+        List<String> classes = getClassNamesFromJar( jarFile );
+        ClassLoader cl;
+
+        // Needed to find com.sun.tools.doclets.Taglet class
+        File tools = new File( System.getProperty( "java.home" ), "../lib/tools.jar" );
+        if ( tools.exists() && tools.isFile() )
+        {
+            cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL(), tools.toURI().toURL() }, null );
+        }
+        else
+        {
+            cl = new URLClassLoader( new URL[] { jarFile.toURI().toURL() }, ClassLoader.getSystemClassLoader() );
+        }
+
+        List<String> tagletClasses = new ArrayList<>();
+
+        Class<?> tagletClass;
+
+        try
+        {
+            tagletClass = cl.loadClass( "com.sun.tools.doclets.Taglet" );
+        }
+        catch ( ClassNotFoundException e )
+        {
+            tagletClass = cl.loadClass( "jdk.javadoc.doclet.Taglet" );
+        }
+
+        for ( String s : classes )
+        {
+            Class<?> c = cl.loadClass( s );
+
+            if ( tagletClass.isAssignableFrom( c ) && !Modifier.isAbstract( c.getModifiers() ) )
+            {
+                tagletClasses.add( c.getName() );
+            }
+        }
+
+        return tagletClasses;
+    }
+
+    /**
+     * Copy the given url to the given file.
+     *
+     * @param url not null url
+     * @param file not null file where the url will be created
+     * @throws IOException if any
+     * @since 2.6
+     */
+    protected static void copyResource( URL url, File file )
+        throws IOException
+    {
+        if ( file == null )
+        {
+            throw new IOException( "The file can't be null." );
+        }
+        if ( url == null )
+        {
+            throw new IOException( "The url could not be null." );
+        }
+
+        FileUtils.copyURLToFile( url, file );
+    }
+
+    /**
+     * Invoke Maven for the given project file with a list of goals and properties, the output will be in the invokerlog
+     * file. <br>
+     * <b>Note</b>: the Maven Home should be defined in the <code>maven.home</code> Java system property or defined in
+     * <code>M2_HOME</code> system env variables.
+     *
+     * @param log a logger could be null.
+     * @param localRepositoryDir the localRepository not null.
+     * @param projectFile a not null project file.
+     * @param goals a not null goals list.
+     * @param properties the properties for the goals, could be null.
+     * @param invokerLog the log file where the invoker will be written, if null using <code>System.out</code>.
+     * @throws MavenInvocationException if any
+     * @since 2.6
+     */
+    protected static void invokeMaven( Log log, File localRepositoryDir, File projectFile, List<String> goals,
+                                       Properties properties, File invokerLog )
+        throws MavenInvocationException
+    {
+        if ( projectFile == null )
+        {
+            throw new IllegalArgumentException( "projectFile should be not null." );
+        }
+        if ( !projectFile.isFile() )
+        {
+            throw new IllegalArgumentException( projectFile.getAbsolutePath() + " is not a file." );
+        }
+        if ( goals == null || goals.size() == 0 )
+        {
+            throw new IllegalArgumentException( "goals should be not empty." );
+        }
+        if ( localRepositoryDir == null || !localRepositoryDir.isDirectory() )
+        {
+            throw new IllegalArgumentException( "localRepositoryDir '" + localRepositoryDir
+                + "' should be a directory." );
+        }
+
+        String mavenHome = getMavenHome( log );
+        if ( StringUtils.isEmpty( mavenHome ) )
+        {
+            String msg = "Could NOT invoke Maven because no Maven Home is defined. You need to have set the M2_HOME "
+                + "system env variable or a maven.home Java system properties.";
+            if ( log != null )
+            {
+                log.error( msg );
+            }
+            else
+            {
+                System.err.println( msg );
+            }
+            return;
+        }
+
+        Invoker invoker = new DefaultInvoker();
+        invoker.setMavenHome( new File( mavenHome ) );
+        invoker.setLocalRepositoryDirectory( localRepositoryDir );
+
+        InvocationRequest request = new DefaultInvocationRequest();
+        request.setBaseDirectory( projectFile.getParentFile() );
+        request.setPomFile( projectFile );
+        request.setBatchMode( true );
+        if ( log != null )
+        {
+            request.setDebug( log.isDebugEnabled() );
+        }
+        else
+        {
+            request.setDebug( true );
+        }
+        request.setGoals( goals );
+        if ( properties != null )
+        {
+            request.setProperties( properties );
+        }
+        File javaHome = getJavaHome( log );
+        if ( javaHome != null )
+        {
+            request.setJavaHome( javaHome );
+        }
+
+        if ( log != null && log.isDebugEnabled() )
+        {
+            log.debug( "Invoking Maven for the goals: " + goals + " with "
+                + ( properties == null ? "no properties" : "properties=" + properties ) );
+        }
+        InvocationResult result = invoke( log, invoker, request, invokerLog, goals, properties, null );
+
+        if ( result.getExitCode() != 0 )
+        {
+            String invokerLogContent = readFile( invokerLog, "UTF-8" );
+
+            // see DefaultMaven
+            if ( invokerLogContent != null && ( !invokerLogContent.contains( "Scanning for projects..." )
+                || invokerLogContent.contains( OutOfMemoryError.class.getName() ) ) )
+            {
+                if ( log != null )
+                {
+                    log.error( "Error occurred during initialization of VM, trying to use an empty MAVEN_OPTS..." );
+
+                    if ( log.isDebugEnabled() )
+                    {
+                        log.debug( "Reinvoking Maven for the goals: " + goals + " with an empty MAVEN_OPTS..." );
+                    }
+                }
+                result = invoke( log, invoker, request, invokerLog, goals, properties, "" );
+            }
+        }
+
+        if ( result.getExitCode() != 0 )
+        {
+            String invokerLogContent = readFile( invokerLog, "UTF-8" );
+
+            // see DefaultMaven
+            if ( invokerLogContent != null && ( !invokerLogContent.contains( "Scanning for projects..." )
+                || invokerLogContent.contains( OutOfMemoryError.class.getName() ) ) )
+            {
+                throw new MavenInvocationException( ERROR_INIT_VM );
+            }
+
+            throw new MavenInvocationException( "Error when invoking Maven, consult the invoker log file: "
+                + invokerLog.getAbsolutePath() );
+        }
+    }
+
+    /**
+     * Read the given file and return the content or null if an IOException occurs.
+     *
+     * @param javaFile not null
+     * @param encoding could be null
+     * @return the content with unified line separator of the given javaFile using the given encoding.
+     * @see FileUtils#fileRead(File, String)
+     * @since 2.6.1
+     */
+    protected static String readFile( final File javaFile, final String encoding )
+    {
+        try
+        {
+            return FileUtils.fileRead( javaFile, encoding );
+        }
+        catch ( IOException e )
+        {
+            return null;
+        }
+    }
+
+    /**
+     * Split the given path with colon and semi-colon, to support Solaris and Windows path. Examples:
+     *
+     * <pre>
+     * splitPath( "/home:/tmp" )     = ["/home", "/tmp"]
+     * splitPath( "/home;/tmp" )     = ["/home", "/tmp"]
+     * splitPath( "C:/home:C:/tmp" ) = ["C:/home", "C:/tmp"]
+     * splitPath( "C:/home;C:/tmp" ) = ["C:/home", "C:/tmp"]
+     * </pre>
+     *
+     * @param path which can contain multiple paths separated with a colon (<code>:</code>) or a semi-colon
+     *            (<code>;</code>), platform independent. Could be null.
+     * @return the path splitted by colon or semi-colon or <code>null</code> if path was <code>null</code>.
+     * @since 2.6.1
+     */
+    protected static String[] splitPath( final String path )
+    {
+        if ( path == null )
+        {
+            return null;
+        }
+
+        List<String> subpaths = new ArrayList<>();
+        PathTokenizer pathTokenizer = new PathTokenizer( path );
+        while ( pathTokenizer.hasMoreTokens() )
+        {
+            subpaths.add( pathTokenizer.nextToken() );
+        }
+
+        return subpaths.toArray( new String[subpaths.size()] );
+    }
+
+    /**
+     * Unify the given path with the current System path separator, to be platform independent. Examples:
+     *
+     * <pre>
+     * unifyPathSeparator( "/home:/tmp" ) = "/home:/tmp" (Solaris box)
+     * unifyPathSeparator( "/home:/tmp" ) = "/home;/tmp" (Windows box)
+     * </pre>
+     *
+     * @param path which can contain multiple paths by separating them with a colon (<code>:</code>) or a semi-colon
+     *            (<code>;</code>), platform independent. Could be null.
+     * @return the same path but separated with the current System path separator or <code>null</code> if path was
+     *         <code>null</code>.
+     * @since 2.6.1
+     * @see #splitPath(String)
+     * @see File#pathSeparator
+     */
+    protected static String unifyPathSeparator( final String path )
+    {
+        if ( path == null )
+        {
+            return null;
+        }
+
+        return StringUtils.join( splitPath( path ), File.pathSeparator );
+    }
+
+    // ----------------------------------------------------------------------
+    // private methods
+    // ----------------------------------------------------------------------
+
+    /**
+     * @param jarFile not null
+     * @return all class names from the given jar file.
+     * @throws IOException if any or if the jarFile is null or doesn't exist.
+     */
+    private static List<String> getClassNamesFromJar( File jarFile )
+        throws IOException
+    {
+        if ( jarFile == null || !jarFile.exists() || !jarFile.isFile() )
+        {
+            throw new IOException( "The jar '" + jarFile + "' doesn't exist or is not a file." );
+        }
+
+        List<String> classes = new ArrayList<>();
+        try ( JarInputStream jarStream = new JarInputStream( new FileInputStream( jarFile ) ) )
+        {
+            for ( JarEntry jarEntry = jarStream.getNextJarEntry(); jarEntry != null; jarEntry =
+                jarStream.getNextJarEntry() )
+            {
+                if ( jarEntry.getName().toLowerCase( Locale.ENGLISH ).endsWith( ".class" ) )
+                {
+                    String name = jarEntry.getName().substring( 0, jarEntry.getName().indexOf( "." ) );
+
+                    classes.add( name.replaceAll( "/", "\\." ) );
+                }
+
+                jarStream.closeEntry();
+            }
+        }
+
+        return classes;
+    }
+
+    /**
+     * @param log could be null
+     * @param invoker not null
+     * @param request not null
+     * @param invokerLog not null
+     * @param goals not null
+     * @param properties could be null
+     * @param mavenOpts could be null
+     * @return the invocation result
+     * @throws MavenInvocationException if any
+     * @since 2.6
+     */
+    private static InvocationResult invoke( Log log, Invoker invoker, InvocationRequest request, File invokerLog,
+                                            List<String> goals, Properties properties, String mavenOpts )
+        throws MavenInvocationException
+    {
+        PrintStream ps;
+        OutputStream os = null;
+        if ( invokerLog != null )
+        {
+            if ( log != null && log.isDebugEnabled() )
+            {
+                log.debug( "Using " + invokerLog.getAbsolutePath() + " to log the invoker" );
+            }
+
+            try
+            {
+                if ( !invokerLog.exists() )
+                {
+                    // noinspection ResultOfMethodCallIgnored
+                    invokerLog.getParentFile().mkdirs();
+                }
+                os = new FileOutputStream( invokerLog );
+                ps = new PrintStream( os, true, "UTF-8" );
+            }
+            catch ( FileNotFoundException e )
+            {
+                if ( log != null && log.isErrorEnabled() )
+                {
+                    log.error( "FileNotFoundException: " + e.getMessage() + ". Using System.out to log the invoker." );
+                }
+                ps = System.out;
+            }
+            catch ( UnsupportedEncodingException e )
+            {
+                if ( log != null && log.isErrorEnabled() )
+                {
+                    log.error( "UnsupportedEncodingException: " + e.getMessage()
+                        + ". Using System.out to log the invoker." );
+                }
+                ps = System.out;
+            }
+        }
+        else
+        {
+            if ( log != null && log.isDebugEnabled() )
+            {
+                log.debug( "Using System.out to log the invoker." );
+            }
+
+            ps = System.out;
+        }
+
+        if ( mavenOpts != null )
+        {
+            request.setMavenOpts( mavenOpts );
+        }
+
+        InvocationOutputHandler outputHandler = new PrintStreamHandler( ps, false );
+        request.setOutputHandler( outputHandler );
+
+        outputHandler.consumeLine( "Invoking Maven for the goals: " + goals + " with "
+            + ( properties == null ? "no properties" : "properties=" + properties ) );
+        outputHandler.consumeLine( "" );
+        outputHandler.consumeLine( "M2_HOME=" + getMavenHome( log ) );
+        outputHandler.consumeLine( "MAVEN_OPTS=" + getMavenOpts( log ) );
+        outputHandler.consumeLine( "JAVA_HOME=" + getJavaHome( log ) );
+        outputHandler.consumeLine( "JAVA_OPTS=" + getJavaOpts( log ) );
+        outputHandler.consumeLine( "" );
+
+        try
+        {
+            return invoker.execute( request );
+        }
+        finally
+        {
+            IOUtil.close( os );
+        }
+    }
+
+    /**
+     * @param log a logger could be null
+     * @return the Maven home defined in the <code>maven.home</code> system property or defined in <code>M2_HOME</code>
+     *         system env variables or null if never set.
+     * @since 2.6
+     */
+    private static String getMavenHome( Log log )
+    {
+        String mavenHome = System.getProperty( "maven.home" );
+        if ( mavenHome == null )
+        {
+            try
+            {
+                mavenHome = CommandLineUtils.getSystemEnvVars().getProperty( "M2_HOME" );
+            }
+            catch ( IOException e )
+            {
+                if ( log != null && log.isDebugEnabled() )
+                {
+                    log.debug( "IOException: " + e.getMessage() );
+                }
+            }
+        }
+
+        File m2Home = new File( mavenHome );
+        if ( !m2Home.exists() )
+        {
+            if ( log != null && log.isErrorEnabled() )
+            {
+                log.error( "Cannot find Maven application directory. Either specify \'maven.home\' system property, or "
+                    + "M2_HOME environment variable." );
+            }
+        }
+
+        return mavenHome;
+    }
+
+    /**
+     * @param log a logger could be null
+     * @return the <code>MAVEN_OPTS</code> env variable value
+     * @since 2.6
+     */
+    private static String getMavenOpts( Log log )
+    {
+        String mavenOpts = null;
+        try
+        {
+            mavenOpts = CommandLineUtils.getSystemEnvVars().getProperty( "MAVEN_OPTS" );
+        }
+        catch ( IOException e )
+        {
+            if ( log != null && log.isDebugEnabled() )
+            {
+                log.debug( "IOException: " + e.getMessage() );
+            }
+        }
+
+        return mavenOpts;
+    }
+
+    /**
+     * @param log a logger could be null
+     * @return the <code>JAVA_HOME</code> from System.getProperty( "java.home" ) By default,
+     *         <code>System.getProperty( "java.home" ) = JRE_HOME</code> and <code>JRE_HOME</code> should be in the
+     *         <code>JDK_HOME</code>
+     * @since 2.6
+     */
+    private static File getJavaHome( Log log )
+    {
+        File javaHome = null;
+
+        String javaHomeValue = null;
+        try
+        {
+            javaHomeValue = CommandLineUtils.getSystemEnvVars().getProperty( "JAVA_HOME" );
+        }
+        catch ( IOException e )
+        {
+            if ( log != null && log.isDebugEnabled() )
+            {
+                log.debug( "IOException: " + e.getMessage() );
+            }
+        }
+
+        // if maven.home is set, we can assume JAVA_HOME must be used for testing
+        if ( System.getProperty( "maven.home" ) == null || javaHomeValue == null )
+        {
+            // JEP220 (Java9) restructured the JRE/JDK runtime image
+            if ( SystemUtils.IS_OS_MAC_OSX || JavaVersion.JAVA_VERSION.isAtLeast( "9" ) )
+            {
+                javaHome = SystemUtils.getJavaHome();
+            }
+            else
+            {
+                javaHome = new File( SystemUtils.getJavaHome(), ".." );
+            }
+        }
+
+        if ( javaHome == null || !javaHome.exists() )
+        {
+            javaHome = new File( javaHomeValue );
+        }
+
+        if ( javaHome == null || !javaHome.exists() )
+        {
+            if ( log != null && log.isErrorEnabled() )
+            {
+                log.error( "Cannot find Java application directory. Either specify \'java.home\' system property, or "
+                    + "JAVA_HOME environment variable." );
+            }
+        }
+
+        return javaHome;
+    }
+
+    /**
+     * @param log a logger could be null
+     * @return the <code>JAVA_OPTS</code> env variable value
+     * @since 2.6
+     */
+    private static String getJavaOpts( Log log )
+    {
+        String javaOpts = null;
+        try
+        {
+            javaOpts = CommandLineUtils.getSystemEnvVars().getProperty( "JAVA_OPTS" );
+        }
+        catch ( IOException e )
+        {
+            if ( log != null && log.isDebugEnabled() )
+            {
+                log.debug( "IOException: " + e.getMessage() );
+            }
+        }
+
+        return javaOpts;
+    }
+
+    /**
+     * A Path tokenizer takes a path and returns the components that make up that path. The path can use path separators
+     * of either ':' or ';' and file separators of either '/' or '\'.
+     *
+     * @version revision 439418 taken on 2009-09-12 from Ant Project (see
+     *          http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/PathTokenizer.java)
+     */
+    private static class PathTokenizer
+    {
+        /**
+         * A tokenizer to break the string up based on the ':' or ';' separators.
+         */
+        private StringTokenizer tokenizer;
+
+        /**
+         * A String which stores any path components which have been read ahead due to DOS filesystem compensation.
+         */
+        private String lookahead = null;
+
+        /**
+         * A boolean that determines if we are running on Novell NetWare, which exhibits slightly different path name
+         * characteristics (multi-character volume / drive names)
+         */
+        private boolean onNetWare = Os.isFamily( "netware" );
+
+        /**
+         * Flag to indicate whether or not we are running on a platform with a DOS style filesystem
+         */
+        private boolean dosStyleFilesystem;
+
+        /**
+         * Constructs a path tokenizer for the specified path.
+         *
+         * @param path The path to tokenize. Must not be <code>null</code>.
+         */
+        PathTokenizer( String path )
+        {
+            if ( onNetWare )
+            {
+                // For NetWare, use the boolean=true mode, so we can use delimiter
+                // information to make a better decision later.
+                tokenizer = new StringTokenizer( path, ":;", true );
+            }
+            else
+            {
+                // on Windows and Unix, we can ignore delimiters and still have
+                // enough information to tokenize correctly.
+                tokenizer = new StringTokenizer( path, ":;", false );
+            }
+            dosStyleFilesystem = File.pathSeparatorChar == ';';
+        }
+
+        /**
+         * Tests if there are more path elements available from this tokenizer's path. If this method returns
+         * <code>true</code>, then a subsequent call to nextToken will successfully return a token.
+         *
+         * @return <code>true</code> if and only if there is at least one token in the string after the current
+         *         position; <code>false</code> otherwise.
+         */
+        public boolean hasMoreTokens()
+        {
+            return lookahead != null || tokenizer.hasMoreTokens();
+
+        }
+
+        /**
+         * Returns the next path element from this tokenizer.
+         *
+         * @return the next path element from this tokenizer.
+         * @exception NoSuchElementException if there are no more elements in this tokenizer's path.
+         */
+        public String nextToken()
+            throws NoSuchElementException
+        {
+            String token;
+            if ( lookahead != null )
+            {
+                token = lookahead;
+                lookahead = null;
+            }
+            else
+            {
+                token = tokenizer.nextToken().trim();
+            }
+
+            if ( !onNetWare )
+            {
+                if ( token.length() == 1 && Character.isLetter( token.charAt( 0 ) ) && dosStyleFilesystem
+                    && tokenizer.hasMoreTokens() )
+                {
+                    // we are on a dos style system so this path could be a drive
+                    // spec. We look at the next token
+                    String nextToken = tokenizer.nextToken().trim();
+                    if ( nextToken.startsWith( "\\" ) || nextToken.startsWith( "/" ) )
+                    {
+                        // we know we are on a DOS style platform and the next path
+                        // starts with a slash or backslash, so we know this is a
+                        // drive spec
+                        token += ":" + nextToken;
+                    }
+                    else
+                    {
+                        // store the token just read for next time
+                        lookahead = nextToken;
+                    }
+                }
+            }
+            else
+            {
+                // we are on NetWare, tokenizing is handled a little differently,
+                // due to the fact that NetWare has multiple-character volume names.
+                if ( token.equals( File.pathSeparator ) || token.equals( ":" ) )
+                {
+                    // ignore ";" and get the next token
+                    token = tokenizer.nextToken().trim();
+                }
+
+                if ( tokenizer.hasMoreTokens() )
+                {
+                    // this path could be a drive spec, so look at the next token
+                    String nextToken = tokenizer.nextToken().trim();
+
+                    // make sure we aren't going to get the path separator next
+                    if ( !nextToken.equals( File.pathSeparator ) )
+                    {
+                        if ( nextToken.equals( ":" ) )
+                        {
+                            if ( !token.startsWith( "/" ) && !token.startsWith( "\\" ) && !token.startsWith( "." )
+                                && !token.startsWith( ".." ) )
+                            {
+                                // it indeed is a drive spec, get the next bit
+                                String oneMore = tokenizer.nextToken().trim();
+                                if ( !oneMore.equals( File.pathSeparator ) )
+                                {
+                                    token += ":" + oneMore;
+                                }
+                                else
+                                {
+                                    token += ":";
+                                    lookahead = oneMore;
+                                }
+                            }
+                            // implicit else: ignore the ':' since we have either a
+                            // UNIX or a relative path
+                        }
+                        else
+                        {
+                            // store the token just read for next time
+                            lookahead = nextToken;
+                        }
+                    }
+                }
+            }
+            return token;
+        }
+    }
+
+    /**
+     * Ignores line like 'Picked up JAVA_TOOL_OPTIONS: ...' as can happen on CI servers.
+     *
+     * @author Robert Scholte
+     * @since 3.0.1
+     */
+    protected static class JavadocOutputStreamConsumer
+        extends CommandLineUtils.StringStreamConsumer
+    {
+        @Override
+        public void consumeLine( String line )
+        {
+            if ( !line.startsWith( "Picked up " ) )
+            {
+                super.consumeLine( line );
+            }
+        }
+    }
+
+    static List<String> toList( String src )
+    {
+        return toList( src, null, null );
+    }
+
+    static List<String> toList( String src, String elementPrefix, String elementSuffix )
+    {
+        if ( StringUtils.isEmpty( src ) )
+        {
+            return null;
+        }
+
+        List<String> result = new ArrayList<>();
+
+        StringTokenizer st = new StringTokenizer( src, "[,:;]" );
+        StringBuilder sb = new StringBuilder( 256 );
+        while ( st.hasMoreTokens() )
+        {
+            sb.setLength( 0 );
+            if ( StringUtils.isNotEmpty( elementPrefix ) )
+            {
+                sb.append( elementPrefix );
+            }
+
+            sb.append( st.nextToken() );
+
+            if ( StringUtils.isNotEmpty( elementSuffix ) )
+            {
+                sb.append( elementSuffix );
+            }
+
+            result.add( sb.toString() );
+        }
+
+        return result;
+    }
+
+    static <T> List<T> toList( T[] multiple )
+    {
+        return toList( null, multiple );
+    }
+
+    static <T> List<T> toList( T single, T[] multiple )
+    {
+        if ( single == null && ( multiple == null || multiple.length < 1 ) )
+        {
+            return null;
+        }
+
+        List<T> result = new ArrayList<>();
+        if ( single != null )
+        {
+            result.add( single );
+        }
+
+        if ( multiple != null && multiple.length > 0 )
+        {
+            result.addAll( Arrays.asList( multiple ) );
+        }
+
+        return result;
+    }
+
+    // TODO: move to plexus-utils or use something appropriate from there
+    public static String toRelative( File basedir, String absolutePath )
+    {
+        String relative;
+
+        absolutePath = absolutePath.replace( '\\', '/' );
+        String basedirPath = basedir.getAbsolutePath().replace( '\\', '/' );
+
+        if ( absolutePath.startsWith( basedirPath ) )
+        {
+            relative = absolutePath.substring( basedirPath.length() );
+            if ( relative.startsWith( "/" ) )
+            {
+                relative = relative.substring( 1 );
+            }
+            if ( relative.length() <= 0 )
+            {
+                relative = ".";
+            }
+        }
+        else
+        {
+            relative = absolutePath;
+        }
+
+        return relative;
+    }
+
+    /**
+     * Convenience method to determine that a collection is not empty or null.
+     * @param collection the collection to verify
+     * @return {@code true} if not {@code null} and not empty, otherwise {@code false}
+     */
+    public static boolean isNotEmpty( final Collection<?> collection )
+    {
+        return collection != null && !collection.isEmpty();
+    }
+
+    /**
+     * Convenience method to determine that a collection is empty or null.
+     * @param collection the collection to verify
+     * @return {@code true} if {@code null} or empty, otherwise {@code false}
+     */
+    public static boolean isEmpty( final Collection<?> collection )
+    {
+        return collection == null || collection.isEmpty();
+    }
+
+    /**
+     * Execute an Http request at the given URL, follows redirects, and returns the last redirect locations. For URLs
+     * that aren't http/https, this does nothing and simply returns the given URL unchanged.
+     *
+     * @param url URL.
+     * @param settings Maven settings.
+     * @return Last redirect location.
+     * @throws IOException if there was an error during the Http request.
+     */
+    protected static URL getRedirectUrl( URL url, Settings settings )
+        throws IOException
+    {
+        String protocol = url.getProtocol();
+        if ( !"http".equals( protocol ) && !"https".equals( protocol ) )
+        {
+            return url;
+        }
+        HttpClient httpClient = null;
+        try
+        {
+            httpClient = createHttpClient( settings, url );
+            HttpClientContext httpContext = HttpClientContext.create();
+            HttpGet httpMethod = new HttpGet( url.toString() );
+            HttpResponse response = httpClient.execute( httpMethod, httpContext );
+            int status = response.getStatusLine().getStatusCode();
+            if ( status != HttpStatus.SC_OK )
+            {
+                throw new FileNotFoundException( "Unexpected HTTP status code " + status + " getting resource "
+                    + url.toExternalForm() + "." );
+            }
+
+            List<URI> redirects = httpContext.getRedirectLocations();
+            return isEmpty( redirects ) ? url : redirects.get( redirects.size() - 1 ).toURL();
+        }
+        finally
+        {
+            if ( httpClient != null )
+            {
+                httpClient.getConnectionManager().shutdown();
+            }
+        }
+    }
+
+    /**
+     * Validates an <code>URL</code> to point to a valid <code>package-list</code> resource.
+     *
+     * @param url The URL to validate.
+     * @param settings The user settings used to configure the connection to the URL or {@code null}.
+     * @param validateContent <code>true</code> to validate the content of the <code>package-list</code> resource;
+     *            <code>false</code> to only check the existence of the <code>package-list</code> resource.
+     * @return <code>true</code> if <code>url</code> points to a valid <code>package-list</code> resource;
+     *         <code>false</code> else.
+     * @throws IOException if reading the resource fails.
+     * @see #createHttpClient(org.apache.maven.settings.Settings, java.net.URL)
+     * @since 2.8
+     */
+    protected static boolean isValidPackageList( URL url, Settings settings, boolean validateContent )
+        throws IOException
+    {
+        if ( url == null )
+        {
+            throw new IllegalArgumentException( "The url is null" );
+        }
+
+        try ( BufferedReader reader = getReader( url, settings ) )
+        {
+            if ( validateContent )
+            {
+                for ( String line = reader.readLine(); line != null; line = reader.readLine() )
+                {
+                    if ( !isValidPackageName( line ) )
+                    {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    protected static boolean isValidElementList( URL url, Settings settings, boolean validateContent )
+                    throws IOException
+    {
+        if ( url == null )
+        {
+            throw new IllegalArgumentException( "The url is null" );
+        }
+
+        try ( BufferedReader reader = getReader( url, settings ) )
+        {
+            if ( validateContent )
+            {
+                for ( String line = reader.readLine(); line != null; line = reader.readLine() )
+                {
+                    if ( line.startsWith( "module:" ) )
+                    {
+                        continue;
+                    }
+
+                    if ( !isValidPackageName( line ) )
+                    {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    private static BufferedReader getReader( URL url, Settings settings ) throws IOException
+    {
+        BufferedReader reader = null;
+
+        if ( "file".equals( url.getProtocol() ) )
+        {
+            // Intentionally using the platform default encoding here since this is what Javadoc uses internally.
+            reader = new BufferedReader( new InputStreamReader( url.openStream() ) );
+        }
+        else
+        {
+            // http, https...
+            final HttpClient httpClient = createHttpClient( settings, url );
+
+            final HttpGet httpMethod = new HttpGet( url.toString() );
+
+            HttpResponse response;
+            HttpClientContext httpContext = HttpClientContext.create();
+            try
+            {
+                response = httpClient.execute( httpMethod, httpContext );
+            }
+            catch ( SocketTimeoutException e )
+            {
+                // could be a sporadic failure, one more retry before we give up
+                response = httpClient.execute( httpMethod, httpContext );
+            }
+
+            int status = response.getStatusLine().getStatusCode();
+            if ( status != HttpStatus.SC_OK )
+            {
+                throw new FileNotFoundException( "Unexpected HTTP status code " + status + " getting resource "
+                    + url.toExternalForm() + "." );
+            }
+            else
+            {
+                int pos = url.getPath().lastIndexOf( '/' );
+                List<URI> redirects = httpContext.getRedirectLocations();
+                if ( pos >= 0 && isNotEmpty( redirects ) )
+                {
+                    URI location = redirects.get( redirects.size() - 1 );
+                    String suffix = url.getPath().substring( pos );
+                    // Redirections shall point to the same file, e.g. /package-list
+                    if ( !location.getPath().endsWith( suffix ) )
+                    {
+                        throw new FileNotFoundException( url.toExternalForm() + " redirects to "
+                                + location.toURL().toExternalForm() + "." );
+                    }
+                }
+            }
+
+            // Intentionally using the platform default encoding here since this is what Javadoc uses internally.
+            reader = new BufferedReader( new InputStreamReader( response.getEntity().getContent() ) )
+            {
+                @Override
+                public void close()
+                    throws IOException
+                {
+                    super.close();
+
+                    if ( httpMethod != null )
+                    {
+                        httpMethod.releaseConnection();
+                    }
+                    if ( httpClient != null )
+                    {
+                        httpClient.getConnectionManager().shutdown();
+                    }
+                }
+            };
+        }
+
+        return reader;
+    }
+
+    private static boolean isValidPackageName( String str )
+    {
+        if ( StringUtils.isEmpty( str ) )
+        {
+            // unnamed package is valid (even if bad practice :) )
+            return true;
+        }
+
+        int idx;
+        while ( ( idx = str.indexOf( '.' ) ) != -1 )
+        {
+            if ( !isValidClassName( str.substring( 0, idx ) ) )
+            {
+                return false;
+            }
+
+            str = str.substring( idx + 1 );
+        }
+
+        return isValidClassName( str );
+    }
+
+    private static boolean isValidClassName( String str )
+    {
+        if ( StringUtils.isEmpty( str ) || !Character.isJavaIdentifierStart( str.charAt( 0 ) ) )
+        {
+            return false;
+        }
+
+        for ( int i = str.length() - 1; i > 0; i-- )
+        {
+            if ( !Character.isJavaIdentifierPart( str.charAt( i ) ) )
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * Creates a new {@code HttpClient} instance.
+     *
+     * @param settings The settings to use for setting up the client or {@code null}.
+     * @param url The {@code URL} to use for setting up the client or {@code null}.
+     * @return A new {@code HttpClient} instance.
+     * @see #DEFAULT_TIMEOUT
+     * @since 2.8
+     */
+    private static HttpClient createHttpClient( Settings settings, URL url )
+    {
+        DefaultHttpClient httpClient = new DefaultHttpClient( new PoolingClientConnectionManager() );
+        httpClient.getParams().setIntParameter( CoreConnectionPNames.SO_TIMEOUT, DEFAULT_TIMEOUT );
+        httpClient.getParams().setIntParameter( CoreConnectionPNames.CONNECTION_TIMEOUT, DEFAULT_TIMEOUT );
+        httpClient.getParams().setBooleanParameter( ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true );
+
+        // Some web servers don't allow the default user-agent sent by httpClient
+        httpClient.getParams().setParameter( CoreProtocolPNames.USER_AGENT,
+                                             "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)" );
+
+        // Some server reject requests that do not have an Accept header
+        httpClient.getParams().setParameter( ClientPNames.DEFAULT_HEADERS,
+                                             Arrays.asList( new BasicHeader( HttpHeaders.ACCEPT, "*/*" ) ) );
+
+        httpClient.getParams().setParameter( ClientPNames.COOKIE_POLICY, CookiePolicy.BROWSER_COMPATIBILITY );
+
+        if ( settings != null && settings.getActiveProxy() != null )
+        {
+            Proxy activeProxy = settings.getActiveProxy();
+
+            ProxyInfo proxyInfo = new ProxyInfo();
+            proxyInfo.setNonProxyHosts( activeProxy.getNonProxyHosts() );
+
+            if ( StringUtils.isNotEmpty( activeProxy.getHost() )
+                && ( url == null || !ProxyUtils.validateNonProxyHosts( proxyInfo, url.getHost() ) ) )
+            {
+                HttpHost proxy = new HttpHost( activeProxy.getHost(), activeProxy.getPort() );
+                httpClient.getParams().setParameter( ConnRoutePNames.DEFAULT_PROXY, proxy );
+
+                if ( StringUtils.isNotEmpty( activeProxy.getUsername() ) && activeProxy.getPassword() != null )
+                {
+                    Credentials credentials =
+                        new UsernamePasswordCredentials( activeProxy.getUsername(), activeProxy.getPassword() );
+
+                    httpClient.getCredentialsProvider().setCredentials( AuthScope.ANY, credentials );
+                }
+            }
+        }
+
+        return httpClient;
+    }
+
+    static boolean equalsIgnoreCase( String value, String... strings )
+    {
+        for ( String s : strings )
+        {
+            if ( s.equalsIgnoreCase( value ) )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    static boolean equals( String value, String... strings )
+    {
+        for ( String s : strings )
+        {
+            if ( s.equals( value ) )
+            {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/test/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojoTest.java b/src/test/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojoTest.java
index 56988f7..da66559 100644
--- a/src/test/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojoTest.java
+++ b/src/test/java/org/apache/maven/plugins/javadoc/AbstractJavadocMojoTest.java
@@ -1,79 +1,90 @@
-package org.apache.maven.plugins.javadoc;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *  http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import java.io.File;
-
-import org.apache.maven.plugin.MojoExecutionException;
-import org.apache.maven.plugin.MojoFailureException;
-import org.apache.maven.plugin.logging.Log;
-import org.apache.maven.plugins.javadoc.AbstractJavadocMojo;
-
-import junit.framework.TestCase;
-
-public class AbstractJavadocMojoTest
-    extends TestCase
-{
-    AbstractJavadocMojo mojo;
-    
-    @Override
-    protected void setUp()
-        throws Exception
-    {
-        super.setUp();
-        mojo = new AbstractJavadocMojo()
-        {
-            @Override
-            public void doExecute()
-                throws MojoExecutionException, MojoFailureException
-            {
-            }
-        };
-    }
-    
-    public void testMJAVADOC432_DetectLinksMessages()
-    {
-        Log log = mock( Log.class );
-        when( log.isErrorEnabled() ).thenReturn( true );
-        mojo.setLog( log );
-        mojo.outputDirectory = new File( "target/test-classes" );
-
-        // first continues after warning, next exits with warning
-        assertFalse( mojo.isValidJavadocLink( new File( "pom.xml" ).getPath(), true ) );
-        assertFalse( mojo.isValidJavadocLink( "file://%%", true ) );
-        assertFalse( mojo.isValidJavadocLink( new File( "pom.xml" ).toURI().toString(), true ) );
-        verify( log, times( 4 ) ).warn( anyString() );
-        verify( log, never() ).error( anyString() );
-
-        // first continues after error, next exits with error
-        assertFalse( mojo.isValidJavadocLink( new File( "pom.xml" ).getPath(), false ) );
-        assertFalse( mojo.isValidJavadocLink( "file://%%", false ) );
-        assertFalse( mojo.isValidJavadocLink( new File( "pom.xml" ).toURI().toString(), false ) );
-        verify( log, times( 4 ) ).error( anyString() );
-        verify( log, times( 4 ) ).warn( anyString() ); // no extra warnings
-    }
-}
+package org.apache.maven.plugins.javadoc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugin.logging.Log;
+import org.apache.maven.plugins.javadoc.AbstractJavadocMojo;
+
+import junit.framework.TestCase;
+
+public class AbstractJavadocMojoTest
+    extends TestCase
+{
+    AbstractJavadocMojo mojo;
+
+    @Override
+    protected void setUp()
+        throws Exception
+    {
+        super.setUp();
+        mojo = new AbstractJavadocMojo()
+        {
+            @Override
+            public void doExecute()
+                throws MojoExecutionException, MojoFailureException
+            {
+            }
+        };
+    }
+
+    public void testMJAVADOC432_DetectLinksMessages()
+    {
+        Log log = mock( Log.class );
+        when( log.isErrorEnabled() ).thenReturn( true );
+        mojo.setLog( log );
+        mojo.outputDirectory = new File( "target/test-classes" );
+
+        // first continues after warning, next exits with warning
+        assertFalse( mojo.isValidJavadocLink( new File( "pom.xml" ).getPath(), true ) );
+        assertFalse( mojo.isValidJavadocLink( "file://%%", true ) );
+        assertFalse( mojo.isValidJavadocLink( new File( "pom.xml" ).toURI().toString(), true ) );
+        verify( log, times( 4 ) ).warn( anyString() );
+        verify( log, never() ).error( anyString() );
+
+        // first continues after error, next exits with error
+        assertFalse( mojo.isValidJavadocLink( new File( "pom.xml" ).getPath(), false ) );
+        assertFalse( mojo.isValidJavadocLink( "file://%%", false ) );
+        assertFalse( mojo.isValidJavadocLink( new File( "pom.xml" ).toURI().toString(), false ) );
+        verify( log, times( 4 ) ).error( anyString() );
+        verify( log, times( 4 ) ).warn( anyString() ); // no extra warnings
+    }
+
+    public void testMJAVADOC527_DetectLinksRecursion()
+    {
+        Log log = mock( Log.class );
+        when( log.isErrorEnabled() ).thenReturn( true );
+        mojo.setLog( log );
+        mojo.outputDirectory = new File( "target/test-classes" );
+
+        assertFalse( mojo.isValidJavadocLink( "http://javamail.java.net/mailapi/apidocs", false ) );
+        assertTrue( mojo.isValidJavadocLink( "http://commons.apache.org/proper/commons-lang/apidocs", false ) );
+    }
+}