/* * $Id$ * This file is a part of the Arakhne Foundation Classes, http://www.arakhne.org/afc * * Copyright (c) 2000-2012 Stephane GALLAND. * Copyright (c) 2005-10, Multiagent Team, Laboratoire Systemes et Transports, * Universite de Technologie de Belfort-Montbeliard. * Copyright (c) 2013-2016 The original authors, and other authors. * * Licensed 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.arakhne.maven; import java.io.BufferedReader; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.lang.reflect.Array; import java.math.BigDecimal; import java.math.BigInteger; import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.ReadableByteChannel; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CodingErrorAction; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.EventListener; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.ResourceBundle; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.ArtifactUtils; import org.apache.maven.artifact.handler.ArtifactHandler; import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.execution.MavenSession; import org.apache.maven.model.Contributor; import org.apache.maven.model.Dependency; import org.apache.maven.model.Developer; import org.apache.maven.model.License; import org.apache.maven.model.Model; import org.apache.maven.model.Organization; import org.apache.maven.model.Parent; import org.apache.maven.model.Plugin; import org.apache.maven.model.Scm; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectBuilder; import org.apache.maven.project.ProjectBuildingException; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.artifact.DefaultArtifact; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ArtifactRequest; import org.eclipse.aether.resolution.ArtifactResolutionException; import org.eclipse.aether.resolution.ArtifactResult; import org.sonatype.plexus.build.incremental.BuildContext; import org.tmatesoft.svn.core.SVNException; import org.tmatesoft.svn.core.wc.SVNClientManager; import org.tmatesoft.svn.core.wc.SVNInfo; import org.tmatesoft.svn.core.wc.SVNRevision; /** * Abstract implementation for all Arakhnê maven modules. This implementation is thread safe. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * * @component */ @SuppressWarnings({"checkstyle:classfanoutcomplexity", "checkstyle:classdataabstractioncoupling", "checkstyle:methodcount"}) public abstract class AbstractArakhneMojo extends AbstractMojo { /** * Empty string constant. */ public static final String EMPTY_STRING = ExtendedArtifact.EMPTY_STRING; /** * Maven tag for artifcat id. */ public static final String PROP_ARTIFACTID = "artifactId"; //$NON-NLS-1$ /** * Maven tag for goup id. */ public static final String PROP_GROUPID = "groupId"; //$NON-NLS-1$ /** * Maven tag for version description. */ public static final String PROP_VERSION = "version"; //$NON-NLS-1$ /** * Preferred charset for the new MacOS and Linux operating systems. */ public static final String PREFERRED_CHARSET_UNIX = "UTF-8"; //$NON-NLS-1$ /** * Preferred charset for the Windows operating systems. */ public static final String PREFERRED_CHARSET_WINDOWS = "windows-1250"; //$NON-NLS-1$ /** * Preferred charset for the old MacOS operating systems. */ public static final String PREFERRED_CHARSET_MACOS = "MacRoman"; //$NON-NLS-1$ /** * Preferred charset for the Java virtual machine (internal charset). */ public static final String PREFERRED_CHARSET_JVM = "UTF-16"; //$NON-NLS-1$ private static final int FILE_BUFFER = 4096; /** * Invocation date. */ protected final Date invocationDate = new Date(); /** * Map the directory of pom.xml files to the definition of the corresponding maven module. */ private final Map<File, ExtendedArtifact> localArtifactDescriptions = new TreeMap<>(); /** * Map the artifact id to the definition of the corresponding maven module. */ private final Map<String, ExtendedArtifact> remoteArtifactDescriptions = new TreeMap<>(); /** * Manager of the SVN repository. */ private SVNClientManager svnManager; /** * Are the preferred charset in the preferred order. */ private Charset[] preferredCharsets; /** Construct. */ public AbstractArakhneMojo() { final List<Charset> availableCharsets = new ArrayList<>(); // New Mac OS and Linux OS addCharset(availableCharsets, PREFERRED_CHARSET_UNIX); // Windows OS addCharset(availableCharsets, PREFERRED_CHARSET_WINDOWS); // Old Mac OS addCharset(availableCharsets, PREFERRED_CHARSET_MACOS); // Java Internal addCharset(availableCharsets, PREFERRED_CHARSET_JVM); this.preferredCharsets = new Charset[availableCharsets.size()]; availableCharsets.toArray(this.preferredCharsets); availableCharsets.clear(); } /** Replies the preferred URL for the given contributor. * * @param contributor the contributor. * @param log the log. * @return the URL or <code>null</code> if no URL could be built. */ protected static URL getContributorURL(Contributor contributor, Log log) { URL url = null; if (contributor != null) { String rawUrl = contributor.getUrl(); if (rawUrl != null && !EMPTY_STRING.equals(rawUrl)) { try { url = new URL(rawUrl); } catch (Throwable exception) { url = null; } } if (url == null) { rawUrl = contributor.getEmail(); if (rawUrl != null && !EMPTY_STRING.equals(rawUrl)) { try { url = new URL("mailto:" + rawUrl); //$NON-NLS-1$ } catch (Throwable exception) { url = null; } } } } return url; } /** * Copy a directory. * * @param in input directory. * @param out output directory. * @param skipHiddenFiles indicates if the hidden files should be ignored. * @throws IOException on error. * @since 3.3 */ public final void dirCopy(File in, File out, boolean skipHiddenFiles) throws IOException { assert in != null; assert out != null; getLog().debug(in.toString() + "->" + out.toString()); //$NON-NLS-1$ getLog().debug("Ignore hidden files: " + skipHiddenFiles); //$NON-NLS-1$ out.mkdirs(); final LinkedList<File> candidates = new LinkedList<>(); candidates.add(in); File[] children; while (!candidates.isEmpty()) { final File f = candidates.removeFirst(); getLog().debug("Scanning: " + f); //$NON-NLS-1$ if (f.isDirectory()) { children = f.listFiles(); if (children != null && children.length > 0) { // Non empty directory for (final File c : children) { if (!skipHiddenFiles || !c.isHidden()) { getLog().debug("Discovering: " + c); //$NON-NLS-1$ candidates.add(c); } } } } else { // not a directory final File targetFile = toOutput(in, f, out); targetFile.getParentFile().mkdirs(); fileCopy(f, targetFile); } } } private static File toOutput(File root, File file, File newRoot) { final String filename = file.getAbsolutePath(); final String rootPath = root.getAbsolutePath(); return new File(filename.replaceAll("^\\Q" + rootPath + "\\E", //$NON-NLS-1$ //$NON-NLS-2$ newRoot.getAbsolutePath())); } /** * Delete a directory and its content. * * @param dir the directory to remove. * @throws IOException on error. * @since 3.3 */ public final void dirRemove(File dir) throws IOException { if (dir != null) { getLog().debug("Deleting tree: " + dir.toString()); //$NON-NLS-1$ final LinkedList<File> candidates = new LinkedList<>(); candidates.add(dir); File[] children; final BuildContext buildContext = getBuildContext(); while (!candidates.isEmpty()) { final File f = candidates.getFirst(); getLog().debug("Scanning: " + f); //$NON-NLS-1$ if (f.isDirectory()) { children = f.listFiles(); if (children != null && children.length > 0) { // Non empty directory for (final File c : children) { getLog().debug("Discovering: " + c); //$NON-NLS-1$ candidates.push(c); } } else { // empty directory getLog().debug("Deleting: " + f); //$NON-NLS-1$ candidates.removeFirst(); f.delete(); buildContext.refresh(f.getParentFile()); } } else { // not a directory candidates.removeFirst(); if (f.exists()) { getLog().debug("Deleting: " + f); //$NON-NLS-1$ f.delete(); buildContext.refresh(f.getParentFile()); } } } getLog().debug("Deletion done"); //$NON-NLS-1$ } } /** * Copy a file. * * @param in input file. * @param out output file. * @throws IOException on error. */ public final void fileCopy(File in, File out) throws IOException { assert in != null; assert out != null; getLog().debug("Copying file: " + in.toString() + " into " + out.toString()); //$NON-NLS-1$ //$NON-NLS-2$ try (FileInputStream fis = new FileInputStream(in)) { try (FileChannel inChannel = fis.getChannel()) { try (FileOutputStream fos = new FileOutputStream(out)) { try (FileChannel outChannel = fos.getChannel()) { inChannel.transferTo(0, inChannel.size(), outChannel); } } } } finally { getBuildContext().refresh(out); } } /** * Copy a file. * * @param in input file. * @param out output file. * @throws IOException on error. */ public final void fileCopy(URL in, File out) throws IOException { assert in != null; try (InputStream inStream = in.openStream()) { try (OutputStream outStream = new FileOutputStream(out)) { final byte[] buf = new byte[FILE_BUFFER]; int len; while ((len = inStream.read(buf)) > 0) { outStream.write(buf, 0, len); } } } finally { getBuildContext().refresh(out); } } /** * Read a resource property and replace the parametrized macros by the given parameters. * * @param source * is the source of the properties. * @param label * is the name of the property. * @param params * are the parameters to replace. * @return the read text. */ public static final String getLString(Class<?> source, String label, Object... params) { final ResourceBundle rb = ResourceBundle.getBundle(source.getCanonicalName()); String text = rb.getString(label); text = text.replaceAll("[\\n\\r]", "\n"); //$NON-NLS-1$ //$NON-NLS-2$ text = text.replaceAll("\\t", "\t"); //$NON-NLS-1$ //$NON-NLS-2$ text = MessageFormat.format(text, params); return text; } /** * Remove the path prefix from a file. * * @param prefix path prefix to remove. * @param file input filename. * @return the {@code file} without the prefix. */ public static final String removePathPrefix(File prefix, File file) { final String r = file.getAbsolutePath().replaceFirst( "^" //$NON-NLS-1$ + Pattern.quote(prefix.getAbsolutePath()), EMPTY_STRING); if (r.startsWith(File.separator)) { return r.substring(File.separator.length()); } return r; } private static void addCharset(List<Charset> availableCharsets, String csName) { try { final Charset cs = Charset.forName(csName); if (!availableCharsets.contains(cs)) { availableCharsets.add(cs); } } catch (Throwable exception) { // } } /** * Replies the preferred charsets in the preferred order of use. * * @return the preferred charsets in the preferred order of use. */ public final Charset[] getPreferredCharsets() { return this.preferredCharsets; } /** * Set the preferred charsets in the preferred order of use. * * @param charsets * are the preferred charsets in the preferred order of use. */ public final void setPreferredCharsets(Charset... charsets) { this.preferredCharsets = charsets; } /** * Replies the manager of the SVN repository. * * @return the manager of the SVN repository. */ public final synchronized SVNClientManager getSVNClientManager() { if (this.svnManager == null) { this.svnManager = SVNClientManager.newInstance(); } return this.svnManager; } /** * Replies the artifact handler manager. * * <p>It is an attribute defined as: <pre><code> * <span>/</span>* <span>@</span>component * <span>*</span>/ * private ArtifactHandlerManager manager; * </code></pre> * * @return the artifact resolver. */ public abstract ArtifactHandlerManager getArtifactHandlerManager(); /** * Replies the output directory of the project. Basically it is <code>getRootDirectory()+"/target"</code>. * * <p>It is an attribute defined as: <pre><code> * <span>/</span>* <span>@</span>parameter expression="${project.build.directory}" * <span>*</span>/ * private File outputDirectory; * </code></pre> * * @return the output directory. */ public abstract File getOutputDirectory(); /** * Replies the root directory of the project. Basically it is the value stored inside the * Maven property named <code>project.basedir</code>. * * <p>It is an attribute defined as: <pre><code> * <span>/</span>* <span>@</span>parameter expression="${project.basedir}" * <span>*</span>/ * private File baseDirectory; * </code></pre> * * @return the root directory. */ public abstract File getBaseDirectory(); /** Replies the build context that may be used during Mojo execution. * This build context permits to be used inside and outside the * Eclipse IDE. * * @return the build context. */ public abstract BuildContext getBuildContext(); /** * Replies the current maven session. Basically it is an internal component of Maven. * * <p>It is an attribute defined as: <pre><code> * <span>/</span>* <span>@</span>component role="org.apache.maven.project.MavenProjectBuilder" * * <span>@</span>required * * <span>@</span>readonly * <span>*</span>/ * private MavenProjectBuilder projectBuilder; * </code></pre> * * @return the maven session */ public abstract MavenProjectBuilder getMavenProjectBuilder(); /** * Replies the current project builder. Basically it is an internal component of Maven. * * <p>It is an attribute defined as: <pre><code> * <span>/</span>* <span>@</span>parameter expression="${session}" * * <span>@</span>required * <span>*</span>/ * private MavenSession mvnSession; * </code></pre> * * @return the maven session */ public abstract MavenSession getMavenSession(); /** * Search and reply the maven artifact which is corresponding to the given file. * * @param file * is the file for which the maven artifact should be retreived. * @return the maven artifact or <code>null</code> if none. */ public final synchronized ExtendedArtifact searchArtifact(File file) { final String filename = removePathPrefix(getBaseDirectory(), file); getLog().debug("Retreiving module for " + filename); //$NON-NLS-1$ File theFile = file; File pomDirectory = null; while (theFile != null && pomDirectory == null) { if (theFile.isDirectory()) { final File pomFile = new File(theFile, "pom.xml"); //$NON-NLS-1$ if (pomFile.exists()) { pomDirectory = theFile; } } theFile = theFile.getParentFile(); } if (pomDirectory != null) { ExtendedArtifact a = this.localArtifactDescriptions.get(pomDirectory); if (a == null) { a = readPom(pomDirectory); this.localArtifactDescriptions.put(pomDirectory, a); getLog().debug("Found local module description for " //$NON-NLS-1$ + a.toString()); } return a; } final BuildContext buildContext = getBuildContext(); buildContext.addMessage(file, 1, 1, "The maven module for this file cannot be retreived.", //$NON-NLS-1$ BuildContext.SEVERITY_WARNING, null); return null; } /** * Replies the project's remote repositories to use for the resolution of plugins and their dependencies.. * * <p>It is an attribute defined as: <pre><code> * <span>/</span>* <span>@</span>parameter default-value="${project.remoteProjectRepositories}" * <span>*</span>/ * private List<RemoteRepository> remoteRepos; * </code></pre> * * @return the repository system */ public abstract List<RemoteRepository> getRemoteRepositoryList(); /** * Replies the repository system used by this maven instance. Basically it is an internal component of Maven. * * <p>It is an attribute defined as: <pre><code> * <span>/</span>* <span>@</span>component * <span>*</span>/ * private RepositorySystem repoSystem; * </code></pre> * * @return the repository system */ public abstract RepositorySystem getRepositorySystem(); /** * Replies the current repository/network configuration of Maven.. * * <p>It is an attribute defined as: <pre><code> * <span>/</span>* <span>@</span>parameter default-value="${repositorySystemSession}" * <span>@</span>readonly * <span>*</span>/ * private RepositorySystemSession repoSession; * </code></pre> * * @return the repository system */ public abstract RepositorySystemSession getRepositorySystemSession(); /** * Retreive the extended artifact definition of the given artifact. * @param mavenArtifact - the artifact to resolve * @return the artifact definition. * @throws MojoExecutionException on error. */ public final Artifact resolveArtifact(Artifact mavenArtifact) throws MojoExecutionException { final org.eclipse.aether.artifact.Artifact aetherArtifact = createArtifact(mavenArtifact); final ArtifactRequest request = new ArtifactRequest(); request.setArtifact(aetherArtifact); request.setRepositories(getRemoteRepositoryList()); final ArtifactResult result; try { result = getRepositorySystem().resolveArtifact(getRepositorySystemSession(), request); } catch (ArtifactResolutionException e) { throw new MojoExecutionException(e.getMessage(), e); } return createArtifact(result.getArtifact()); } /** * Retreive the extended artifact definition of the given artifact id. * * @param groupId * is the identifier of the group. * @param artifactId * is the identifier of the artifact. * @param version * is the version of the artifact to retreive. * @return the artifact definition. * @throws MojoExecutionException on error. */ public final Artifact resolveArtifact(String groupId, String artifactId, String version) throws MojoExecutionException { return resolveArtifact(createArtifact(groupId, artifactId, version)); } /** * Replies a list of files which are found on the file system. * * @param directory * is the directory to search in. * @param filter * is the file selector * @return the list of files. */ public final Collection<File> findFiles(File directory, FileFilter filter) { final Collection<File> files = new ArrayList<>(); findFiles(directory, filter, files); return files; } /** * Replies a list of files which are found on the file system. * * @param directory * is the directory to search in. * @param filter * is the file selector * @param fileOut * is the list of files to fill. */ public final synchronized void findFiles(File directory, FileFilter filter, Collection<? super File> fileOut) { if (directory != null && filter != null) { File candidate; final List<File> candidates = new ArrayList<>(); final String relativePath = removePathPrefix(getBaseDirectory(), directory); getLog().debug("Retreiving " //$NON-NLS-1$ + filter.toString() + " files from " //$NON-NLS-1$ + relativePath); candidates.add(directory); int nbFiles = 0; while (!candidates.isEmpty()) { candidate = candidates.remove(0); if (candidate.isDirectory()) { final File[] children = candidate.listFiles(filter); if (children != null) { for (final File child : children) { if (child != null && child.isDirectory()) { candidates.add(child); } else { fileOut.add(child); ++nbFiles; } } } } } getLog().debug("Found " //$NON-NLS-1$ + nbFiles + " file(s)"); //$NON-NLS-1$ } } /** * Replies a map of files which are found on the file system. The map has the * found files as keys and the search directory as values. * * @param directory * is the directory to search in. * @param filter * is the file selector * @param fileOut * is the list of files to fill. */ public final synchronized void findFiles(File directory, FileFilter filter, Map<? super File, File> fileOut) { findFiles(directory, filter, fileOut, null); } /** * Replies a map of files which are found on the file system. The map has the * found files as keys and the search directory as values. * * @param directory * is the directory to search in. * @param filter * is the file selector * @param fileOut * is the list of files to fill. * @param listener on the files that are not matching the file filter. */ public final synchronized void findFiles(File directory, FileFilter filter, Map<? super File, File> fileOut, FindFileListener listener) { if (directory != null && filter != null) { File candidate; final List<File> candidates = new ArrayList<>(); final String relativePath = removePathPrefix(getBaseDirectory(), directory); getLog().debug("Retreiving " //$NON-NLS-1$ + filter.toString() + " files from " //$NON-NLS-1$ + relativePath); candidates.add(directory); int nbFiles = 0; while (!candidates.isEmpty()) { candidate = candidates.remove(0); if (candidate.isDirectory()) { final File[] children = candidate.listFiles(); if (children != null) { for (final File child : children) { if (child != null && child.isDirectory()) { candidates.add(child); } else if (filter.accept(child)) { fileOut.put(child, directory); ++nbFiles; } else if (listener != null) { listener.findFile(child, directory); } } } } } getLog().debug("Found " //$NON-NLS-1$ + nbFiles + " file(s)"); //$NON-NLS-1$ } } /** * Replies the maven artifact which is described by the <code>pom.xml</code> file in the given directory. * * @param pomDirectory * is the directory where to find the <code>pom.xml</code> file. * @return the artifact or <code>null</code>. */ public final synchronized ExtendedArtifact readPom(File pomDirectory) { return readPomFile(new File(pomDirectory, "pom.xml")); //$NON-NLS-1$ } /** * Replies the maven artifact which is described by the given <code>pom.xml</code>. * * @param pomFile * is the <code>pom.xml</code> file. * @return the artifact or <code>null</code>. */ @SuppressWarnings({"checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity", "checkstyle:nestedifdepth"}) public final synchronized ExtendedArtifact readPomFile(File pomFile) { String groupId; final String artifactId; final String name; String version; final String url; final Organization organization; final Scm scm; List<Developer> developers; List<Contributor> contributors; List<License> licenses; final Parent parent; getLog().debug("Read pom file: " + pomFile.toString()); //$NON-NLS-1$ if (!pomFile.canRead()) { return null; } final MavenXpp3Reader pomReader = new MavenXpp3Reader(); try (FileReader fr = new FileReader(pomFile)) { final Model model = pomReader.read(fr); groupId = model.getGroupId(); artifactId = model.getArtifactId(); name = model.getName(); version = model.getVersion(); url = model.getUrl(); organization = model.getOrganization(); scm = model.getScm(); developers = model.getDevelopers(); contributors = model.getContributors(); licenses = model.getLicenses(); parent = model.getParent(); } catch (IOException | XmlPullParserException e) { return null; } if (developers == null) { developers = new ArrayList<>(); } else { final List<Developer> list = new ArrayList<>(); list.addAll(developers); developers = list; } if (contributors == null) { contributors = new ArrayList<>(); } else { final List<Contributor> list = new ArrayList<>(); list.addAll(contributors); contributors = list; } if (licenses == null) { licenses = new ArrayList<>(); } else { final List<License> list = new ArrayList<>(); list.addAll(licenses); licenses = list; } if (parent != null) { final String relPath = parent.getRelativePath(); File parentPomDirectory = new File(pomFile.getParentFile(), relPath); try { parentPomDirectory = parentPomDirectory.getCanonicalFile(); if (!parentPomDirectory.isDirectory()) { parentPomDirectory = parentPomDirectory.getParentFile(); } ExtendedArtifact parentArtifact = this.localArtifactDescriptions.get(parentPomDirectory); if (parentArtifact == null) { parentArtifact = readPom(parentPomDirectory); if (parentArtifact != null) { this.localArtifactDescriptions.put(parentPomDirectory, parentArtifact); getLog().debug("Add local module description for " //$NON-NLS-1$ + parentArtifact.toString()); } else { final String key = ArtifactUtils.key(parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); final Artifact artifact = createArtifact(parent.getGroupId(), parent.getArtifactId(), parent.getVersion()); final ArtifactRepository repo = getMavenSession().getLocalRepository(); String artifactPath = repo.pathOf(artifact); artifactPath = artifactPath.replaceFirst("\\.jar$", ".pom"); //$NON-NLS-1$ //$NON-NLS-2$ final File artifactFile = new File(repo.getBasedir(), artifactPath); getLog().debug("Getting pom file in local repository for " //$NON-NLS-1$ + key + ": " + artifactFile.getAbsolutePath()); //$NON-NLS-1$ final BuildContext buildContext = getBuildContext(); buildContext.removeMessages(pomFile); if (artifactFile.canRead()) { parentArtifact = readPomFile(artifactFile); if (parentArtifact != null) { this.remoteArtifactDescriptions.put(key, parentArtifact); getLog().debug("Add remote module description for " //$NON-NLS-1$ + parentArtifact.toString()); } else { buildContext.addMessage( pomFile, 1, 1, "Unable to retreive the pom file of " + key, //$NON-NLS-1$ BuildContext.SEVERITY_WARNING, null); } } else { buildContext.addMessage( pomFile, 1, 1, "Cannot read the file for '" + key + "': " //$NON-NLS-1$ //$NON-NLS-2$ + artifactFile.getAbsolutePath(), BuildContext.SEVERITY_WARNING, null); } } } if (parentArtifact != null) { developers.addAll(parentArtifact.getDevelopers()); contributors.addAll(parentArtifact.getContributors()); } } catch (IOException e) { getLog().warn(e); } // Be sure that the optional fields version and groupId are correctly set. if (version == null || version.isEmpty()) { version = parent.getVersion(); } if (groupId == null || groupId.isEmpty()) { groupId = parent.getGroupId(); } } String scmRevision = null; try { final SVNClientManager svnManager = getSVNClientManager(); final SVNInfo svnInfo = svnManager.getWCClient().doInfo(pomFile.getParentFile(), SVNRevision.UNDEFINED); if (svnInfo != null) { final SVNRevision revision = svnInfo.getRevision(); if (revision != null) { scmRevision = Long.toString(revision.getNumber()); } } } catch (SVNException exception) { // } final Artifact a = createArtifact(groupId, artifactId, version); return new ExtendedArtifact(a, name, url, organization, scmRevision, scm, developers, contributors, licenses); } /** * Create an Jar runtime artifact from the given values. * * @param groupId group id. * @param artifactId artifact id. * @param version version number. * @return the artifact */ public final Artifact createArtifact(String groupId, String artifactId, String version) { return createArtifact(groupId, artifactId, version, "runtime", "jar"); //$NON-NLS-1$ //$NON-NLS-2$ } /** Convert the maven artifact to Aether artifact. * * @param artifact - the maven artifact. * @return the Aether artifact. */ protected static final org.eclipse.aether.artifact.Artifact createArtifact(Artifact artifact) { return new DefaultArtifact( artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getType(), artifact.getVersion()); } /** Convert the Aether artifact to maven artifact. * * @param artifact - the Aether artifact. * @return the maven artifact. */ protected final Artifact createArtifact(org.eclipse.aether.artifact.Artifact artifact) { return createArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getVersion()); } /** * Create an artifact from the given values. * * @param groupId group id. * @param artifactId artifact id. * @param version version number. * @param scope artifact scope. * @param type artifact type. * @return the artifact */ public final Artifact createArtifact(String groupId, String artifactId, String version, String scope, String type) { VersionRange versionRange = null; if (version != null) { versionRange = VersionRange.createFromVersion(version); } String desiredScope = scope; if (Artifact.SCOPE_TEST.equals(desiredScope)) { desiredScope = Artifact.SCOPE_TEST; } if (Artifact.SCOPE_PROVIDED.equals(desiredScope)) { desiredScope = Artifact.SCOPE_PROVIDED; } if (Artifact.SCOPE_SYSTEM.equals(desiredScope)) { // system scopes come through unchanged... desiredScope = Artifact.SCOPE_SYSTEM; } final ArtifactHandler handler = getArtifactHandlerManager().getArtifactHandler(type); return new org.apache.maven.artifact.DefaultArtifact( groupId, artifactId, versionRange, desiredScope, type, null, handler, false); } /** * Check if the values of the attributes of this Mojo are correctly set. This function may * be overridden by subclasses to test subclasse's attributes. * * @throws MojoExecutionException on error. */ protected abstract void checkMojoAttributes() throws MojoExecutionException; @SuppressWarnings({"checkstyle:returncount", "checkstyle:cyclomaticcomplexity", "checkstyle:npathcomplexity"}) private static String getLogType(Object obj) { if (obj instanceof Boolean || obj instanceof AtomicBoolean) { return "B"; //$NON-NLS-1$ } if (obj instanceof Byte) { return "b"; //$NON-NLS-1$ } if (obj instanceof Short) { return "s"; //$NON-NLS-1$ } if (obj instanceof Integer || obj instanceof AtomicInteger) { return "i"; //$NON-NLS-1$ } if (obj instanceof Long || obj instanceof AtomicLong) { return "l"; //$NON-NLS-1$ } if (obj instanceof Float) { return "f"; //$NON-NLS-1$ } if (obj instanceof Double) { return "d"; //$NON-NLS-1$ } if (obj instanceof BigDecimal) { return "D"; //$NON-NLS-1$ } if (obj instanceof BigInteger) { return "I"; //$NON-NLS-1$ } if (obj instanceof CharSequence) { return "s"; //$NON-NLS-1$ } if (obj instanceof Array) { final Array array = (Array) obj; return array.getClass().getComponentType().getName() + "[]"; //$NON-NLS-1$ } if (obj instanceof Set<?>) { return "set"; //$NON-NLS-1$ } if (obj instanceof Map<?, ?>) { return "map"; //$NON-NLS-1$ } if (obj instanceof List<?>) { return "list"; //$NON-NLS-1$ } if (obj instanceof Collection<?>) { return "col"; //$NON-NLS-1$ } return "o"; //$NON-NLS-1$ } /** * Throw an exception when the given object is null. * * @param message * is the message to put in the exception. * @param obj the object to test. */ protected final void assertNotNull(String message, Object obj) { if (getLog().isDebugEnabled()) { getLog().debug( "\t(" //$NON-NLS-1$ + getLogType(obj) + ") " //$NON-NLS-1$ + message + " = " //$NON-NLS-1$ + obj); } if (obj == null) { throw new AssertionError("assertNotNull: " + message); //$NON-NLS-1$ } } @Override public final void execute() throws MojoExecutionException { try { checkMojoAttributes(); executeMojo(); } finally { clearInternalBuffers(); } } /** * Clear internal buffers. */ protected synchronized void clearInternalBuffers() { this.localArtifactDescriptions.clear(); this.remoteArtifactDescriptions.clear(); } /** * Invoked when the Mojo should be executed. * * @throws MojoExecutionException on error. */ protected abstract void executeMojo() throws MojoExecutionException; /** Join the values with the given joint. * * @param joint the joint. * @param values the values. * @return the jointed values */ public static String join(String joint, String... values) { final StringBuilder b = new StringBuilder(); for (final String value : values) { if (value != null && !EMPTY_STRING.equals(value)) { if (b.length() > 0) { b.append(joint); } b.append(value); } } return b.toString(); } private static void detectEncoding(File file, CharsetDecoder decoder) throws IOException, CharacterCodingException { decoder.onMalformedInput(CodingErrorAction.REPORT); decoder.onUnmappableCharacter(CodingErrorAction.REPORT); try (FileInputStream fis = new FileInputStream(file)) { try (ReadableByteChannel channel = Channels.newChannel(fis)) { try (Reader reader = Channels.newReader(channel, decoder, -1)) { try (BufferedReader bReader = new BufferedReader(reader)) { String line = bReader.readLine(); while (line != null) { line = bReader.readLine(); } } } } } } /** * Try to detect and reply the encoding of the given file. This function uses the * charsets replied by {@link #getPreferredCharsets()} to select a charset when many are possible. * * @param file * is the file to read. * @return the encoding charset of the given file or <code>null</code> if the encoding could not be detected. * @see #getPreferredCharsets() * @see #setPreferredCharsets(Charset...) */ @SuppressWarnings("checkstyle:npathcomplexity") public final Charset detectEncoding(File file) { final Collection<Charset> fittingCharsets = new TreeSet<>(); for (final Charset c : Charset.availableCharsets().values()) { final CharsetDecoder decoder = c.newDecoder(); try { detectEncoding(file, decoder); fittingCharsets.add(c); } catch (Throwable e) { // } } if (getLog().isDebugEnabled()) { getLog().debug("Valid charsets for " + file.getName() + ":\n" //$NON-NLS-1$ //$NON-NLS-2$ + fittingCharsets.toString()); } for (final Charset prefCharset : getPreferredCharsets()) { if (prefCharset.canEncode() && fittingCharsets.contains(prefCharset)) { getLog().debug("Use preferred charset for " + file.getName() //$NON-NLS-1$ + ": " + prefCharset.displayName()); //$NON-NLS-1$ return prefCharset; } } final Charset platformCharset = Charset.defaultCharset(); if (platformCharset.canEncode() && fittingCharsets.contains(platformCharset)) { getLog().debug("Use platform default charset for " + file.getName() + ": " //$NON-NLS-1$ //$NON-NLS-2$ + platformCharset.displayName()); return Charset.defaultCharset(); } final Iterator<Charset> iterator = fittingCharsets.iterator(); while (iterator.hasNext()) { final Charset c = iterator.next(); if (c.canEncode()) { getLog().debug("Use first valid charset for " + file.getName() + ": " //$NON-NLS-1$ //$NON-NLS-2$ + c.displayName()); return c; } } return null; } /** Replies the dependencies specified in the the Maven configuration * of the current project. * * @param isTransitive indicates if the dependencies of dependencies * must also be replied by the iterator. * @return the iterator. * @see #getDependencies(MavenProject, boolean) */ public final Iterator<MavenProject> getDependencies(boolean isTransitive) { return getDependencies(getMavenSession().getCurrentProject(), isTransitive); } /** Replies the dependencies specified in the the Maven configuration * of the given project. * * @param project is the maven project for which the dependencies must be replied. * @param isTransitive indicates if the dependencies of dependencies * must also be replied by the iterator. * @return the iterator. * @see #getDependencies(boolean) */ public final Iterator<MavenProject> getDependencies(MavenProject project, boolean isTransitive) { return new DependencyIterator(project, isTransitive); } /** Replies the plugins specified in the the Maven configuration * of the current project. * * @param isTransitive indicates if the plugins of dependencies * must also be replied by the iterator. * @return the iterator. * @see #getPlugins(MavenProject, boolean) */ public final Iterator<Plugin> getPlugins(boolean isTransitive) { return getPlugins(getMavenSession().getCurrentProject(), isTransitive); } /** Replies the plugins specified in the the Maven configuration * of the given project. * * @param project is the maven project for which the plugins must be replied. * @param isTransitive indicates if the plugins of dependencies * must also be replied by the iterator. * @return the iterator. * @see #getPlugins(boolean) */ public final Iterator<Plugin> getPlugins(MavenProject project, boolean isTransitive) { return new PluginIterator(project, isTransitive); } /** Load the Maven project for the given artifact. * * @param artifact the artifact. * @return the maven project. */ public MavenProject getMavenProject(Artifact artifact) { try { final MavenSession session = getMavenSession(); final MavenProject current = session.getCurrentProject(); final MavenProject prj = getMavenProjectBuilder().buildFromRepository( artifact, current.getRemoteArtifactRepositories(), session.getLocalRepository()); return prj; } catch (ProjectBuildingException e) { getLog().warn(e); } return null; } /** Dependency iterator. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class DependencyIterator implements Iterator<MavenProject> { private final List<ArtifactRepository> remoteRepositiories; private final boolean isTransitive; private final File projectFile; private List<Dependency> dependencies = new ArrayList<>(); private Set<String> treated = new TreeSet<>(); private MavenProject next; /** * @param project is the project for which the dependencies must * be replied. * @param isTransitive indicates if the dependencies of dependencies must also be replied * by the iterator. */ DependencyIterator(MavenProject project, boolean isTransitive) { this.isTransitive = isTransitive; this.remoteRepositiories = project.getRemoteArtifactRepositories(); this.dependencies.addAll(project.getDependencies()); this.projectFile = project.getFile(); getBuildContext().removeMessages(this.projectFile); searchNext(); } private void searchNext() { this.next = null; while (this.next == null && !this.dependencies.isEmpty()) { final Dependency dependency = this.dependencies.remove(0); if (dependency != null) { final String artifactId = dependency.getGroupId() + ":" + dependency.getArtifactId() //$NON-NLS-1$ + ":" + dependency.getVersion(); //$NON-NLS-1$ if (!this.treated.contains(artifactId)) { boolean isTreated = false; try { final Artifact dependencyArtifact = createArtifact( dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), dependency.getScope(), dependency.getType()); resolveArtifact(dependencyArtifact); final MavenProjectBuilder builder = getMavenProjectBuilder(); final MavenProject dependencyProject = builder.buildFromRepository( dependencyArtifact, this.remoteRepositiories, getMavenSession().getLocalRepository()); if (dependencyProject != null) { if (this.isTransitive) { this.dependencies.addAll(dependencyProject.getDependencies()); } this.next = dependencyProject; isTreated = true; } } catch (MojoExecutionException | ProjectBuildingException e) { getBuildContext().addMessage( this.projectFile, 1, 1, "Unable to retreive the Maven plugin: " + artifactId, //$NON-NLS-1$ BuildContext.SEVERITY_WARNING, e); isTreated = true; } if (isTreated) { this.treated.add(artifactId); } } } } } @Override public boolean hasNext() { return this.next != null; } @Override public MavenProject next() { final MavenProject n = this.next; if (n == null) { throw new NoSuchElementException(); } searchNext(); return n; } } /** Plugin iterator. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ */ private class PluginIterator implements Iterator<Plugin> { private final Iterator<MavenProject> dependencyIterator; private Iterator<org.apache.maven.model.Plugin> pluginIterator; private Plugin next; /** * @param project the project. * @param isTransitive indicates if the dependency is transitive. */ PluginIterator(MavenProject project, boolean isTransitive) { this.dependencyIterator = getDependencies(project, isTransitive); searchNext(); } private void searchNext() { this.next = null; while (this.next == null) { if (this.pluginIterator != null && this.pluginIterator.hasNext()) { this.next = this.pluginIterator.next(); } else if (this.dependencyIterator.hasNext()) { final MavenProject project = this.dependencyIterator.next(); final List<Plugin> buildPlugins = project.getBuildPlugins(); if (buildPlugins != null) { this.pluginIterator = buildPlugins.iterator(); } } else { return; } } } @Override public boolean hasNext() { return this.next != null; } @Override public Plugin next() { final org.apache.maven.model.Plugin n = this.next; if (n == null) { throw new NoSuchElementException(); } searchNext(); return n; } } /** * Abstract implementation for all Arakhnê maven modules. This implementation is thread safe. * * @author $Author: sgalland$ * @version $FullVersion$ * @mavengroupid $GroupId$ * @mavenartifactid $ArtifactId$ * * @component */ @FunctionalInterface public interface FindFileListener extends EventListener { /** Invoked when a file which is not matching the file filter was found. * * @param file is the file that is not matching the file filter. * @param rootDirectory is the root directory in which the file was found. */ void findFile(File file, File rootDirectory); } }