Java tutorial
package org.apache.maven.plugin.javadoc; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.commons.lang.SystemUtils; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.params.ClientPNames; import org.apache.http.conn.params.ConnRoutePNames; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.PoolingClientConnectionManager; import org.apache.http.params.CoreConnectionPNames; import org.apache.http.params.CoreProtocolPNames; import org.apache.maven.artifact.Artifact; 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.util.DirectoryScanner; import org.codehaus.plexus.util.FileUtils; import org.codehaus.plexus.util.IOUtil; import org.codehaus.plexus.util.Os; import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.cli.CommandLineException; import org.codehaus.plexus.util.cli.CommandLineUtils; import org.codehaus.plexus.util.cli.Commandline; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintStream; import java.io.UnsupportedEncodingException; import java.lang.reflect.Modifier; import java.net.SocketTimeoutException; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.NoSuchElementException; import java.util.Properties; import java.util.Set; import java.util.StringTokenizer; import java.util.jar.JarEntry; import java.util.jar.JarInputStream; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** * Set of utilities methods for Javadoc. * * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a> * @version $Id$ * @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 " + "environnement 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 list of <code>String</code> directories path that will be validated. * @return a List of valid <code>String</code> directories absolute paths. */ public static List<String> pruneDirs(MavenProject project, List<String> dirs) { List<String> pruned = new ArrayList<String>(dirs.size()); for (String dir : dirs) { if (dir == null) { continue; } File directory = new File(dir); if (!directory.isAbsolute()) { directory = new File(project.getBasedir(), directory.getPath()); } if (directory.isDirectory() && !pruned.contains(directory.getAbsolutePath())) { pruned.add(directory.getAbsolutePath()); } } 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(List<String> files) { List<String> pruned = new ArrayList<String>(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. */ public static boolean shouldPruneFile(String f, List<String> pruned) { if (f != null) { File file = new File(f); if (file.isFile() && (isEmpty(pruned) || !pruned.contains(f))) { return false; } } return true; } /** * Method that gets all the source files to be excluded from the javadoc on the given * source paths. * * @param sourcePaths the path to the source files * @param subpackagesList list of subpackages to be included in the javadoc * @param excludedPackages the package names to be excluded in the javadoc * @return a List of the source files to be excluded in the generated javadoc */ protected static List<String> getExcludedNames(List<String> sourcePaths, String[] subpackagesList, String[] excludedPackages) { List<String> excludedNames = new ArrayList<String>(); for (String path : sourcePaths) { for (String aSubpackagesList : subpackagesList) { List<String> excludes = getExcludedPackages(path, excludedPackages); excludedNames.addAll(excludes); } } return excludedNames; } /** * Copy from {@link org.apache.maven.project.MavenProject#getCompileArtifacts()} * @param artifacts not null * @return list of compile artifacts with compile scope * @deprecated since 2.5, using {@link #getCompileArtifacts(Set, boolean)} instead of. */ protected static List<Artifact> getCompileArtifacts(Set<Artifact> artifacts) { return getCompileArtifacts(artifacts, false); } /** * Copy from {@link org.apache.maven.project.MavenProject#getCompileArtifacts()} * @param artifacts not null * @param withTestScope flag to include or not the artifacts with test scope * @return list of compile artifacts with or without test scope. */ protected static List<Artifact> getCompileArtifacts(Set<Artifact> artifacts, boolean withTestScope) { List<Artifact> list = new ArrayList<Artifact>(artifacts.size()); for (Artifact a : artifacts) { // TODO: classpath check doesn't belong here - that's the other method if (a.getArtifactHandler().isAddedToClasspath()) { // TODO: let the scope handler deal with this if (withTestScope) { if (Artifact.SCOPE_COMPILE.equals(a.getScope()) || Artifact.SCOPE_PROVIDED.equals(a.getScope()) || Artifact.SCOPE_SYSTEM.equals(a.getScope()) || Artifact.SCOPE_TEST.equals(a.getScope())) { list.add(a); } } else { if (Artifact.SCOPE_COMPILE.equals(a.getScope()) || Artifact.SCOPE_PROVIDED.equals(a.getScope()) || Artifact.SCOPE_SYSTEM.equals(a.getScope())) { list.add(a); } } } } return list; } /** * Convenience method to wrap an argument value in single quotes (i.e. <code>'</code>). Intended for values * which may contain whitespaces. * <br/> * To prevent javadoc error, the line separator (i.e. <code>\n</code>) are skipped. * * @param value the argument value. * @return argument with quote */ protected static String quotedArgument(String value) { String arg = value; if (StringUtils.isNotEmpty(arg)) { if (arg.contains("'")) { arg = StringUtils.replace(arg, "'", "\\'"); } arg = "'" + arg + "'"; // To prevent javadoc error arg = StringUtils.replace(arg, "\n", " "); } return arg; } /** * Convenience method to format a path argument so that it is properly interpreted by the javadoc tool. Intended * for path values which may contain whitespaces. * * @param value the argument value. * @return path argument with quote */ protected static String quotedPathArgument(String value) { String path = value; if (StringUtils.isNotEmpty(path)) { path = path.replace('\\', '/'); if (path.contains("\'")) { String split[] = path.split("\'"); path = ""; for (int i = 0; i < split.length; i++) { if (i != split.length - 1) { path = path + split[i] + "\\'"; } else { path = path + split[i]; } } } path = "'" + path + "'"; } return path; } /** * Convenience method that copy all <code>doc-files</code> directories from <code>javadocDir</code> * to the <code>outputDirectory</code>. * * @param outputDirectory the output directory * @param javadocDir the javadoc directory * @throws IOException if any * @deprecated since 2.5, using {@link #copyJavadocResources(File, File, String)} instead of. */ protected static void copyJavadocResources(File outputDirectory, File javadocDir) throws IOException { copyJavadocResources(outputDirectory, javadocDir, null); } /** * 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<String>(); excludes.addAll(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 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, String[] excludePackages) { List<String> files = new ArrayList<String>(); for (String aFileList : fileList) { boolean include = true; for (int k = 0; k < excludePackages.length && include; k++) { // handle wildcards (*) in the excludePackageNames String[] excludeName = excludePackages[k].split("[*]"); if (excludeName.length == 0) { continue; } if (excludeName.length > 1) { int u = 0; while (include && u < excludeName.length) { if (!"".equals(excludeName[u].trim()) && aFileList.contains(excludeName[u])) { include = false; } u++; } } else { if (aFileList.startsWith(sourceDirectory.toString() + File.separatorChar + excludeName[0])) { if (excludeName[0].endsWith(String.valueOf(File.separatorChar))) { int i = aFileList.lastIndexOf(File.separatorChar); String packageName = aFileList.substring(0, i + 1); File currentPackage = new File(packageName); File excludedPackage = new File(sourceDirectory, excludeName[0]); if (currentPackage.equals(excludedPackage) && aFileList.substring(i).contains(".java")) { include = true; } else { include = false; } } else { include = false; } } } } if (include) { files.add(quotedPathArgument(aFileList)); } } 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 List<String> getExcludedPackages(String sourceDirectory, String[] excludePackagenames) { List<String> files = new ArrayList<String>(); for (String excludePackagename : excludePackagenames) { String[] fileList = FileUtils.getFilesFromExtension(sourceDirectory, new String[] { "java" }); for (String aFileList : fileList) { String[] excludeName = excludePackagename.split("[*]"); int u = 0; while (u < excludeName.length) { if (!"".equals(excludeName[u].trim()) && aFileList.contains(excludeName[u]) && !sourceDirectory.contains(excludeName[u])) { files.add(aFileList); } u++; } } } List<String> excluded = new ArrayList<String>(); for (String file : files) { int idx = file.lastIndexOf(File.separatorChar); String tmpStr = file.substring(0, idx); tmpStr = tmpStr.replace('\\', '/'); String[] srcSplit = tmpStr.split(Pattern.quote(sourceDirectory.replace('\\', '/') + '/')); String excludedPackage = srcSplit[1].replace('/', '.'); if (!excluded.contains(excludedPackage)) { excluded.add(excludedPackage); } } return excluded; } /** * Convenience method that gets the files to be included in the javadoc. * * @param sourceDirectory the directory where the source files are located * @param files the variable that contains the appended filenames of the files to be included in the javadoc * @param excludePackages the packages to be excluded in the javadocs */ protected static void addFilesFromSource(List<String> files, File sourceDirectory, List<String> sourceFileIncludes, List<String> sourceFileExcludes, 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(); String[] pathList = new String[fileList.length]; for (int x = 0; x < fileList.length; x++) { pathList[x] = new File(sourceDirectory, fileList[x]).getAbsolutePath(); } if (pathList.length != 0) { List<String> tmpFiles = getIncludedFiles(sourceDirectory, pathList, excludePackages); files.addAll(tmpFiles); } } /** * 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 #parseJavadocVersion(String) */ protected static float 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 CommandLineUtils.StringStreamConsumer(); CommandLineUtils.StringStreamConsumer err = new CommandLineUtils.StringStreamConsumer(); int exitCode = CommandLineUtils.executeCommandLine(cmd, out, err); if (exitCode != 0) { StringBuilder msg = new StringBuilder("Exit code: " + exitCode + " - " + err.getOutput()); msg.append('\n'); msg.append("Command line was:" + CommandLineUtils.toString(cmd.getCommandline())); throw new CommandLineException(msg.toString()); } if (StringUtils.isNotEmpty(err.getOutput())) { return parseJavadocVersion(err.getOutput()); } else if (StringUtils.isNotEmpty(out.getOutput())) { return parseJavadocVersion(out.getOutput()); } throw new IllegalArgumentException("No output found from the command line 'javadoc -J-version'"); } /** * Parse the output for 'javadoc -J-version' and return the javadoc version recognized. * <br/> * Here are some output for 'javadoc -J-version' depending the JDK used: * <table> * <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 complte 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. * @throws PatternSyntaxException if the output doesn't match with the output pattern * <tt>(?s).*?([0-9]+\\.[0-9]+)(\\.([0-9]+))?.*</tt>. * @throws IllegalArgumentException if the output is null */ protected static float parseJavadocVersion(String output) throws IllegalArgumentException { if (StringUtils.isEmpty(output)) { throw new IllegalArgumentException("The output could not be null."); } Pattern pattern = Pattern.compile("(?s).*?([0-9]+\\.[0-9]+)(\\.([0-9]+))?.*"); Matcher matcher = pattern.matcher(output); if (!matcher.matches()) { throw new PatternSyntaxException("Unrecognized version of Javadoc: '" + output + "'", pattern.pattern(), pattern.toString().length() - 1); } String version = matcher.group(3); if (version == null) { version = matcher.group(1); } else { version = matcher.group(1) + version; } return Float.parseFloat(version); } /** * 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> * <tr> * <th>JDK</th> * <th>Memory argument support for <code>-Xms</code> or <code>-Xmx</code></th> * </tr> * <tr> * <td>SUN</td> * <td>1024k | 128m | 1g | 1t</td> * </tr> * <tr> * <td>IBM</td> * <td>1024k | 1024b | 128m | 128mb | 1g | 1gb</td> * </tr> * <tr> * <td>BEA</td> * <td>1024k | 1024kb | 128m | 128mb | 1g | 1gb</td> * </tr> * </table> * * @param memory the memory to be parsed, not null. * @return the memory parsed with a supported unit. If no unit specified in the <code>memory</code> parameter, * the default unit is <code>m</code>. The units <code>g | gb</code> or <code>t | tb</code> will be converted * in <code>m</code>. * @throws IllegalArgumentException if the <code>memory</code> parameter is null or doesn't match any pattern. */ protected static String parseJavadocMemory(String memory) throws IllegalArgumentException { if (StringUtils.isEmpty(memory)) { throw new IllegalArgumentException("The memory could not be null."); } Pattern p = Pattern.compile("^\\s*(\\d+)\\s*?\\s*$"); Matcher m = p.matcher(memory); if (m.matches()) { return m.group(1) + "m"; } p = Pattern.compile("^\\s*(\\d+)\\s*k(b)?\\s*$", Pattern.CASE_INSENSITIVE); m = p.matcher(memory); if (m.matches()) { return m.group(1) + "k"; } p = Pattern.compile("^\\s*(\\d+)\\s*m(b)?\\s*$", Pattern.CASE_INSENSITIVE); m = p.matcher(memory); if (m.matches()) { return m.group(1) + "m"; } p = Pattern.compile("^\\s*(\\d+)\\s*g(b)?\\s*$", Pattern.CASE_INSENSITIVE); m = p.matcher(memory); if (m.matches()) { return (Integer.parseInt(m.group(1)) * 1024) + "m"; } p = Pattern.compile("^\\s*(\\d+)\\s*t(b)?\\s*$", Pattern.CASE_INSENSITIVE); m = p.matcher(memory); if (m.matches()) { return (Integer.parseInt(m.group(1)) * 1024 * 1024) + "m"; } throw new IllegalArgumentException("Could convert not to a memory size: " + memory); } /** * Validate if a charset is supported on this platform. * * @param charsetName the charsetName to be check. * @return <code>true</code> if the given charset is supported by the JVM, <code>false</code> otherwise. */ protected static boolean validateEncoding(String charsetName) { if (StringUtils.isEmpty(charsetName)) { return false; } OutputStream ost = new ByteArrayOutputStream(); OutputStreamWriter osw = null; try { osw = new OutputStreamWriter(ost, charsetName); } catch (UnsupportedEncodingException exc) { return false; } finally { IOUtil.close(osw); } return true; } /** * For security reasons, if an active proxy is defined and needs an authentication by * username/password, hide the proxy password in the command line. * * @param cmdLine a command line, not null * @param settings the user settings * @return the cmdline with '*' for the http.proxyPassword JVM property */ protected static String hideProxyPassword(String cmdLine, Settings settings) { if (cmdLine == null) { throw new IllegalArgumentException("cmdLine could not be null"); } if (settings == null) { return cmdLine; } Proxy activeProxy = settings.getActiveProxy(); if (activeProxy != null && StringUtils.isNotEmpty(activeProxy.getHost()) && StringUtils.isNotEmpty(activeProxy.getUsername()) && StringUtils.isNotEmpty(activeProxy.getPassword())) { String pass = "-J-Dhttp.proxyPassword=\"" + activeProxy.getPassword() + "\""; String hidepass = "-J-Dhttp.proxyPassword=\"" + StringUtils.repeat("*", activeProxy.getPassword().length()) + "\""; return StringUtils.replace(cmdLine, pass, hidepass); } return cmdLine; } /** * Auto-detect the class names of the implementation of <code>com.sun.tools.doclets.Taglet</code> class from a * given jar file. * <br/> * <b>Note</b>: <code>JAVA_HOME/lib/tools.jar</code> is a requirement to find * <code>com.sun.tools.doclets.Taglet</code> class. * * @param jarFile not null * @return the list of <code>com.sun.tools.doclets.Taglet</code> class names from a given jarFile. * @throws IOException if jarFile is invalid or not found, or if the <code>JAVA_HOME/lib/tools.jar</code> * is not found. * @throws ClassNotFoundException if any * @throws NoClassDefFoundError if any */ protected static List<String> getTagletClassNames(File jarFile) throws IOException, ClassNotFoundException, NoClassDefFoundError { List<String> classes = getClassNamesFromJar(jarFile); ClassLoader cl; // Needed to find com.sun.tools.doclets.Taglet class File tools = new File(System.getProperty("java.home"), "../lib/tools.jar"); if (tools.exists() && tools.isFile()) { cl = new URLClassLoader(new URL[] { jarFile.toURI().toURL(), tools.toURI().toURL() }, null); } else { cl = new URLClassLoader(new URL[] { jarFile.toURI().toURL() }, null); } List<String> tagletClasses = new ArrayList<String>(); Class<?> tagletClass = cl.loadClass("com.sun.tools.doclets.Taglet"); for (String s : classes) { Class<?> c = cl.loadClass(s); if (tagletClass.isAssignableFrom(c) && !Modifier.isAbstract(c.getModifiers())) { tagletClasses.add(c.getName()); } } return tagletClasses; } /** * Copy the given url to the given file. * * @param url not null url * @param file not null file where the url will be created * @throws IOException if any * @since 2.6 */ protected static void copyResource(URL url, File file) throws IOException { if (file == null) { throw new IOException("The file can't be null."); } if (url == null) { throw new IOException("The url could not be null."); } InputStream is = url.openStream(); if (is == null) { throw new IOException("The resource " + url + " doesn't exists."); } if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } OutputStream os = null; try { os = new FileOutputStream(file); IOUtil.copy(is, os); } finally { IOUtil.close(is); IOUtil.close(os); } } /** * Invoke Maven for the given project file with a list of goals and properties, the output will be in the * invokerlog file. * <br/> * <b>Note</b>: the Maven Home should be defined in the <code>maven.home</code> Java system property or defined in * <code>M2_HOME</code> system env variables. * * @param log a logger could be null. * @param localRepositoryDir the localRepository not null. * @param projectFile a not null project file. * @param goals a not null goals list. * @param properties the properties for the goals, could be null. * @param invokerLog the log file where the invoker will be written, if null using <code>System.out</code>. * @throws MavenInvocationException if any * @since 2.6 */ protected static void invokeMaven(Log log, File localRepositoryDir, File projectFile, List<String> goals, Properties properties, File invokerLog) throws MavenInvocationException { if (projectFile == null) { throw new IllegalArgumentException("projectFile should be not null."); } if (!projectFile.isFile()) { throw new IllegalArgumentException(projectFile.getAbsolutePath() + " is not a file."); } if (goals == null || goals.size() == 0) { throw new IllegalArgumentException("goals should be not empty."); } if (localRepositoryDir == null || !localRepositoryDir.isDirectory()) { throw new IllegalArgumentException( "localRepositoryDir '" + localRepositoryDir + "' should be a directory."); } String mavenHome = getMavenHome(log); if (StringUtils.isEmpty(mavenHome)) { String msg = "Could NOT invoke Maven because no Maven Home is defined. You need to have set the M2_HOME " + "system env variable or a maven.home Java system properties."; if (log != null) { log.error(msg); } else { System.err.println(msg); } return; } Invoker invoker = new DefaultInvoker(); invoker.setMavenHome(new File(mavenHome)); invoker.setLocalRepositoryDirectory(localRepositoryDir); InvocationRequest request = new DefaultInvocationRequest(); request.setBaseDirectory(projectFile.getParentFile()); request.setPomFile(projectFile); 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<String>(); 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<String>(); JarInputStream jarStream = null; try { jarStream = new JarInputStream(new FileInputStream(jarFile)); JarEntry jarEntry = jarStream.getNextJarEntry(); while (jarEntry != null) { if (jarEntry.getName().toLowerCase(Locale.ENGLISH).endsWith(".class")) { String name = jarEntry.getName().substring(0, jarEntry.getName().indexOf(".")); classes.add(name.replaceAll("/", "\\.")); } jarStream.closeEntry(); jarEntry = jarStream.getNextJarEntry(); } } finally { IOUtil.close(jarStream); } return classes; } /** * @param log could be null * @param invoker not null * @param request not null * @param invokerLog not null * @param goals not null * @param properties could be null * @param mavenOpts could be null * @return the invocation result * @throws MavenInvocationException if any * @since 2.6 */ private static InvocationResult invoke(Log log, Invoker invoker, InvocationRequest request, File invokerLog, List<String> goals, Properties properties, String mavenOpts) throws MavenInvocationException { PrintStream ps; OutputStream os = null; if (invokerLog != null) { if (log != null && log.isDebugEnabled()) { log.debug("Using " + invokerLog.getAbsolutePath() + " to log the invoker"); } try { if (!invokerLog.exists()) { //noinspection ResultOfMethodCallIgnored invokerLog.getParentFile().mkdirs(); } os = new FileOutputStream(invokerLog); ps = new PrintStream(os, true, "UTF-8"); } catch (FileNotFoundException e) { if (log != null && log.isErrorEnabled()) { log.error( "FileNotFoundException: " + e.getMessage() + ". Using System.out to log the invoker."); } ps = System.out; } catch (UnsupportedEncodingException e) { if (log != null && log.isErrorEnabled()) { log.error("UnsupportedEncodingException: " + e.getMessage() + ". Using System.out to log the invoker."); } ps = System.out; } } else { if (log != null && log.isDebugEnabled()) { log.debug("Using System.out to log the invoker."); } ps = System.out; } if (mavenOpts != null) { request.setMavenOpts(mavenOpts); } InvocationOutputHandler outputHandler = new PrintStreamHandler(ps, false); request.setOutputHandler(outputHandler); outputHandler.consumeLine("Invoking Maven for the goals: " + goals + " with " + (properties == null ? "no properties" : "properties=" + properties)); outputHandler.consumeLine(""); outputHandler.consumeLine("M2_HOME=" + getMavenHome(log)); outputHandler.consumeLine("MAVEN_OPTS=" + getMavenOpts(log)); outputHandler.consumeLine("JAVA_HOME=" + getJavaHome(log)); outputHandler.consumeLine("JAVA_OPTS=" + getJavaOpts(log)); outputHandler.consumeLine(""); try { return invoker.execute(request); } finally { IOUtil.close(os); } } /** * @param log a logger could be null * @return the Maven home defined in the <code>maven.home</code> system property or defined * in <code>M2_HOME</code> system env variables or null if never set. * @since 2.6 */ private static String getMavenHome(Log log) { String mavenHome = System.getProperty("maven.home"); if (mavenHome == null) { try { mavenHome = CommandLineUtils.getSystemEnvVars().getProperty("M2_HOME"); } catch (IOException e) { if (log != null && log.isDebugEnabled()) { log.debug("IOException: " + e.getMessage()); } } } File m2Home = new File(mavenHome); if (!m2Home.exists()) { if (log != null && log.isErrorEnabled()) { log.error( "Cannot find Maven application directory. Either specify \'maven.home\' system property, or " + "M2_HOME environment variable."); } } return mavenHome; } /** * @param log a logger could be null * @return the <code>MAVEN_OPTS</code> env variable value * @since 2.6 */ private static String getMavenOpts(Log log) { String mavenOpts = null; try { mavenOpts = CommandLineUtils.getSystemEnvVars().getProperty("MAVEN_OPTS"); } catch (IOException e) { if (log != null && log.isDebugEnabled()) { log.debug("IOException: " + e.getMessage()); } } return mavenOpts; } /** * @param log a logger could be null * @return the <code>JAVA_HOME</code> from System.getProperty( "java.home" ) * By default, <code>System.getProperty( "java.home" ) = JRE_HOME</code> and <code>JRE_HOME</code> * should be in the <code>JDK_HOME</code> * @since 2.6 */ private static File getJavaHome(Log log) { File javaHome; if (SystemUtils.IS_OS_MAC_OSX) { javaHome = SystemUtils.getJavaHome(); } else { javaHome = new File(SystemUtils.getJavaHome(), ".."); } if (javaHome == null || !javaHome.exists()) { try { javaHome = new File(CommandLineUtils.getSystemEnvVars().getProperty("JAVA_HOME")); } catch (IOException e) { if (log != null && log.isDebugEnabled()) { log.debug("IOException: " + e.getMessage()); } } } if (javaHome == null || !javaHome.exists()) { if (log != null && log.isErrorEnabled()) { log.error( "Cannot find Java application directory. Either specify \'java.home\' system property, or " + "JAVA_HOME environment variable."); } } return javaHome; } /** * @param log a logger could be null * @return the <code>JAVA_OPTS</code> env variable value * @since 2.6 */ private static String getJavaOpts(Log log) { String javaOpts = null; try { javaOpts = CommandLineUtils.getSystemEnvVars().getProperty("JAVA_OPTS"); } catch (IOException e) { if (log != null && log.isDebugEnabled()) { log.debug("IOException: " + e.getMessage()); } } return javaOpts; } /** * A Path tokenizer takes a path and returns the components that make up * that path. * * The path can use path separators of either ':' or ';' and file separators * of either '/' or '\'. * * @version revision 439418 taken on 2009-09-12 from Ant Project * (see http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/PathTokenizer.java) */ private static class PathTokenizer { /** * A tokenizer to break the string up based on the ':' or ';' separators. */ private StringTokenizer tokenizer; /** * A String which stores any path components which have been read ahead * due to DOS filesystem compensation. */ private String lookahead = null; /** * A boolean that determines if we are running on Novell NetWare, which * exhibits slightly different path name characteristics (multi-character * volume / drive names) */ private boolean onNetWare = Os.isFamily("netware"); /** * Flag to indicate whether or not we are running on a platform with a * DOS style filesystem */ private boolean dosStyleFilesystem; /** * Constructs a path tokenizer for the specified path. * * @param path The path to tokenize. Must not be <code>null</code>. */ public PathTokenizer(String path) { if (onNetWare) { // For NetWare, use the boolean=true mode, so we can use delimiter // information to make a better decision later. tokenizer = new StringTokenizer(path, ":;", true); } else { // on Windows and Unix, we can ignore delimiters and still have // enough information to tokenize correctly. tokenizer = new StringTokenizer(path, ":;", false); } dosStyleFilesystem = File.pathSeparatorChar == ';'; } /** * Tests if there are more path elements available from this tokenizer's * path. If this method returns <code>true</code>, then a subsequent call * to nextToken will successfully return a token. * * @return <code>true</code> if and only if there is at least one token * in the string after the current position; <code>false</code> otherwise. */ public boolean hasMoreTokens() { return lookahead != null || tokenizer.hasMoreTokens(); } /** * Returns the next path element from this tokenizer. * * @return the next path element from this tokenizer. * * @exception NoSuchElementException if there are no more elements in this * tokenizer's path. */ public String nextToken() throws NoSuchElementException { String token; if (lookahead != null) { token = lookahead; lookahead = null; } else { token = tokenizer.nextToken().trim(); } if (!onNetWare) { if (token.length() == 1 && Character.isLetter(token.charAt(0)) && dosStyleFilesystem && tokenizer.hasMoreTokens()) { // we are on a dos style system so this path could be a drive // spec. We look at the next token String nextToken = tokenizer.nextToken().trim(); if (nextToken.startsWith("\\") || nextToken.startsWith("/")) { // we know we are on a DOS style platform and the next path // starts with a slash or backslash, so we know this is a // drive spec token += ":" + nextToken; } else { // store the token just read for next time lookahead = nextToken; } } } else { // we are on NetWare, tokenizing is handled a little differently, // due to the fact that NetWare has multiple-character volume names. if (token.equals(File.pathSeparator) || token.equals(":")) { // ignore ";" and get the next token token = tokenizer.nextToken().trim(); } if (tokenizer.hasMoreTokens()) { // this path could be a drive spec, so look at the next token String nextToken = tokenizer.nextToken().trim(); // make sure we aren't going to get the path separator next if (!nextToken.equals(File.pathSeparator)) { if (nextToken.equals(":")) { if (!token.startsWith("/") && !token.startsWith("\\") && !token.startsWith(".") && !token.startsWith("..")) { // it indeed is a drive spec, get the next bit String oneMore = tokenizer.nextToken().trim(); if (!oneMore.equals(File.pathSeparator)) { token += ":" + oneMore; } else { token += ":"; lookahead = oneMore; } } // implicit else: ignore the ':' since we have either a // UNIX or a relative path } else { // store the token just read for next time lookahead = nextToken; } } } } return token; } } 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<String>(); 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<T>(); 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. */ public static boolean isNotEmpty(final Collection<?> collection) { return collection != null && !collection.isEmpty(); } /** * Convenience method to determine that a collection is empty or null. */ public static boolean isEmpty(final Collection<?> collection) { return collection == null || collection.isEmpty(); } /** * 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"); } BufferedReader reader = null; HttpGet httpMethod = null; HttpClient httpClient = null; try { 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... httpClient = createHttpClient(settings, url); httpMethod = new HttpGet(url.toString()); HttpResponse response; try { response = httpClient.execute(httpMethod); } catch (SocketTimeoutException e) { // could be a sporadic failure, one more retry before we give up response = httpClient.execute(httpMethod); } int status = response.getStatusLine().getStatusCode(); if (status != HttpStatus.SC_OK) { throw new FileNotFoundException("Unexpected HTTP status code " + status + " getting resource " + url.toExternalForm() + "."); } // Intentionally using the platform default encoding here since this is what Javadoc uses internally. reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); } if (validateContent) { String line; while ((line = reader.readLine()) != null) { if (!isValidPackageName(line)) { return false; } } } return true; } finally { IOUtil.close(reader); if (httpMethod != null) { httpMethod.releaseConnection(); } if (httpClient != null) { httpClient.getConnectionManager().shutdown(); } } } private static boolean isValidPackageName(String str) { if (StringUtils.isEmpty(str)) { // unnamed package is valid (even if bad practice :) ) return true; } int idx; while ((idx = str.indexOf('.')) != -1) { if (!isValidClassName(str.substring(0, idx))) { return false; } str = str.substring(idx + 1); } return isValidClassName(str); } private static boolean isValidClassName(String str) { if (StringUtils.isEmpty(str) || !Character.isJavaIdentifierStart(str.charAt(0))) { return false; } for (int i = str.length() - 1; i > 0; i--) { if (!Character.isJavaIdentifierPart(str.charAt(i))) { return false; } } return true; } /** * Creates a new {@code HttpClient} instance. * * @param settings The settings to use for setting up the client or {@code null}. * @param url The {@code URL} to use for setting up the client or {@code null}. * * @return A new {@code HttpClient} instance. * * @see #DEFAULT_TIMEOUT * @since 2.8 */ private static HttpClient createHttpClient(Settings settings, URL url) { DefaultHttpClient httpClient = new DefaultHttpClient(new PoolingClientConnectionManager()); httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, DEFAULT_TIMEOUT); httpClient.getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, DEFAULT_TIMEOUT); httpClient.getParams().setBooleanParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, true); // Some web servers don't allow the default user-agent sent by httpClient httpClient.getParams().setParameter(CoreProtocolPNames.USER_AGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)"); if (settings != null && settings.getActiveProxy() != null) { Proxy activeProxy = settings.getActiveProxy(); ProxyInfo proxyInfo = new ProxyInfo(); proxyInfo.setNonProxyHosts(activeProxy.getNonProxyHosts()); if (StringUtils.isNotEmpty(activeProxy.getHost()) && (url == null || !ProxyUtils.validateNonProxyHosts(proxyInfo, url.getHost()))) { HttpHost proxy = new HttpHost(activeProxy.getHost(), activeProxy.getPort()); httpClient.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, proxy); if (StringUtils.isNotEmpty(activeProxy.getUsername()) && activeProxy.getPassword() != null) { Credentials credentials = new UsernamePasswordCredentials(activeProxy.getUsername(), activeProxy.getPassword()); httpClient.getCredentialsProvider().setCredentials(AuthScope.ANY, credentials); } } } return httpClient; } static boolean equalsIgnoreCase(String value, String... strings) { for (String s : strings) { if (s.equalsIgnoreCase(value)) { return true; } } return false; } static boolean equals(String value, String... strings) { for (String s : strings) { if (s.equals(value)) { return true; } } return false; } }