blob: 049ce1f1286ba3c45286aab983c3e23ca3ce2bbc [file] [log] [blame]
package org.apache.maven.model.merge;
/*
* 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 java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.maven.model.BuildBase;
import org.apache.maven.model.CiManagement;
import org.apache.maven.model.Dependency;
import org.apache.maven.model.DeploymentRepository;
import org.apache.maven.model.DistributionManagement;
import org.apache.maven.model.Exclusion;
import org.apache.maven.model.Extension;
import org.apache.maven.model.InputLocation;
import org.apache.maven.model.IssueManagement;
import org.apache.maven.model.Model;
import org.apache.maven.model.ModelBase;
import org.apache.maven.model.Organization;
import org.apache.maven.model.Plugin;
import org.apache.maven.model.PluginExecution;
import org.apache.maven.model.ReportPlugin;
import org.apache.maven.model.ReportSet;
import org.apache.maven.model.Repository;
import org.apache.maven.model.RepositoryBase;
import org.apache.maven.model.Scm;
import org.apache.maven.model.Site;
import org.codehaus.plexus.util.StringUtils;
/**
* The domain-specific model merger for the Maven POM, overriding generic code from parent class when necessary with
* more adapted algorithms.
*
* @author Benjamin Bentmann
*/
public class MavenModelMerger
extends ModelMerger
{
/**
* The hint key for the child path adjustment used during inheritance for URL calculations.
*/
public static final String CHILD_PATH_ADJUSTMENT = "child-path-adjustment";
/**
* The context key for the artifact id of the target model.
*/
public static final String ARTIFACT_ID = "artifact-id";
@Override
protected void mergeModel( Model target, Model source, boolean sourceDominant, Map<Object, Object> context )
{
context.put( ARTIFACT_ID, target.getArtifactId() );
super.mergeModel( target, source, sourceDominant, context );
}
@Override
protected void mergeModel_Name( Model target, Model source, boolean sourceDominant, Map<Object, Object> context )
{
String src = source.getName();
if ( src != null )
{
if ( sourceDominant )
{
target.setName( src );
target.setLocation( "name", source.getLocation( "name" ) );
}
}
}
@Override
protected void mergeModel_Url( Model target, Model source, boolean sourceDominant, Map<Object, Object> context )
{
String src = source.getUrl();
if ( src != null )
{
if ( sourceDominant )
{
target.setUrl( src );
target.setLocation( "url", source.getLocation( "url" ) );
}
else if ( target.getUrl() == null )
{
target.setUrl( extrapolateChildUrl( src, source.isChildProjectUrlInheritAppendPath(), context ) );
target.setLocation( "url", source.getLocation( "url" ) );
}
}
}
/*
* TODO: Whether the merge continues recursively into an existing node or not could be an option for the generated
* merger
*/
@Override
protected void mergeModel_Organization( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
Organization src = source.getOrganization();
if ( src != null )
{
Organization tgt = target.getOrganization();
if ( tgt == null )
{
tgt = new Organization();
tgt.setLocation( "", src.getLocation( "" ) );
target.setOrganization( tgt );
mergeOrganization( tgt, src, sourceDominant, context );
}
}
}
@Override
protected void mergeModel_IssueManagement( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
IssueManagement src = source.getIssueManagement();
if ( src != null )
{
IssueManagement tgt = target.getIssueManagement();
if ( tgt == null )
{
tgt = new IssueManagement();
tgt.setLocation( "", src.getLocation( "" ) );
target.setIssueManagement( tgt );
mergeIssueManagement( tgt, src, sourceDominant, context );
}
}
}
@Override
protected void mergeModel_CiManagement( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
CiManagement src = source.getCiManagement();
if ( src != null )
{
CiManagement tgt = target.getCiManagement();
if ( tgt == null )
{
tgt = new CiManagement();
tgt.setLocation( "", src.getLocation( "" ) );
target.setCiManagement( tgt );
mergeCiManagement( tgt, src, sourceDominant, context );
}
}
}
@Override
protected void mergeModel_ModelVersion( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
// neither inherited nor injected
}
@Override
protected void mergeModel_ArtifactId( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
// neither inherited nor injected
}
@Override
protected void mergeModel_Profiles( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
// neither inherited nor injected
}
@Override
protected void mergeModel_Prerequisites( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
// neither inherited nor injected
}
@Override
protected void mergeModel_Licenses( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
if ( target.getLicenses().isEmpty() )
{
target.setLicenses( new ArrayList<>( source.getLicenses() ) );
}
}
@Override
protected void mergeModel_Developers( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
if ( target.getDevelopers().isEmpty() )
{
target.setDevelopers( new ArrayList<>( source.getDevelopers() ) );
}
}
@Override
protected void mergeModel_Contributors( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
if ( target.getContributors().isEmpty() )
{
target.setContributors( new ArrayList<>( source.getContributors() ) );
}
}
@Override
protected void mergeModel_MailingLists( Model target, Model source, boolean sourceDominant,
Map<Object, Object> context )
{
if ( target.getMailingLists().isEmpty() )
{
target.setMailingLists( new ArrayList<>( source.getMailingLists() ) );
}
}
@Override
protected void mergeModelBase_Modules( ModelBase target, ModelBase source, boolean sourceDominant,
Map<Object, Object> context )
{
List<String> src = source.getModules();
if ( !src.isEmpty() && sourceDominant )
{
List<Integer> indices = new ArrayList<>();
List<String> tgt = target.getModules();
Set<String> excludes = new LinkedHashSet<>( tgt );
List<String> merged = new ArrayList<>( tgt.size() + src.size() );
merged.addAll( tgt );
for ( int i = 0, n = tgt.size(); i < n; i++ )
{
indices.add( i );
}
for ( int i = 0, n = src.size(); i < n; i++ )
{
String s = src.get( i );
if ( !excludes.contains( s ) )
{
merged.add( s );
indices.add( ~i );
}
}
target.setModules( merged );
target.setLocation( "modules", InputLocation.merge( target.getLocation( "modules" ),
source.getLocation( "modules" ), indices ) );
}
}
/*
* TODO: The order of the merged list could be controlled by an attribute in the model association: target-first,
* source-first, dominant-first, recessive-first
*/
@Override
protected void mergeModelBase_Repositories( ModelBase target, ModelBase source, boolean sourceDominant,
Map<Object, Object> context )
{
List<Repository> src = source.getRepositories();
if ( !src.isEmpty() )
{
List<Repository> tgt = target.getRepositories();
Map<Object, Repository> merged = new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
List<Repository> dominant, recessive;
if ( sourceDominant )
{
dominant = src;
recessive = tgt;
}
else
{
dominant = tgt;
recessive = src;
}
for ( Repository element : dominant )
{
Object key = getRepositoryKey().apply( element );
merged.put( key, element );
}
for ( Repository element : recessive )
{
Object key = getRepositoryKey().apply( element );
if ( !merged.containsKey( key ) )
{
merged.put( key, element );
}
}
target.setRepositories( new ArrayList<>( merged.values() ) );
}
}
@Override
protected void mergeModelBase_PluginRepositories( ModelBase target, ModelBase source, boolean sourceDominant,
Map<Object, Object> context )
{
List<Repository> src = source.getPluginRepositories();
if ( !src.isEmpty() )
{
List<Repository> tgt = target.getPluginRepositories();
Map<Object, Repository> merged = new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
List<Repository> dominant, recessive;
if ( sourceDominant )
{
dominant = src;
recessive = tgt;
}
else
{
dominant = tgt;
recessive = src;
}
for ( Repository element : dominant )
{
Object key = getRepositoryKey().apply( element );
merged.put( key, element );
}
for ( Repository element : recessive )
{
Object key = getRepositoryKey().apply( element );
if ( !merged.containsKey( key ) )
{
merged.put( key, element );
}
}
target.setPluginRepositories( new ArrayList<>( merged.values() ) );
}
}
/*
* TODO: Whether duplicates should be removed looks like an option for the generated merger.
*/
@Override
protected void mergeBuildBase_Filters( BuildBase target, BuildBase source, boolean sourceDominant,
Map<Object, Object> context )
{
List<String> src = source.getFilters();
if ( !src.isEmpty() )
{
List<String> tgt = target.getFilters();
Set<String> excludes = new LinkedHashSet<>( tgt );
List<String> merged = new ArrayList<>( tgt.size() + src.size() );
merged.addAll( tgt );
for ( String s : src )
{
if ( !excludes.contains( s ) )
{
merged.add( s );
}
}
target.setFilters( merged );
}
}
@Override
protected void mergeBuildBase_Resources( BuildBase target, BuildBase source, boolean sourceDominant,
Map<Object, Object> context )
{
if ( sourceDominant || target.getResources().isEmpty() )
{
super.mergeBuildBase_Resources( target, source, sourceDominant, context );
}
}
@Override
protected void mergeBuildBase_TestResources( BuildBase target, BuildBase source, boolean sourceDominant,
Map<Object, Object> context )
{
if ( sourceDominant || target.getTestResources().isEmpty() )
{
super.mergeBuildBase_TestResources( target, source, sourceDominant, context );
}
}
@Override
protected void mergeDistributionManagement_Repository( DistributionManagement target,
DistributionManagement source, boolean sourceDominant,
Map<Object, Object> context )
{
DeploymentRepository src = source.getRepository();
if ( src != null )
{
DeploymentRepository tgt = target.getRepository();
if ( sourceDominant || tgt == null )
{
tgt = new DeploymentRepository();
tgt.setLocation( "", src.getLocation( "" ) );
target.setRepository( tgt );
mergeDeploymentRepository( tgt, src, sourceDominant, context );
}
}
}
@Override
protected void mergeDistributionManagement_SnapshotRepository( DistributionManagement target,
DistributionManagement source,
boolean sourceDominant,
Map<Object, Object> context )
{
DeploymentRepository src = source.getSnapshotRepository();
if ( src != null )
{
DeploymentRepository tgt = target.getSnapshotRepository();
if ( sourceDominant || tgt == null )
{
tgt = new DeploymentRepository();
tgt.setLocation( "", src.getLocation( "" ) );
target.setSnapshotRepository( tgt );
mergeDeploymentRepository( tgt, src, sourceDominant, context );
}
}
}
@Override
protected void mergeDistributionManagement_Site( DistributionManagement target, DistributionManagement source,
boolean sourceDominant, Map<Object, Object> context )
{
Site src = source.getSite();
if ( src != null )
{
Site tgt = target.getSite();
if ( sourceDominant || tgt == null || isSiteEmpty( tgt ) )
{
if ( tgt == null )
{
tgt = new Site();
}
tgt.setLocation( "", src.getLocation( "" ) );
target.setSite( tgt );
mergeSite( tgt, src, sourceDominant, context );
}
mergeSite_ChildSiteUrlInheritAppendPath( tgt, src, sourceDominant, context );
}
}
@Override
protected void mergeSite( Site target, Site source, boolean sourceDominant, Map<Object, Object> context )
{
mergeSite_Id( target, source, sourceDominant, context );
mergeSite_Name( target, source, sourceDominant, context );
mergeSite_Url( target, source, sourceDominant, context );
}
protected boolean isSiteEmpty( Site site )
{
return StringUtils.isEmpty( site.getId() ) && StringUtils.isEmpty( site.getName() )
&& StringUtils.isEmpty( site.getUrl() );
}
@Override
protected void mergeSite_Url( Site target, Site source, boolean sourceDominant, Map<Object, Object> context )
{
String src = source.getUrl();
if ( src != null )
{
if ( sourceDominant )
{
target.setUrl( src );
target.setLocation( "url", source.getLocation( "url" ) );
}
else if ( target.getUrl() == null )
{
target.setUrl( extrapolateChildUrl( src, source.isChildSiteUrlInheritAppendPath(), context ) );
target.setLocation( "url", source.getLocation( "url" ) );
}
}
}
@Override
protected void mergeScm_Url( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
{
String src = source.getUrl();
if ( src != null )
{
if ( sourceDominant )
{
target.setUrl( src );
target.setLocation( "url", source.getLocation( "url" ) );
}
else if ( target.getUrl() == null )
{
target.setUrl( extrapolateChildUrl( src, source.isChildScmUrlInheritAppendPath(), context ) );
target.setLocation( "url", source.getLocation( "url" ) );
}
}
}
@Override
protected void mergeScm_Connection( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
{
String src = source.getConnection();
if ( src != null )
{
if ( sourceDominant )
{
target.setConnection( src );
target.setLocation( "connection", source.getLocation( "connection" ) );
}
else if ( target.getConnection() == null )
{
target.setConnection( extrapolateChildUrl( src, source.isChildScmConnectionInheritAppendPath(),
context ) );
target.setLocation( "connection", source.getLocation( "connection" ) );
}
}
}
@Override
protected void mergeScm_DeveloperConnection( Scm target, Scm source, boolean sourceDominant,
Map<Object, Object> context )
{
String src = source.getDeveloperConnection();
if ( src != null )
{
if ( sourceDominant )
{
target.setDeveloperConnection( src );
target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
}
else if ( target.getDeveloperConnection() == null )
{
String e = extrapolateChildUrl( src, source.isChildScmDeveloperConnectionInheritAppendPath(), context );
target.setDeveloperConnection( e );
target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
}
}
}
@Override
protected void mergePlugin_Executions( Plugin target, Plugin source, boolean sourceDominant,
Map<Object, Object> context )
{
List<PluginExecution> src = source.getExecutions();
if ( !src.isEmpty() )
{
List<PluginExecution> tgt = target.getExecutions();
Map<Object, PluginExecution> merged =
new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
for ( PluginExecution element : src )
{
if ( sourceDominant
|| ( element.getInherited() != null ? element.isInherited() : source.isInherited() ) )
{
Object key = getPluginExecutionKey().apply( element );
merged.put( key, element );
}
}
for ( PluginExecution element : tgt )
{
Object key = getPluginExecutionKey().apply( element );
PluginExecution existing = merged.get( key );
if ( existing != null )
{
mergePluginExecution( element, existing, sourceDominant, context );
}
merged.put( key, element );
}
target.setExecutions( new ArrayList<>( merged.values() ) );
}
}
@Override
protected void mergePluginExecution_Goals( PluginExecution target, PluginExecution source, boolean sourceDominant,
Map<Object, Object> context )
{
List<String> src = source.getGoals();
if ( !src.isEmpty() )
{
List<String> tgt = target.getGoals();
Set<String> excludes = new LinkedHashSet<>( tgt );
List<String> merged = new ArrayList<>( tgt.size() + src.size() );
merged.addAll( tgt );
for ( String s : src )
{
if ( !excludes.contains( s ) )
{
merged.add( s );
}
}
target.setGoals( merged );
}
}
@Override
protected void mergeReportPlugin_ReportSets( ReportPlugin target, ReportPlugin source, boolean sourceDominant,
Map<Object, Object> context )
{
List<ReportSet> src = source.getReportSets();
if ( !src.isEmpty() )
{
List<ReportSet> tgt = target.getReportSets();
Map<Object, ReportSet> merged = new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
for ( ReportSet rset : src )
{
if ( sourceDominant || ( rset.getInherited() != null ? rset.isInherited() : source.isInherited() ) )
{
Object key = getReportSetKey().apply( rset );
merged.put( key, rset );
}
}
for ( ReportSet element : tgt )
{
Object key = getReportSetKey().apply( element );
ReportSet existing = merged.get( key );
if ( existing != null )
{
mergeReportSet( element, existing, sourceDominant, context );
}
merged.put( key, element );
}
target.setReportSets( new ArrayList<>( merged.values() ) );
}
}
@Override
protected KeyComputer<Dependency> getDependencyKey()
{
return Dependency::getManagementKey;
}
@Override
protected KeyComputer<Plugin> getPluginKey()
{
return Plugin::getKey;
}
@Override
protected KeyComputer<PluginExecution> getPluginExecutionKey()
{
return PluginExecution::getId;
}
@Override
protected KeyComputer<ReportPlugin> getReportPluginKey()
{
return ReportPlugin::getKey;
}
@Override
protected KeyComputer<ReportSet> getReportSetKey()
{
return ReportSet::getId;
}
@Override
protected KeyComputer<RepositoryBase> getRepositoryBaseKey()
{
return RepositoryBase::getId;
}
@Override
protected KeyComputer<Extension> getExtensionKey()
{
return e -> e.getGroupId() + ':' + e.getArtifactId();
}
@Override
protected KeyComputer<Exclusion> getExclusionKey()
{
return e -> e.getGroupId() + ':' + e.getArtifactId();
}
protected String extrapolateChildUrl( String parentUrl, boolean appendPath, Map<Object, Object> context )
{
return parentUrl;
}
}