Java tutorial
package org.codehaus.mojo.versions.api; /* * 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.maven.artifact.Artifact; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; import org.apache.maven.artifact.versioning.VersionRange; import org.apache.maven.model.Dependency; import org.apache.maven.model.Model; import org.apache.maven.model.Parent; import org.apache.maven.model.Plugin; import org.apache.maven.model.Profile; import org.apache.maven.model.ReportPlugin; import org.apache.maven.model.io.xpp3.MavenXpp3Reader; import org.apache.maven.plugin.logging.Log; import org.apache.maven.profiles.ProfileManager; import org.apache.maven.project.MavenProject; import org.apache.maven.project.MavenProjectBuilder; import org.apache.maven.project.ProjectBuildingException; import org.codehaus.mojo.versions.rewriting.ModifiedPomXMLEventReader; import org.codehaus.mojo.versions.utils.RegexUtils; import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException; import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator; import org.codehaus.plexus.util.IOUtil; import org.codehaus.plexus.util.ReaderFactory; import org.codehaus.plexus.util.StringUtils; import org.codehaus.plexus.util.xml.pull.XmlPullParserException; import javax.xml.stream.XMLStreamException; import javax.xml.stream.events.XMLEvent; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.Stack; import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Helper class for modifying pom files. * * @author Stephen Connolly * @since 1.0-alpha-3 */ public class PomHelper { public static final String APACHE_MAVEN_PLUGINS_GROUPID = "org.apache.maven.plugins"; /** * Gets the raw model before any interpolation what-so-ever. * * @param project The project to get the raw model for. * @return The raw model. * @throws IOException if the file is not found or if the file does not parse. */ public static Model getRawModel(MavenProject project) throws IOException { return getRawModel(project.getFile()); } /** * Gets the raw model before any interpolation what-so-ever. * * @param moduleProjectFile The project file to get the raw model for. * @return The raw model. * @throws IOException if the file is not found or if the file does not parse. */ public static Model getRawModel(File moduleProjectFile) throws IOException { FileReader fileReader = null; BufferedReader bufferedReader = null; try { fileReader = new FileReader(moduleProjectFile); bufferedReader = new BufferedReader(fileReader); MavenXpp3Reader reader = new MavenXpp3Reader(); return reader.read(bufferedReader); } catch (XmlPullParserException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } finally { if (bufferedReader != null) { bufferedReader.close(); } if (fileReader != null) { fileReader.close(); } } } /** * Gets the current raw model before any interpolation what-so-ever. * * @param modifiedPomXMLEventReader The {@link ModifiedPomXMLEventReader} to get the raw model for. * @return The raw model. * @throws IOException if the file is not found or if the file does not parse. */ public static Model getRawModel(ModifiedPomXMLEventReader modifiedPomXMLEventReader) throws IOException { StringReader stringReader = null; try { stringReader = new StringReader(modifiedPomXMLEventReader.asStringBuilder().toString()); MavenXpp3Reader reader = new MavenXpp3Reader(); return reader.read(stringReader); } catch (XmlPullParserException e) { IOException ioe = new IOException(e.getMessage()); ioe.initCause(e); throw ioe; } finally { if (stringReader != null) { stringReader.close(); } } } /** * Searches the pom re-defining the specified property to the specified version. * * @param pom The pom to modify. * @param profileId The profile in which to modify the property. * @param property The property to modify. * @param value The new value of the property. * @return <code>true</code> if a replacement was made. * @throws XMLStreamException if somethinh went wrong. */ public static boolean setPropertyVersion(final ModifiedPomXMLEventReader pom, final String profileId, final String property, final String value) throws XMLStreamException { Stack<String> stack = new Stack<String>(); String path = ""; final Pattern propertyRegex; final Pattern matchScopeRegex; final Pattern projectProfileId; boolean inMatchScope = false; boolean madeReplacement = false; if (profileId == null) { propertyRegex = Pattern.compile("/project/properties/" + RegexUtils.quote(property)); matchScopeRegex = Pattern.compile("/project/properties"); projectProfileId = null; } else { propertyRegex = Pattern.compile("/project/profiles/profile/properties/" + RegexUtils.quote(property)); matchScopeRegex = Pattern.compile("/project/profiles/profile"); projectProfileId = Pattern.compile("/project/profiles/profile/id"); } pom.rewind(); while (pom.hasNext()) { XMLEvent event = pom.nextEvent(); if (event.isStartElement()) { stack.push(path); path = path + "/" + event.asStartElement().getName().getLocalPart(); if (propertyRegex.matcher(path).matches()) { pom.mark(0); } else if (matchScopeRegex.matcher(path).matches()) { // we're in a new match scope // reset any previous partial matches inMatchScope = profileId == null; pom.clearMark(0); pom.clearMark(1); } else if (profileId != null && projectProfileId.matcher(path).matches()) { String candidateId = pom.getElementText(); path = stack.pop(); // since getElementText will be after the end element inMatchScope = profileId.trim().equals(candidateId.trim()); } } if (event.isEndElement()) { if (propertyRegex.matcher(path).matches()) { pom.mark(1); } else if (matchScopeRegex.matcher(path).matches()) { if (inMatchScope && pom.hasMark(0) && pom.hasMark(1)) { pom.replaceBetween(0, 1, value); madeReplacement = true; } pom.clearMark(0); pom.clearMark(1); inMatchScope = false; } path = stack.pop(); } } return madeReplacement; } /** * Searches the pom re-defining the project version to the specified version. * * @param pom The pom to modify. * @param value The new value of the property. * @return <code>true</code> if a replacement was made. * @throws XMLStreamException if somethinh went wrong. */ public static boolean setProjectVersion(final ModifiedPomXMLEventReader pom, final String value) throws XMLStreamException { Stack<String> stack = new Stack<String>(); String path = ""; final Pattern matchScopeRegex; boolean madeReplacement = false; matchScopeRegex = Pattern.compile("/project/version"); pom.rewind(); while (pom.hasNext()) { XMLEvent event = pom.nextEvent(); if (event.isStartElement()) { stack.push(path); path = path + "/" + event.asStartElement().getName().getLocalPart(); if (matchScopeRegex.matcher(path).matches()) { pom.mark(0); } } if (event.isEndElement()) { if (matchScopeRegex.matcher(path).matches()) { pom.mark(1); if (pom.hasMark(0) && pom.hasMark(1)) { pom.replaceBetween(0, 1, value); madeReplacement = true; } pom.clearMark(0); pom.clearMark(1); } path = stack.pop(); } } return madeReplacement; } /** * Retrieves the project version from the pom. * * @param pom The pom. * @return the project version or <code>null</code> if the project version is not defined (i.e. inherited from parent version). * @throws XMLStreamException if something went wrong. */ public static String getProjectVersion(final ModifiedPomXMLEventReader pom) throws XMLStreamException { Stack<String> stack = new Stack<String>(); String path = ""; final Pattern matchScopeRegex = Pattern.compile("/project/version"); pom.rewind(); while (pom.hasNext()) { XMLEvent event = pom.nextEvent(); if (event.isStartElement()) { stack.push(path); path = path + "/" + event.asStartElement().getName().getLocalPart(); if (matchScopeRegex.matcher(path).matches()) { pom.mark(0); } } if (event.isEndElement()) { if (matchScopeRegex.matcher(path).matches()) { pom.mark(1); if (pom.hasMark(0) && pom.hasMark(1)) { return pom.getBetween(0, 1).trim(); } pom.clearMark(0); pom.clearMark(1); } path = stack.pop(); } } return null; } /** * Searches the pom re-defining the project version to the specified version. * * @param pom The pom to modify. * @param value The new value of the property. * @return <code>true</code> if a replacement was made. * @throws XMLStreamException if somethinh went wrong. */ public static boolean setProjectParentVersion(final ModifiedPomXMLEventReader pom, final String value) throws XMLStreamException { Stack<String> stack = new Stack<String>(); String path = ""; final Pattern matchScopeRegex; boolean madeReplacement = false; matchScopeRegex = Pattern.compile("/project/parent/version"); pom.rewind(); while (pom.hasNext()) { XMLEvent event = pom.nextEvent(); if (event.isStartElement()) { stack.push(path); path = path + "/" + event.asStartElement().getName().getLocalPart(); if (matchScopeRegex.matcher(path).matches()) { pom.mark(0); } } if (event.isEndElement()) { if (matchScopeRegex.matcher(path).matches()) { pom.mark(1); if (pom.hasMark(0) && pom.hasMark(1)) { pom.replaceBetween(0, 1, value); madeReplacement = true; } pom.clearMark(0); pom.clearMark(1); } path = stack.pop(); } } return madeReplacement; } /** * Gets the parent artifact from the pom. * * @param pom The pom. * @param helper The helper (used to create the artifact). * @return The parent artifact or <code>null</code> if no parent is specified. * @throws XMLStreamException if something went wrong. */ public static Artifact getProjectParent(final ModifiedPomXMLEventReader pom, VersionsHelper helper) throws XMLStreamException { Stack<String> stack = new Stack<String>(); String path = ""; final Pattern matchScopeRegex = Pattern.compile("/project/parent((/groupId)|(/artifactId)|(/version))"); String groupId = null; String artifactId = null; String version = null; pom.rewind(); while (pom.hasNext()) { XMLEvent event = pom.nextEvent(); if (event.isStartElement()) { stack.push(path); final String elementName = event.asStartElement().getName().getLocalPart(); path = path + "/" + elementName; if (matchScopeRegex.matcher(path).matches()) { if ("groupId".equals(elementName)) { groupId = pom.getElementText().trim(); path = stack.pop(); } else if ("artifactId".equals(elementName)) { artifactId = pom.getElementText().trim(); path = stack.pop(); } else if ("version".equals(elementName)) { version = pom.getElementText().trim(); path = stack.pop(); } } } if (event.isEndElement()) { path = stack.pop(); } } if (groupId == null || artifactId == null || version == null) { return null; } return helper.createDependencyArtifact(groupId, artifactId, VersionRange.createFromVersion(version), "pom", null, null, false); } /** * Searches the pom re-defining the specified dependency to the specified version. * * @param pom The pom to modify. * @param groupId The groupId of the dependency. * @param artifactId The artifactId of the dependency. * @param oldVersion The old version of the dependency. * @param newVersion The new version of the dependency. * @return <code>true</code> if a replacement was made. * @throws XMLStreamException if somethinh went wrong. */ public static boolean setDependencyVersion(final ModifiedPomXMLEventReader pom, final String groupId, final String artifactId, final String oldVersion, final String newVersion) throws XMLStreamException { Stack<String> stack = new Stack<String>(); String path = ""; Set<String> implicitPaths = new HashSet<String>( Arrays.<String>asList("/project/parent/groupId", "/project/parent/artifactId", "/project/parent/version", "/project/groupId", "/project/artifactId", "/project/version")); Map<String, String> implicitProperties = new HashMap<String, String>(); pom.rewind(); while (pom.hasNext()) { while (pom.hasNext()) { XMLEvent event = pom.nextEvent(); if (event.isStartElement()) { stack.push(path); final String elementName = event.asStartElement().getName().getLocalPart(); path = path + "/" + elementName; if (implicitPaths.contains(path)) { final String elementText = pom.getElementText().trim(); implicitProperties.put(path.substring(1).replace('/', '.'), elementText); path = stack.pop(); } } if (event.isEndElement()) { path = stack.pop(); } } } boolean modified = true; while (modified) { modified = false; for (Map.Entry<String, String> entry : implicitProperties.entrySet()) { if (entry.getKey().contains(".parent")) { String child = entry.getKey().replace(".parent", ""); if (!implicitProperties.containsKey(child)) { implicitProperties.put(child, entry.getValue()); modified = true; break; } } } } stack = new Stack<String>(); path = ""; boolean inMatchScope = false; boolean madeReplacement = false; boolean haveGroupId = false; boolean haveArtifactId = false; boolean haveOldVersion = false; final Pattern matchScopeRegex = Pattern.compile("/project" + "(/profiles/profile)?" + "((/dependencyManagement)|(/build(/pluginManagement)?/plugins/plugin))?" + "/dependencies/dependency"); final Pattern matchTargetRegex = Pattern.compile("/project" + "(/profiles/profile)?" + "((/dependencyManagement)|(/build(/pluginManagement)?/plugins/plugin))?" + "/dependencies/dependency" + "((/groupId)|(/artifactId)|(/version))"); pom.rewind(); while (pom.hasNext()) { XMLEvent event = pom.nextEvent(); if (event.isStartElement()) { stack.push(path); final String elementName = event.asStartElement().getName().getLocalPart(); path = path + "/" + elementName; if (matchScopeRegex.matcher(path).matches()) { // we're in a new match scope // reset any previous partial matches inMatchScope = true; pom.clearMark(0); pom.clearMark(1); haveGroupId = false; haveArtifactId = false; haveOldVersion = false; } else if (inMatchScope && matchTargetRegex.matcher(path).matches()) { if ("groupId".equals(elementName)) { haveGroupId = groupId.equals(evaluate(pom.getElementText().trim(), implicitProperties)); path = stack.pop(); } else if ("artifactId".equals(elementName)) { haveArtifactId = artifactId .equals(evaluate(pom.getElementText().trim(), implicitProperties)); path = stack.pop(); } else if ("version".equals(elementName)) { pom.mark(0); } } } if (event.isEndElement()) { if (matchTargetRegex.matcher(path).matches() && "version".equals(event.asEndElement().getName().getLocalPart())) { pom.mark(1); String compressedPomVersion = StringUtils.deleteWhitespace(pom.getBetween(0, 1).trim()); String compressedOldVersion = StringUtils.deleteWhitespace(oldVersion); try { haveOldVersion = isVersionOverlap(compressedOldVersion, compressedPomVersion); } catch (InvalidVersionSpecificationException e) { // fall back to string comparison haveOldVersion = compressedOldVersion.equals(compressedPomVersion); } } else if (matchScopeRegex.matcher(path).matches()) { if (inMatchScope && pom.hasMark(0) && pom.hasMark(1) && haveGroupId && haveArtifactId && haveOldVersion) { pom.replaceBetween(0, 1, newVersion); madeReplacement = true; } pom.clearMark(0); pom.clearMark(1); haveArtifactId = false; haveGroupId = false; haveOldVersion = false; inMatchScope = false; } path = stack.pop(); } } return madeReplacement; } /** * A lightweight expression evaluation function. * * @param expr The expression to evaluate. * @param properties The properties to substitute. * @return The evaluated expression. */ public static String evaluate(String expr, Map<String, String> properties) { if (expr == null) { return null; } String expression = stripTokens(expr); if (expression.equals(expr)) { int index = expr.indexOf("${"); if (index >= 0) { int lastIndex = expr.indexOf("}", index); if (lastIndex >= 0) { String retVal = expr.substring(0, index); if (index > 0 && expr.charAt(index - 1) == '$') { retVal += expr.substring(index + 1, lastIndex + 1); } else { retVal += evaluate(expr.substring(index, lastIndex + 1), properties); } retVal += evaluate(expr.substring(lastIndex + 1), properties); return retVal; } } // Was not an expression if (expression.contains("$$")) { return expression.replaceAll("\\$\\$", "\\$"); } else { return expression; } } String value = properties.get(expression); if (value != null) { int exprStartDelimiter = value.indexOf("${"); if (exprStartDelimiter >= 0) { if (exprStartDelimiter > 0) { value = value.substring(0, exprStartDelimiter) + evaluate(value.substring(exprStartDelimiter), properties); } else { value = evaluate(value.substring(exprStartDelimiter), properties); } } } else { // TODO find a way to log that and not use this System.out!! // this class could be a component with logger injected !! System.out.println("expression: " + expression + " no value "); } return value == null ? expr : value; } /** * Strips the expression token markers from the start and end of the string. * * @param expr the string (perhaps with token markers) * @return the string (definately without token markers) */ private static String stripTokens(String expr) { if (expr.startsWith("${") && expr.indexOf("}") == expr.length() - 1) { expr = expr.substring(2, expr.length() - 1); } return expr; } /** * Checks if two versions or ranges have an overlap. * * @param leftVersionOrRange the 1st version number or range to test * @param rightVersionOrRange the 2nd version number or range to test * @return true if both versions have an overlap * @throws InvalidVersionSpecificationException * if the versions can't be parsed to a range */ public static boolean isVersionOverlap(String leftVersionOrRange, String rightVersionOrRange) throws InvalidVersionSpecificationException { VersionRange pomVersionRange = createVersionRange(leftVersionOrRange); if (!pomVersionRange.hasRestrictions()) { return true; } VersionRange oldVersionRange = createVersionRange(rightVersionOrRange); if (!oldVersionRange.hasRestrictions()) { return true; } VersionRange result = oldVersionRange.restrict(pomVersionRange); return result.hasRestrictions(); } private static VersionRange createVersionRange(String versionOrRange) throws InvalidVersionSpecificationException { VersionRange versionRange = VersionRange.createFromVersionSpec(versionOrRange); if (versionRange.getRecommendedVersion() != null) { versionRange = VersionRange.createFromVersionSpec("[" + versionOrRange + "]"); } return versionRange; } /** * Searches the pom re-defining the specified plugin to the specified version. * * @param pom The pom to modify. * @param groupId The groupId of the dependency. * @param artifactId The artifactId of the dependency. * @param oldVersion The old version of the dependency. * @param newVersion The new version of the dependency. * @return <code>true</code> if a replacement was made. * @throws XMLStreamException if somethinh went wrong. */ public static boolean setPluginVersion(final ModifiedPomXMLEventReader pom, final String groupId, final String artifactId, final String oldVersion, final String newVersion) throws XMLStreamException { Stack<String> stack = new Stack<String>(); String path = ""; final Pattern matchScopeRegex; final Pattern matchTargetRegex; boolean inMatchScope = false; boolean madeReplacement = false; boolean haveGroupId = false; boolean needGroupId = groupId != null && !APACHE_MAVEN_PLUGINS_GROUPID.equals(groupId); boolean haveArtifactId = false; boolean haveOldVersion = false; matchScopeRegex = Pattern.compile( "/project" + "(/profiles/profile)?" + "((/build(/pluginManagement)?)|(/reporting))/plugins/plugin"); matchTargetRegex = Pattern.compile( "/project" + "(/profiles/profile)?" + "((/build(/pluginManagement)?)|(/reporting))/plugins/plugin" + "((/groupId)|(/artifactId)|(/version))"); pom.rewind(); while (pom.hasNext()) { XMLEvent event = pom.nextEvent(); if (event.isStartElement()) { stack.push(path); final String elementName = event.asStartElement().getName().getLocalPart(); path = path + "/" + elementName; if (matchScopeRegex.matcher(path).matches()) { // we're in a new match scope // reset any previous partial matches inMatchScope = true; pom.clearMark(0); pom.clearMark(1); haveGroupId = false; haveArtifactId = false; haveOldVersion = false; } else if (inMatchScope && matchTargetRegex.matcher(path).matches()) { if ("groupId".equals(elementName)) { haveGroupId = groupId.equals(pom.getElementText().trim()); path = stack.pop(); } else if ("artifactId".equals(elementName)) { haveArtifactId = artifactId.equals(pom.getElementText().trim()); path = stack.pop(); } else if ("version".equals(elementName)) { pom.mark(0); } } } if (event.isEndElement()) { if (matchTargetRegex.matcher(path).matches() && "version".equals(event.asEndElement().getName().getLocalPart())) { pom.mark(1); try { haveOldVersion = isVersionOverlap(oldVersion, pom.getBetween(0, 1).trim()); } catch (InvalidVersionSpecificationException e) { // fall back to string comparison haveOldVersion = oldVersion.equals(pom.getBetween(0, 1).trim()); } } else if (matchScopeRegex.matcher(path).matches()) { if (inMatchScope && pom.hasMark(0) && pom.hasMark(1) && (haveGroupId || !needGroupId) && haveArtifactId && haveOldVersion) { pom.replaceBetween(0, 1, newVersion); madeReplacement = true; pom.clearMark(0); pom.clearMark(1); haveArtifactId = false; haveGroupId = false; haveOldVersion = false; } inMatchScope = false; } path = stack.pop(); } } return madeReplacement; } /** * Examines the project to find any properties which are associated with versions of artifacts in the project. * * @param helper Our versions helper. * @param project The project to examine. * @return An array of properties that are associated within the project. * @throws ExpressionEvaluationException if an expression cannot be evaluated. * @throws IOException if the project's pom file cannot be parsed. * @since 1.0-alpha-3 */ public static PropertyVersionsBuilder[] getPropertyVersionsBuilders(VersionsHelper helper, MavenProject project) throws ExpressionEvaluationException, IOException { ExpressionEvaluator expressionEvaluator = helper.getExpressionEvaluator(project); Model model = getRawModel(project); Map<String, PropertyVersionsBuilder> result = new TreeMap<String, PropertyVersionsBuilder>(); Set<String> activeProfiles = new TreeSet<String>(); for (Profile profile : (List<Profile>) project.getActiveProfiles()) { activeProfiles.add(profile.getId()); } // add any properties from profiles first (as they override properties from the project for (Profile profile : model.getProfiles()) { if (!activeProfiles.contains(profile.getId())) { continue; } addProperties(helper, result, profile.getId(), profile.getProperties()); if (profile.getDependencyManagement() != null) { addDependencyAssocations(helper, expressionEvaluator, result, profile.getDependencyManagement().getDependencies(), false); } addDependencyAssocations(helper, expressionEvaluator, result, profile.getDependencies(), false); if (profile.getBuild() != null) { if (profile.getBuild().getPluginManagement() != null) { addPluginAssociations(helper, expressionEvaluator, result, profile.getBuild().getPluginManagement().getPlugins()); } addPluginAssociations(helper, expressionEvaluator, result, profile.getBuild().getPlugins()); } if (profile.getReporting() != null) { addReportPluginAssociations(helper, expressionEvaluator, result, profile.getReporting().getPlugins()); } } // second, we add all the properties in the pom addProperties(helper, result, null, model.getProperties()); if (model.getDependencyManagement() != null) { addDependencyAssocations(helper, expressionEvaluator, result, model.getDependencyManagement().getDependencies(), false); } addDependencyAssocations(helper, expressionEvaluator, result, model.getDependencies(), false); if (model.getBuild() != null) { if (model.getBuild().getPluginManagement() != null) { addPluginAssociations(helper, expressionEvaluator, result, model.getBuild().getPluginManagement().getPlugins()); } addPluginAssociations(helper, expressionEvaluator, result, model.getBuild().getPlugins()); } if (model.getReporting() != null) { addReportPluginAssociations(helper, expressionEvaluator, result, model.getReporting().getPlugins()); } // third, we add any associations from the active profiles for (Profile profile : model.getProfiles()) { if (!activeProfiles.contains(profile.getId())) { continue; } if (profile.getDependencyManagement() != null) { addDependencyAssocations(helper, expressionEvaluator, result, profile.getDependencyManagement().getDependencies(), false); } addDependencyAssocations(helper, expressionEvaluator, result, profile.getDependencies(), false); if (profile.getBuild() != null) { if (profile.getBuild().getPluginManagement() != null) { addPluginAssociations(helper, expressionEvaluator, result, profile.getBuild().getPluginManagement().getPlugins()); } addPluginAssociations(helper, expressionEvaluator, result, profile.getBuild().getPlugins()); } if (profile.getReporting() != null) { addReportPluginAssociations(helper, expressionEvaluator, result, profile.getReporting().getPlugins()); } } // finally, remove any properties without associations purgeProperties(result); return result.values().toArray(new PropertyVersionsBuilder[result.values().size()]); } /** * Takes a list of {@link org.apache.maven.model.Plugin} instances and adds associations to properties used to * define versions of the plugin artifact or any of the plugin dependencies specified in the pom. * * @param helper Our helper. * @param expressionEvaluator Our expression evaluator. * @param result The map of {@link org.codehaus.mojo.versions.api.PropertyVersionsBuilder} keyed by property name. * @param plugins The list of {@link org.apache.maven.model.Plugin}. * @throws ExpressionEvaluationException if an expression cannot be evaluated. */ private static void addPluginAssociations(VersionsHelper helper, ExpressionEvaluator expressionEvaluator, Map<String, PropertyVersionsBuilder> result, List<Plugin> plugins) throws ExpressionEvaluationException { if (plugins == null) { return; } for (Plugin plugin : plugins) { String version = plugin.getVersion(); if (version != null && version.contains("${") && version.indexOf('}') != -1) { version = StringUtils.deleteWhitespace(version); for (PropertyVersionsBuilder property : result.values()) { // any of these could be defined by a property final String propertyRef = "${" + property.getName() + "}"; if (version.contains(propertyRef)) { String groupId = plugin.getGroupId(); if (groupId == null || groupId.trim().length() == 0) { // group Id has a special default groupId = APACHE_MAVEN_PLUGINS_GROUPID; } else { groupId = (String) expressionEvaluator.evaluate(groupId); } String artifactId = plugin.getArtifactId(); if (artifactId == null || artifactId.trim().length() == 0) { // malformed pom continue; } else { artifactId = (String) expressionEvaluator.evaluate(artifactId); } // might as well capture the current value VersionRange versionRange = VersionRange .createFromVersion((String) expressionEvaluator.evaluate(plugin.getVersion())); property.addAssociation(helper.createPluginArtifact(groupId, artifactId, versionRange), true); if (!propertyRef.equals(version)) { addBounds(property, version, propertyRef, versionRange.toString()); } } } } addDependencyAssocations(helper, expressionEvaluator, result, plugin.getDependencies(), true); } } private static void addReportPluginAssociations(VersionsHelper helper, ExpressionEvaluator expressionEvaluator, Map<String, PropertyVersionsBuilder> result, List<ReportPlugin> reportPlugins) throws ExpressionEvaluationException { if (reportPlugins == null) { return; } for (ReportPlugin plugin : reportPlugins) { String version = plugin.getVersion(); if (version != null && version.contains("${") && version.indexOf('}') != -1) { version = StringUtils.deleteWhitespace(version); for (PropertyVersionsBuilder property : result.values()) { final String propertyRef = "${" + property.getName() + "}"; if (version.contains(propertyRef)) { // any of these could be defined by a property String groupId = plugin.getGroupId(); if (groupId == null || groupId.trim().length() == 0) { // group Id has a special default groupId = APACHE_MAVEN_PLUGINS_GROUPID; } else { groupId = (String) expressionEvaluator.evaluate(groupId); } String artifactId = plugin.getArtifactId(); if (artifactId == null || artifactId.trim().length() == 0) { // malformed pom continue; } else { artifactId = (String) expressionEvaluator.evaluate(artifactId); } // might as well capture the current value VersionRange versionRange = VersionRange .createFromVersion((String) expressionEvaluator.evaluate(plugin.getVersion())); property.addAssociation(helper.createPluginArtifact(groupId, artifactId, versionRange), true); if (!propertyRef.equals(version)) { addBounds(property, version, propertyRef, versionRange.toString()); } } } } } } private static void addDependencyAssocations(VersionsHelper helper, ExpressionEvaluator expressionEvaluator, Map<String, PropertyVersionsBuilder> result, List<Dependency> dependencies, boolean usePluginRepositories) throws ExpressionEvaluationException { if (dependencies == null) { return; } for (Dependency dependency : dependencies) { String version = dependency.getVersion(); if (version != null && version.contains("${") && version.indexOf('}') != -1) { version = StringUtils.deleteWhitespace(version); for (PropertyVersionsBuilder property : result.values()) { final String propertyRef = "${" + property.getName() + "}"; if (version.contains(propertyRef)) { // Any of these could be defined by a property String groupId = dependency.getGroupId(); if (groupId == null || groupId.trim().length() == 0) { // malformed pom continue; } else { groupId = (String) expressionEvaluator.evaluate(groupId); } String artifactId = dependency.getArtifactId(); if (artifactId == null || artifactId.trim().length() == 0) { // malformed pom continue; } else { artifactId = (String) expressionEvaluator.evaluate(artifactId); } // might as well capture the current value VersionRange versionRange = VersionRange .createFromVersion((String) expressionEvaluator.evaluate(dependency.getVersion())); property.addAssociation(helper.createDependencyArtifact(groupId, artifactId, versionRange, dependency.getType(), dependency.getClassifier(), dependency.getScope(), dependency.isOptional()), usePluginRepositories); if (!propertyRef.equals(version)) { addBounds(property, version, propertyRef, versionRange.toString()); } } } } } } private static void addBounds(PropertyVersionsBuilder builder, String rawVersionRange, String propertyRef, String evaluatedVersionRange) { Pattern lowerBound = Pattern.compile("([(\\[])([^,]*)," + RegexUtils.quote(propertyRef) + "([)\\]])"); Pattern upperBound = Pattern.compile("([(\\[])" + RegexUtils.quote(propertyRef) + ",([^,]*)([)\\]])"); Matcher m = lowerBound.matcher(rawVersionRange); if (m.find()) { boolean includeLower = "[".equals(m.group(1)); String lowerLimit = m.group(2); if (StringUtils.isNotEmpty(lowerLimit)) { builder.addLowerBound(lowerLimit, includeLower); } } m = upperBound.matcher(rawVersionRange); if (m.find()) { boolean includeUpper = "[".equals(m.group(3)); String upperLimit = m.group(2); if (StringUtils.isNotEmpty(upperLimit)) { builder.addUpperBound(upperLimit, includeUpper); } } } private static void addProperties(VersionsHelper helper, Map<String, PropertyVersionsBuilder> result, String profileId, Properties properties) { if (properties == null) { return; } for (Enumeration j = properties.propertyNames(); j.hasMoreElements();) { String propertyName = (String) j.nextElement(); if (!result.containsKey(propertyName)) { result.put(propertyName, new PropertyVersionsBuilder(profileId, propertyName, helper)); } } } private static void purgeProperties(Map<String, PropertyVersionsBuilder> result) { for (Iterator i = result.values().iterator(); i.hasNext();) { PropertyVersionsBuilder versions = (PropertyVersionsBuilder) i.next(); if (versions.getAssociations().length == 0) { i.remove(); } } } /** * Returns a set of all child modules for a project, including any defined in profiles (ignoring profile * activation). * * @param project The project. * @param logger The logger to use. * @return the set of all child modules of the project. */ public static Set getAllChildModules(MavenProject project, Log logger) { return getAllChildModules(project.getOriginalModel(), logger); } /** * Returns a set of all child modules for a project, including any defined in profiles (ignoring profile * activation). * * @param model The project model. * @param logger The logger to use. * @return the set of all child modules of the project. */ public static Set<String> getAllChildModules(Model model, Log logger) { logger.debug("Finding child modules..."); Set<String> childModules = new TreeSet<String>(); childModules.addAll(model.getModules()); for (Profile profile : model.getProfiles()) { childModules.addAll(profile.getModules()); } debugModules(logger, "Child modules:", childModules); return childModules; } /** * Outputs a debug message with a list of modules. * * @param logger The logger to log to. * @param message The message to display. * @param modules The modules to append to the message. */ public static void debugModules(Log logger, String message, Collection modules) { Iterator i; if (logger.isDebugEnabled()) { logger.debug(message); if (modules.isEmpty()) { logger.debug("None."); } else { i = modules.iterator(); while (i.hasNext()) { logger.debug(" " + i.next()); } } } } /** * Modifies the collection of child modules removing those which cannot be found relative to the parent. * * @param logger The logger to log to. * @param project the project. * @param childModules the child modules. */ public static void removeMissingChildModules(Log logger, MavenProject project, Collection<String> childModules) { removeMissingChildModules(logger, project.getBasedir(), childModules); } /** * Modifies the collection of child modules removing those which cannot be found relative to the parent. * * @param logger The logger to log to. * @param basedir the project basedir. * @param childModules the child modules. */ public static void removeMissingChildModules(Log logger, File basedir, Collection<String> childModules) { logger.debug("Removing child modules which are missing..."); Iterator<String> i = childModules.iterator(); while (i.hasNext()) { String modulePath = i.next(); File moduleFile = new File(basedir, modulePath); if (moduleFile.isDirectory() && new File(moduleFile, "pom.xml").isFile()) { // it's a directory that exists continue; } if (moduleFile.isFile()) { // it's the pom.xml file directly referenced and it exists. continue; } logger.debug("Removing missing child module " + modulePath); i.remove(); } debugModules(logger, "After removing missing", childModules); } /** * Extracts the version from a raw model, interpolating from the parent if necessary. * * @param model The model. * @return The version. */ public static String getVersion(Model model) { String targetVersion = model.getVersion(); if (targetVersion == null && model.getParent() != null) { targetVersion = model.getParent().getVersion(); } return targetVersion; } /** * Checks to see if the model contains an explicitly specified version. * * @param model The model. * @return {@code true} if the model explicitly specifies the project version, i.e. /project/version */ public static boolean isExplicitVersion(Model model) { return model.getVersion() != null; } /** * Extracts the artifactId from a raw model, interpolating from the parent if necessary. * * @param model The model. * @return The artifactId. */ public static String getArtifactId(Model model) { String sourceArtifactId = model.getArtifactId(); if (sourceArtifactId == null && model.getParent() != null) { sourceArtifactId = model.getParent().getArtifactId(); } return sourceArtifactId; } /** * Extracts the groupId from a raw model, interpolating from the parent if necessary. * * @param model The model. * @return The groupId. */ public static String getGroupId(Model model) { String targetGroupId = model.getGroupId(); if (targetGroupId == null && model.getParent() != null) { targetGroupId = model.getParent().getGroupId(); } return targetGroupId; } /** * Finds the local root of the specified project. * * @param project The project to find the local root for. * @param localRepository the local repo. * @param globalProfileManager the global profile manager. * @param logger The logger to log to. * @return The local root (note this may be the project passed as an argument). */ public static MavenProject getLocalRoot(MavenProjectBuilder builder, MavenProject project, ArtifactRepository localRepository, ProfileManager globalProfileManager, Log logger) { logger.info("Searching for local aggregator root..."); while (true) { final File parentDir = project.getBasedir().getParentFile(); if (parentDir.isDirectory()) { logger.debug("Checking to see if " + parentDir + " is an aggregator parent"); File parent = new File(parentDir, "pom.xml"); if (parent.isFile()) { try { final MavenProject parentProject = builder.build(parent, localRepository, globalProfileManager); if (getAllChildModules(parentProject, logger).contains(project.getBasedir().getName())) { logger.debug(parentDir + " is an aggregator parent"); project = parentProject; continue; } else { logger.debug(parentDir + " is not an aggregator parent"); } } catch (ProjectBuildingException e) { logger.warn(e); } } } logger.debug("Local aggregation root is " + project.getBasedir()); return project; } } /** * Builds a map of raw models keyed by module path. * * @param project The project to build from. * @param logger The logger for logging. * @return A map of raw models keyed by path relative to the project's basedir. * @throws IOException if things go wrong. */ public static Map<String, Model> getReactorModels(MavenProject project, Log logger) throws IOException { Map<String, Model> result = new LinkedHashMap<String, Model>(); final Model model = getRawModel(project); final String path = ""; result.put(path, model); result.putAll(getReactorModels(path, model, project, logger)); return result; } /** * Builds a sub-map of raw models keyed by module path. * * @param path The relative path to base the sub-map on. * @param model The model at the relative path. * @param project The project to build from. * @param logger The logger for logging. * @return A map of raw models keyed by path relative to the project's basedir. * @throws IOException if things go wrong. */ private static Map<String, Model> getReactorModels(String path, Model model, MavenProject project, Log logger) throws IOException { if (path.length() > 0 && !path.endsWith("/")) { path += '/'; } Map<String, Model> result = new LinkedHashMap<String, Model>(); Map<String, Model> childResults = new LinkedHashMap<String, Model>(); File baseDir = path.length() > 0 ? new File(project.getBasedir(), path) : project.getBasedir(); Set<String> childModules = getAllChildModules(model, logger); removeMissingChildModules(logger, baseDir, childModules); for (String moduleName : childModules) { String modulePath = path + moduleName; File moduleDir = new File(baseDir, moduleName); File moduleProjectFile; if (moduleDir.isDirectory()) { moduleProjectFile = new File(moduleDir, "pom.xml"); } else { // i don't think this should ever happen... but just in case // the module references the file-name moduleProjectFile = moduleDir; } try { // the aim of this goal is to fix problems when the project cannot be parsed by Maven // so we have to work with the raw model and not the interpolated parsed model from maven Model moduleModel = getRawModel(moduleProjectFile); result.put(modulePath, moduleModel); childResults.putAll(getReactorModels(modulePath, moduleModel, project, logger)); } catch (IOException e) { logger.debug("Could not parse " + moduleProjectFile.getPath(), e); } } result.putAll(childResults); // more efficient update order if all children are added after siblings return result; } /** * Returns all the models that have a specified groupId and artifactId as parent. * * @param reactor The map of models keyed by path. * @param groupId The groupId of the parent. * @param artifactId The artifactId of the parent. * @return a map of models that have a specified groupId and artifactId as parent keyed by path. */ public static Map<String, Model> getChildModels(Map<String, Model> reactor, String groupId, String artifactId) { final Map<String, Model> result = new LinkedHashMap<String, Model>(); for (Map.Entry<String, Model> entry : reactor.entrySet()) { final String path = entry.getKey(); final Model model = entry.getValue(); final Parent parent = model.getParent(); if (parent != null && groupId.equals(parent.getGroupId()) && artifactId.equals(parent.getArtifactId())) { result.put(path, model); } } return result; } /** * Returns the model that has the specified groupId and artifactId or <code>null</code> if no such model exists. * * @param reactor The map of models keyed by path. * @param groupId The groupId to match. * @param artifactId The artifactId to match. * @return The model or <code>null</code> if the model was not in the reactor. */ public static Model getModel(Map<String, Model> reactor, String groupId, String artifactId) { Map.Entry<String, Model> entry = getModelEntry(reactor, groupId, artifactId); return entry == null ? null : entry.getValue(); } /** * Returns the model that has the specified groupId and artifactId or <code>null</code> if no such model exists. * * @param reactor The map of models keyed by path. * @param groupId The groupId to match. * @param artifactId The artifactId to match. * @return The model entry or <code>null</code> if the model was not in the reactor. */ public static Map.Entry<String, Model> getModelEntry(Map<String, Model> reactor, String groupId, String artifactId) { for (Map.Entry<String, Model> entry : reactor.entrySet()) { Model model = entry.getValue(); if (groupId.equals(getGroupId(model)) && artifactId.equals(getArtifactId(model))) { return entry; } } return null; } /** * Returns a count of how many parents a model has in the reactor. * * @param reactor The map of models keyed by path. * @param model The model. * @return The number of parents of this model in the reactor. */ public static int getReactorParentCount(Map<String, Model> reactor, Model model) { if (model.getParent() == null) { return 0; } else { Model parentModel = getModel(reactor, model.getParent().getGroupId(), model.getParent().getArtifactId()); if (parentModel != null) { return getReactorParentCount(reactor, parentModel) + 1; } return 0; } } /** * Reads a file into a String. * * @param outFile The file to read. * @return String The content of the file. * @throws java.io.IOException when things go wrong. */ public static StringBuilder readXmlFile(File outFile) throws IOException { Reader reader = ReaderFactory.newXmlReader(outFile); try { return new StringBuilder(IOUtil.toString(reader)); } finally { IOUtil.close(reader); } } /** * Returns the GAV coordinates of a model. * * @param model the model. * @return the GAV coordinates of a model. */ public static String getGAV(Model model) { return getGroupId(model) + ":" + getArtifactId(model) + ":" + getVersion(model); } }