All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.maven.plugins.javadoc.JavadocUtil Maven / Gradle / Ivy

Go to download

The Maven Javadoc Plugin is a plugin that uses the javadoc tool for generating javadocs for the specified project.

There is a newer version: 3.11.1
Show newest version
/*
 * 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.
 */
package org.apache.maven.plugins.javadoc;

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.URISyntaxException;
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.Paths;
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.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;

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.CredentialsProvider;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
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;

/**
 * Set of utilities methods for Javadoc.
 *
 * @author Vincent Siveton
 * @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: and -Xmx:.";

    /**
     * Method that removes the invalid directories in the specified directories. Note: All elements in
     * dirs could be an absolute or relative against the project's base directory String path.
     *
     * @param project the current Maven project not null
     * @param dirs the collection of String directories path that will be validated.
     * @return a List of valid String directories absolute paths.
     */
    public static Collection pruneDirs(MavenProject project, Collection dirs) {
        final Path projectBasedir = project.getBasedir().toPath();

        Set 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. Note: All elements in files
     * should be an absolute String path.
     *
     * @param files the list of String files paths that will be validated.
     * @return a List of valid File objects.
     */
    protected static List pruneFiles(Collection files) {
        List 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 pruned) {
        if (f != null) {
            if (Files.isRegularFile(Paths.get(f)) && !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 getExcludedPackages(
            Collection sourcePaths, Collection excludedPackages) {
        List 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. '). Intended for values which
     * may contain whitespaces. 
* To prevent javadoc error, the line separator (i.e. \n) 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("'")) { StringBuilder pathBuilder = new StringBuilder(); pathBuilder.append('\''); String[] split = path.split("'"); for (int i = 0; i < split.length; i++) { if (i != split.length - 1) { pathBuilder.append(split[i]).append("\\'"); } else { pathBuilder.append(split[i]); } } pathBuilder.append('\''); path = pathBuilder.toString(); } else { path = "'" + path + "'"; } } return path; } /** * Convenience method that copy all doc-files directories from javadocDir to the * outputDirectory. * * @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 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 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 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 getIncludedFiles( File sourceDirectory, String[] fileList, Collection excludePackages) { List files = new ArrayList<>(); List 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.replace('\\', '/')); } } 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 getExcludedPackages( final Path sourceDirectory, Collection excludePackagenames) { final String regexFileSeparator = File.separator.replace("\\", "\\\\"); final Collection fileList = new ArrayList<>(); try { Files.walkFileTree(sourceDirectory, new SimpleFileVisitor() { @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 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 getFilesFromSource( File sourceDirectory, List sourceFileIncludes, List sourceFileExcludes, Collection 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 files = new ArrayList<>(); if (fileList.length != 0) { files.addAll(getIncludedFiles(sourceDirectory, fileList, excludePackages)); } return files; } /** * Call the Javadoc tool and parse its output to find its version, i.e.: * *
     * javadoc.exe( or.sh ) - J - version
     * 
* * @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:").append(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'"); } private static final Pattern EXTRACT_JAVADOC_VERSION_PATTERN = Pattern.compile("(?s).*?[^a-zA-Z](([0-9]+\\.?[0-9]*)(\\.[0-9]+)?).*"); /** * Parse the output for 'javadoc -J-version' and return the javadoc version recognized.
* Here are some output for 'javadoc -J-version' depending the JDK used: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Output for 'javadoc -J-version' per JDK
JDKOutput for 'javadoc -J-version'
Sun 1.4java full version "1.4.2_12-b03"
Sun 1.5java full version "1.5.0_07-164"
IBM 1.4javadoc full version "J2RE 1.4.2 IBM Windows 32 build cn1420-20040626"
IBM 1.5 (French JVM)javadoc version complète de "J2RE 1.5.0 IBM Windows 32 build pwi32pdev-20070426a"
FreeBSD 1.5java full version "diablo-1.5.0-b01"
BEA jrockit 1.5java full version "1.5.0_11-b03"
* * @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 * {@code (?s).*?[^a-zA-Z]([0-9]+\\.?[0-9]*)(\\.([0-9]+))?.*}. * @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 = EXTRACT_JAVADOC_VERSION_PATTERN; 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); } private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_0 = Pattern.compile("^\\s*(\\d+)\\s*?\\s*$"); private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_1 = Pattern.compile("^\\s*(\\d+)\\s*k(b)?\\s*$", Pattern.CASE_INSENSITIVE); private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_2 = Pattern.compile("^\\s*(\\d+)\\s*m(b)?\\s*$", Pattern.CASE_INSENSITIVE); private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_3 = Pattern.compile("^\\s*(\\d+)\\s*g(b)?\\s*$", Pattern.CASE_INSENSITIVE); private static final Pattern PARSE_JAVADOC_MEMORY_PATTERN_4 = Pattern.compile("^\\s*(\\d+)\\s*t(b)?\\s*$", Pattern.CASE_INSENSITIVE); /** * Parse a memory string which be used in the JVM arguments -Xms or -Xmx.
* Here are some supported memory string depending the JDK used: * * * * * * * * * * * * * * * * * *
Memory argument support per JDK
JDKMemory argument support for -Xms or -Xmx
SUN1024k | 128m | 1g | 1t
IBM1024k | 1024b | 128m | 128mb | 1g | 1gb
BEA1024k | 1024kb | 128m | 128mb | 1g | 1gb
* * @param memory the memory to be parsed, not null. * @return the memory parsed with a supported unit. If no unit specified in the memory parameter, the * default unit is m. The units g | gb or t | tb will be converted in * m. * @throws IllegalArgumentException if the memory 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."); } Matcher m0 = PARSE_JAVADOC_MEMORY_PATTERN_0.matcher(memory); if (m0.matches()) { return m0.group(1) + "m"; } Matcher m1 = PARSE_JAVADOC_MEMORY_PATTERN_1.matcher(memory); if (m1.matches()) { return m1.group(1) + "k"; } Matcher m2 = PARSE_JAVADOC_MEMORY_PATTERN_2.matcher(memory); if (m2.matches()) { return m2.group(1) + "m"; } Matcher m3 = PARSE_JAVADOC_MEMORY_PATTERN_3.matcher(memory); if (m3.matches()) { return (Integer.parseInt(m3.group(1)) * 1024) + "m"; } Matcher m4 = PARSE_JAVADOC_MEMORY_PATTERN_4.matcher(memory); if (m4.matches()) { return (Integer.parseInt(m4.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 true if the given charset is supported by the JVM, false otherwise. */ protected static boolean validateEncoding(String charsetName) { if (StringUtils.isEmpty(charsetName)) { return false; } try { return Charset.isSupported(charsetName); } catch (IllegalCharsetNameException e) { return false; } } /** * Auto-detect the class names of the implementation of com.sun.tools.doclets.Taglet class from a given * jar file.
* Note: JAVA_HOME/lib/tools.jar is a requirement to find * com.sun.tools.doclets.Taglet class. * * @param jarFile not null * @return the list of com.sun.tools.doclets.Taglet class names from a given jarFile. * @throws IOException if jarFile is invalid or not found, or if the JAVA_HOME/lib/tools.jar is not * found. * @throws ClassNotFoundException if any * @throws NoClassDefFoundError if any */ protected static List getTagletClassNames(File jarFile) throws IOException, ClassNotFoundException, NoClassDefFoundError { List classes = getClassNamesFromJar(jarFile); URLClassLoader 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 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()); } } try { cl.close(); } catch (IOException ex) { // no big deal } 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.
* Note: the Maven Home should be defined in the maven.home Java system property or defined in * M2_HOME 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 System.out. * @param globalSettingsFile reference to settings file, could be null. * @throws MavenInvocationException if any * @since 2.6 */ protected static void invokeMaven( Log log, File localRepositoryDir, File projectFile, List goals, Properties properties, File invokerLog, File globalSettingsFile) 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.setGlobalSettingsFile(globalSettingsFile); 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: * *
     * 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"]
     * 
* * @param path which can contain multiple paths separated with a colon (:) or a semi-colon * (;), platform independent. Could be null. * @return the path splitted by colon or semi-colon or null if path was null. * @since 2.6.1 */ protected static String[] splitPath(final String path) { if (path == null) { return null; } List 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: * *
     * unifyPathSeparator( "/home:/tmp" ) = "/home:/tmp" (Solaris box)
     * unifyPathSeparator( "/home:/tmp" ) = "/home;/tmp" (Windows box)
     * 
* * @param path which can contain multiple paths by separating them with a colon (:) or a semi-colon * (;), platform independent. Could be null. * @return the same path but separated with the current System path separator or null if path was * null. * @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 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 classes = new ArrayList<>(); Pattern pattern = Pattern.compile("(?i)^(META-INF/versions/(?[0-9]+)/)?(?.+)[.]class$"); try (JarInputStream jarStream = new JarInputStream(new FileInputStream(jarFile))) { for (JarEntry jarEntry = jarStream.getNextJarEntry(); jarEntry != null; jarEntry = jarStream.getNextJarEntry()) { Matcher matcher = pattern.matcher(jarEntry.getName()); if (matcher.matches()) { String version = matcher.group("v"); if (StringUtils.isEmpty(version) || JavaVersion.JAVA_VERSION.isAtLeast(version)) { String name = matcher.group("n"); 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 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); try { 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(""); } catch (IOException ioe) { throw new MavenInvocationException("IOException while consuming invocation output", ioe); } try { return invoker.execute(request); } finally { IOUtil.close(os); } } /** * @param log a logger could be null * @return the Maven home defined in the maven.home system property * or null if never set. * @since 2.6 */ private static String getMavenHome(Log log) { String mavenHome = System.getProperty("maven.home"); 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."); } } return mavenHome; } /** * @param log a logger could be null * @return the MAVEN_OPTS env variable value * @since 2.6 */ private static String getMavenOpts(Log log) { return CommandLineUtils.getSystemEnvVars().getProperty("MAVEN_OPTS"); } /** * @param log a logger could be null * @return the JAVA_HOME from System.getProperty( "java.home" ) By default, * System.getProperty( "java.home" ) = JRE_HOME and JRE_HOME should be in the * JDK_HOME * @since 2.6 */ private static File getJavaHome(Log log) { File javaHome = null; String javaHomeValue = CommandLineUtils.getSystemEnvVars().getProperty("JAVA_HOME"); // 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 JAVA_OPTS env variable value * @since 2.6 */ private static String getJavaOpts(Log log) { return CommandLineUtils.getSystemEnvVars().getProperty("JAVA_OPTS"); } /** * 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 null. */ 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 * true, then a subsequent call to nextToken will successfully return a token. * * @return true if and only if there is at least one token in the string after the current * position; false 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 toList(String src) { return toList(src, null, null); } static List toList(String src, String elementPrefix, String elementSuffix) { if (StringUtils.isEmpty(src)) { return null; } List 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 List toList(T[] multiple) { return toList(null, multiple); } static List toList(T single, T[] multiple) { if (single == null && (multiple == null || multiple.length < 1)) { return null; } List 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; } try (CloseableHttpClient 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 redirects = httpContext.getRedirectLocations(); if (isEmpty(redirects)) { return url; } else { URI last = redirects.get(redirects.size() - 1); // URI must refer to directory, so prevent redirects to index.html // see https://issues.apache.org/jira/browse/MJAVADOC-539 String truncate = "index.html"; if (last.getPath().endsWith("/" + truncate)) { try { String fixedPath = last.getPath().substring(0, last.getPath().length() - truncate.length()); last = new URI( last.getScheme(), last.getAuthority(), fixedPath, last.getQuery(), last.getFragment()); } catch (URISyntaxException ex) { // not supposed to happen, but when it does just keep the last URI } } return last.toURL(); } } } /** * Validates an URL to point to a valid package-list 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 true to validate the content of the package-list resource; * false to only check the existence of the package-list resource. * @return true if url points to a valid package-list resource; * false 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 CloseableHttpClient 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 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.close(); } } }; } 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 CloseableHttpClient createHttpClient(Settings settings, URL url) { HttpClientBuilder builder = HttpClients.custom(); Registry csfRegistry = RegistryBuilder.create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", SSLConnectionSocketFactory.getSystemSocketFactory()) .build(); builder.setConnectionManager(new PoolingHttpClientConnectionManager(csfRegistry)); builder.setDefaultRequestConfig(RequestConfig.custom() .setSocketTimeout(DEFAULT_TIMEOUT) .setConnectTimeout(DEFAULT_TIMEOUT) .setCircularRedirectsAllowed(true) .setCookieSpec(CookieSpecs.IGNORE_COOKIES) .build()); // Some web servers don't allow the default user-agent sent by httpClient builder.setUserAgent("Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"); // Some server reject requests that do not have an Accept header builder.setDefaultHeaders(Arrays.asList(new BasicHeader(HttpHeaders.ACCEPT, "*/*"))); 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()); builder.setProxy(proxy); if (StringUtils.isNotEmpty(activeProxy.getUsername()) && activeProxy.getPassword() != null) { Credentials credentials = new UsernamePasswordCredentials(activeProxy.getUsername(), activeProxy.getPassword()); CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, credentials); builder.setDefaultCredentialsProvider(credentialsProvider); } } } return builder.build(); } 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy