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.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.Collection; import java.util.Enumeration; 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.asStringBuffer().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 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 = new StringBuffer().append( path ).append( "/" ).append( event.asStartElement().getName().getLocalPart() ).toString(); 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 = (String) 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 = (String) 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 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 = new StringBuffer().append( path ).append( "/" ).append( event.asStartElement().getName().getLocalPart() ).toString(); 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 = (String) 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 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 = new StringBuffer().append( path ).append( "/" ).append( event.asStartElement().getName().getLocalPart() ).toString(); 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 = (String) 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 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 = new StringBuffer().append( path ).append( "/" ).append( event.asStartElement().getName().getLocalPart() ).toString(); 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 = (String) 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 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 = new StringBuffer().append( path ).append( "/" ).append( elementName ).toString(); if ( matchScopeRegex.matcher( path ).matches() ) { if ( "groupId".equals( elementName ) ) { groupId = pom.getElementText().trim(); path = (String) stack.pop(); } else if ( "artifactId".equals( elementName ) ) { artifactId = pom.getElementText().trim(); path = (String) stack.pop(); } else if ( "version".equals( elementName ) ) { version = pom.getElementText().trim(); path = (String) stack.pop(); } } } if ( event.isEndElement() ) { path = (String) 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 stack = new Stack(); String path = ""; final Pattern matchScopeRegex; final Pattern matchTargetRegex; boolean inMatchScope = false; boolean madeReplacement = false; boolean haveGroupId = false; boolean haveArtifactId = false; boolean haveOldVersion = false; matchScopeRegex = Pattern.compile( "/project" + "(/profiles/profile)?" + "((/dependencyManagement)|(/build(/pluginManagement)?/plugins/plugin))?" + "/dependencies/dependency" ); 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 = new StringBuffer().append( path ).append( "/" ).append( elementName ).toString(); 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 = (String) stack.pop(); } else if ( "artifactId".equals( elementName ) ) { haveArtifactId = artifactId.equals( pom.getElementText().trim() ); path = (String) 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 ); 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 = (String) stack.pop(); } } return madeReplacement; } /** * 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 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 = new StringBuffer().append( path ).append( "/" ).append( elementName ).toString(); 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 = (String) stack.pop(); } else if ( "artifactId".equals( elementName ) ) { haveArtifactId = artifactId.equals( pom.getElementText().trim() ); path = (String) 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 ); 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 = (String) 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(); Set/*<String*/ activeProfiles = new TreeSet(); for ( Iterator i = project.getActiveProfiles().iterator(); i.hasNext(); ) { Profile profile = (Profile) i.next(); activeProfiles.add( profile.getId() ); } // add any properties from profiles first (as they override properties from the project for ( Iterator i = model.getProfiles().iterator(); i.hasNext(); ) { Profile profile = (Profile) i.next(); 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 ( Iterator i = model.getProfiles().iterator(); i.hasNext(); ) { Profile profile = (Profile) i.next(); 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 (PropertyVersionsBuilder[]) 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 ( Iterator i = plugins.iterator(); i.hasNext(); ) { Plugin plugin = (Plugin) i.next(); String version = plugin.getVersion(); if ( version != null && version.indexOf( "${" ) != -1 && version.indexOf( '}' ) != -1 ) { version = StringUtils.deleteWhitespace( version ); for ( Iterator j = result.values().iterator(); j.hasNext(); ) { // any of these could be defined by a property PropertyVersionsBuilder property = (PropertyVersionsBuilder) j.next(); final String propertyRef = "${" + property.getName() + "}"; if ( version.indexOf( propertyRef ) != -1 ) { 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 ( Iterator i = reportPlugins.iterator(); i.hasNext(); ) { ReportPlugin plugin = (ReportPlugin) i.next(); String version = plugin.getVersion(); if ( version != null && version.indexOf( "${" ) != -1 && version.indexOf( '}' ) != -1 ) { version = StringUtils.deleteWhitespace( version ); for ( Iterator j = result.values().iterator(); j.hasNext(); ) { PropertyVersionsBuilder property = (PropertyVersionsBuilder) j.next(); final String propertyRef = "${" + property.getName() + "}"; if ( version.indexOf( propertyRef ) != -1 ) { // 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 ( Iterator i = dependencies.iterator(); i.hasNext(); ) { Dependency dependency = (Dependency) i.next(); String version = dependency.getVersion(); if ( version != null && version.indexOf( "${" ) != -1 && version.indexOf( '}' ) != -1 ) { version = StringUtils.deleteWhitespace( version ); for ( Iterator j = result.values().iterator(); j.hasNext(); ) { PropertyVersionsBuilder property = (PropertyVersionsBuilder) j.next(); final String propertyRef = "${" + property.getName() + "}"; if ( version.indexOf( propertyRef ) != -1 ) { // 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() ); Iterator i = model.getProfiles().iterator(); while ( i.hasNext() ) { Profile profile = (Profile) i.next(); 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 = (String) 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 * @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 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/*<String,Model>*/ 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 ); Iterator i = childModules.iterator(); while ( i.hasNext() ) { final String moduleName = (String) i.next(); 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 result = new LinkedHashMap(); final Iterator iterator = reactor.entrySet().iterator(); while ( iterator.hasNext() ) { final Map.Entry entry = (Map.Entry) iterator.next(); final String path = (String) entry.getKey(); final Model 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 ) { final Iterator iterator = reactor.values().iterator(); while ( iterator.hasNext() ) { final Model model = (Model) iterator.next(); 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/*<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 StringBuffer readXmlFile( File outFile ) throws IOException { Reader reader = ReaderFactory.newXmlReader( outFile ); try { return new StringBuffer( IOUtil.toString( reader ) ); } finally { IOUtil.close( reader ); } } }