org.codehaus.mojo.versions.api.PomHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of versions-maven-plugin Show documentation
Show all versions of versions-maven-plugin Show documentation
Versions Plugin for Maven 2. The Versions Plugin updates the versions of components in the POM.
The newest version!
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 true
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 stack = new Stack();
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 true
if a replacement was made.
* @throws XMLStreamException if somethinh went wrong.
*/
public static boolean setProjectVersion( final ModifiedPomXMLEventReader pom, final String value )
throws XMLStreamException
{
Stack stack = new Stack();
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 null
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 stack = new Stack();
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 true
if a replacement was made.
* @throws XMLStreamException if somethinh went wrong.
*/
public static boolean setProjectParentVersion( final ModifiedPomXMLEventReader pom, final String value )
throws XMLStreamException
{
Stack stack = new Stack();
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 null
if no parent is specified.
* @throws XMLStreamException if something went wrong.
*/
public static Artifact getProjectParent( final ModifiedPomXMLEventReader pom, VersionsHelper helper )
throws XMLStreamException
{
Stack stack = new Stack();
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 true
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 stack = new Stack();
String path = "";
Set implicitPaths = new HashSet(
Arrays.asList( "/project/parent/groupId", "/project/parent/artifactId", "/project/parent/version",
"/project/groupId", "/project/artifactId", "/project/version" ) );
Map implicitProperties = new HashMap();
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 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;
}
}
}
}
System.out.println( "Props: " + implicitProperties );
stack = new Stack();
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 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 true
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 stack = new Stack();
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 result = new TreeMap();
Set activeProfiles = new TreeSet();
for ( Profile profile : (List) 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 result, List 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 result,
List 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 result,
List 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 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 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 getAllChildModules( Model model, Log logger )
{
logger.debug( "Finding child modules..." );
Set childModules = new TreeSet();
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 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 childModules )
{
logger.debug( "Removing child modules which are missing..." );
Iterator 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 getReactorModels( MavenProject project, Log logger )
throws IOException
{
Map result = new LinkedHashMap();
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 getReactorModels( String path, Model model, MavenProject project, Log logger )
throws IOException
{
if ( path.length() > 0 && !path.endsWith( "/" ) )
{
path += '/';
}
Map result = new LinkedHashMap();
Map childResults = new LinkedHashMap();
File baseDir = path.length() > 0 ? new File( project.getBasedir(), path ) : project.getBasedir();
Set 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 getChildModels( Map reactor, String groupId, String artifactId )
{
final Map result = new LinkedHashMap();
for ( Map.Entry 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 null
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 null
if the model was not in the reactor.
*/
public static Model getModel( Map reactor, String groupId, String artifactId )
{
for ( Model model : reactor.values() )
{
if ( groupId.equals( getGroupId( model ) ) && artifactId.equals( getArtifactId( model ) ) )
{
return model;
}
}
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 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 );
}
}
}