| /* |
| * 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.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 <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) { |
| 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<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("'")) { |
| 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 <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.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<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) { |
| files.addAll(getIncludedFiles(sourceDirectory, fileList, excludePackages)); |
| } |
| |
| 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:").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. <br> |
| * Here are some output for 'javadoc -J-version' depending the JDK used: |
| * <table><caption>Output for 'javadoc -J-version' per JDK</caption> |
| * <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 |
| * {@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 <code>-Xms</code> or <code>-Xmx</code>. <br> |
| * Here are some supported memory string depending the JDK used: |
| * <table><caption>Memory argument support per JDK</caption> |
| * <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."); |
| } |
| |
| 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 <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; |
| } |
| } |
| |
| /** |
| * 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); |
| 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<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()); |
| } |
| } |
| |
| 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. <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>. |
| * @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<String> 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: |
| * |
| * <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<>(); |
| Pattern pattern = Pattern.compile("(?i)^(META-INF/versions/(?<v>[0-9]+)/)?(?<n>.+)[.]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<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); |
| |
| 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 <code>maven.home</code> 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 <code>MAVEN_OPTS</code> 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 <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 = 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 <code>JAVA_OPTS</code> 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; |
| |
| /** |
| * 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) { |
| 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 (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; |
| } |
| } |
| 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; |
| } |
| |
| 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<URI> 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 <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 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<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.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<ConnectionSocketFactory> csfRegistry = RegistryBuilder.<ConnectionSocketFactory>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; |
| } |
| } |