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 ) );
+ }
+}