blob: 9d226064de28f9968703dd8d4c3c3df85302295e [file] [log] [blame]
/*
* 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.
*/
package org.apache.maven.model.merge;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.apache.maven.api.model.BuildBase;
import org.apache.maven.api.model.CiManagement;
import org.apache.maven.api.model.Dependency;
import org.apache.maven.api.model.DeploymentRepository;
import org.apache.maven.api.model.DistributionManagement;
import org.apache.maven.api.model.Exclusion;
import org.apache.maven.api.model.Extension;
import org.apache.maven.api.model.InputLocation;
import org.apache.maven.api.model.IssueManagement;
import org.apache.maven.api.model.Model;
import org.apache.maven.api.model.ModelBase;
import org.apache.maven.api.model.Organization;
import org.apache.maven.api.model.Plugin;
import org.apache.maven.api.model.PluginExecution;
import org.apache.maven.api.model.ReportPlugin;
import org.apache.maven.api.model.ReportSet;
import org.apache.maven.api.model.Repository;
import org.apache.maven.api.model.RepositoryBase;
import org.apache.maven.api.model.Scm;
import org.apache.maven.api.model.Site;
import org.apache.maven.model.v4.MavenMerger;
/**
* The domain-specific model merger for the Maven POM, overriding generic code from parent class when necessary with
* more adapted algorithms.
*
*/
public class MavenModelMerger extends MavenMerger {
/**
* 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";
public MavenModelMerger() {
super(false);
}
/**
* Merges the specified source object into the given target object.
*
* @param target The target object whose existing contents should be merged with the source, must not be
* <code>null</code>.
* @param source The (read-only) source object that should be merged into the target object, may be
* <code>null</code>.
* @param sourceDominant A flag indicating whether either the target object or the source object provides the
* dominant data.
* @param hints A set of key-value pairs that customized merger implementations can use to carry domain-specific
* information along, may be <code>null</code>.
*/
public void merge(
org.apache.maven.model.Model target,
org.apache.maven.model.Model source,
boolean sourceDominant,
Map<?, ?> hints) {
Objects.requireNonNull(target, "target cannot be null");
if (source == null) {
return;
}
target.update(merge(target.getDelegate(), source.getDelegate(), sourceDominant, hints));
}
@Override
public Model merge(Model target, Model source, boolean sourceDominant, Map<?, ?> hints) {
return super.merge(target, source, sourceDominant, hints);
}
@Override
protected Model mergeModel(Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
context.put(ARTIFACT_ID, target.getArtifactId());
return super.mergeModel(target, source, sourceDominant, context);
}
@Override
protected void mergeModel_Name(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getName();
if (src != null) {
if (sourceDominant) {
builder.name(src);
builder.location("name", source.getLocation("name"));
}
}
}
@Override
protected void mergeModel_Url(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getUrl();
if (src != null) {
if (sourceDominant) {
builder.url(src);
builder.location("url", source.getLocation("url"));
} else if (target.getUrl() == null) {
builder.url(extrapolateChildUrl(src, source.isChildProjectUrlInheritAppendPath(), context));
builder.location("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.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
Organization src = source.getOrganization();
if (src != null) {
Organization tgt = target.getOrganization();
if (tgt == null) {
builder.organization(src);
builder.location("organisation", source.getLocation("organisation"));
}
}
}
@Override
protected void mergeModel_IssueManagement(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
IssueManagement src = source.getIssueManagement();
if (src != null) {
IssueManagement tgt = target.getIssueManagement();
if (tgt == null) {
builder.issueManagement(src);
builder.location("issueManagement", source.getLocation("issueManagement"));
}
}
}
@Override
protected void mergeModel_CiManagement(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
CiManagement src = source.getCiManagement();
if (src != null) {
CiManagement tgt = target.getCiManagement();
if (tgt == null) {
builder.ciManagement(src);
builder.location("ciManagement", source.getLocation("ciManagement"));
}
}
}
@Override
protected void mergeModel_ModelVersion(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// neither inherited nor injected
}
@Override
protected void mergeModel_ArtifactId(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// neither inherited nor injected
}
@Override
protected void mergeModel_Profiles(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// neither inherited nor injected
}
@Override
protected void mergeModel_Prerequisites(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
// neither inherited nor injected
}
@Override
protected void mergeModel_Licenses(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
builder.licenses(target.getLicenses().isEmpty() ? source.getLicenses() : target.getLicenses());
}
@Override
protected void mergeModel_Developers(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
builder.developers(target.getDevelopers().isEmpty() ? source.getDevelopers() : target.getDevelopers());
}
@Override
protected void mergeModel_Contributors(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
builder.contributors(target.getContributors().isEmpty() ? source.getContributors() : target.getContributors());
}
@Override
protected void mergeModel_MailingLists(
Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
if (target.getMailingLists().isEmpty()) {
builder.mailingLists(source.getMailingLists());
}
}
@Override
protected void mergeModelBase_Modules(
ModelBase.Builder builder,
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);
}
}
builder.modules(merged);
builder.location(
"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.Builder builder,
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);
}
}
builder.repositories(merged.values());
}
}
@Override
protected void mergeModelBase_PluginRepositories(
ModelBase.Builder builder,
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);
}
}
builder.pluginRepositories(merged.values());
}
}
/*
* TODO: Whether duplicates should be removed looks like an option for the generated merger.
*/
@Override
protected void mergeBuildBase_Filters(
BuildBase.Builder builder,
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);
}
}
builder.filters(merged);
}
}
@Override
protected void mergeBuildBase_Resources(
BuildBase.Builder builder,
BuildBase target,
BuildBase source,
boolean sourceDominant,
Map<Object, Object> context) {
if (sourceDominant || target.getResources().isEmpty()) {
super.mergeBuildBase_Resources(builder, target, source, sourceDominant, context);
}
}
@Override
protected void mergeBuildBase_TestResources(
BuildBase.Builder builder,
BuildBase target,
BuildBase source,
boolean sourceDominant,
Map<Object, Object> context) {
if (sourceDominant || target.getTestResources().isEmpty()) {
super.mergeBuildBase_TestResources(builder, target, source, sourceDominant, context);
}
}
@Override
protected void mergeDistributionManagement_Relocation(
DistributionManagement.Builder builder,
DistributionManagement target,
DistributionManagement source,
boolean sourceDominant,
Map<Object, Object> context) {}
@Override
protected void mergeDistributionManagement_Repository(
DistributionManagement.Builder builder,
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 = DeploymentRepository.newInstance(false);
builder.repository(mergeDeploymentRepository(tgt, src, sourceDominant, context));
}
}
}
@Override
protected void mergeDistributionManagement_SnapshotRepository(
DistributionManagement.Builder builder,
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 = DeploymentRepository.newInstance(false);
builder.snapshotRepository(mergeDeploymentRepository(tgt, src, sourceDominant, context));
}
}
}
@Override
protected void mergeDistributionManagement_Site(
DistributionManagement.Builder builder,
DistributionManagement target,
DistributionManagement source,
boolean sourceDominant,
Map<Object, Object> context) {
Site src = source.getSite();
if (src != null) {
Site tgt = target.getSite();
if (tgt == null) {
tgt = Site.newBuilder(false).build();
}
Site.Builder sbuilder = Site.newBuilder(tgt);
if (sourceDominant || tgt == null || isSiteEmpty(tgt)) {
mergeSite(sbuilder, tgt, src, sourceDominant, context);
}
super.mergeSite_ChildSiteUrlInheritAppendPath(sbuilder, tgt, src, sourceDominant, context);
builder.site(sbuilder.build());
}
}
@Override
protected void mergeSite_ChildSiteUrlInheritAppendPath(
Site.Builder builder, Site target, Site source, boolean sourceDominant, Map<Object, Object> context) {}
protected boolean isSiteEmpty(Site site) {
return (site.getId() == null || site.getId().isEmpty())
&& (site.getName() == null || site.getName().isEmpty())
&& (site.getUrl() == null || site.getUrl().isEmpty());
}
@Override
protected void mergeSite_Url(
Site.Builder builder, Site target, Site source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getUrl();
if (src != null) {
if (sourceDominant) {
builder.url(src);
builder.location("url", source.getLocation("url"));
} else if (target.getUrl() == null) {
builder.url(extrapolateChildUrl(src, source.isChildSiteUrlInheritAppendPath(), context));
builder.location("url", source.getLocation("url"));
}
}
}
@Override
protected void mergeScm_Url(
Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getUrl();
if (src != null) {
if (sourceDominant) {
builder.url(src);
builder.location("url", source.getLocation("url"));
} else if (target.getUrl() == null) {
builder.url(extrapolateChildUrl(src, source.isChildScmUrlInheritAppendPath(), context));
builder.location("url", source.getLocation("url"));
}
}
}
@Override
protected void mergeScm_Connection(
Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getConnection();
if (src != null) {
if (sourceDominant) {
builder.connection(src);
builder.location("connection", source.getLocation("connection"));
} else if (target.getConnection() == null) {
builder.connection(extrapolateChildUrl(src, source.isChildScmConnectionInheritAppendPath(), context));
builder.location("connection", source.getLocation("connection"));
}
}
}
@Override
protected void mergeScm_DeveloperConnection(
Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
String src = source.getDeveloperConnection();
if (src != null) {
if (sourceDominant) {
builder.developerConnection(src);
builder.location("developerConnection", source.getLocation("developerConnection"));
} else if (target.getDeveloperConnection() == null) {
String e = extrapolateChildUrl(src, source.isChildScmDeveloperConnectionInheritAppendPath(), context);
builder.developerConnection(e);
builder.location("developerConnection", source.getLocation("developerConnection"));
}
}
}
@Override
protected void mergePlugin_Executions(
Plugin.Builder builder, 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) {
element = mergePluginExecution(element, existing, sourceDominant, context);
}
merged.put(key, element);
}
builder.executions(merged.values());
}
}
@Override
protected void mergePluginExecution_Goals(
PluginExecution.Builder builder,
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);
}
}
builder.goals(merged);
}
}
@Override
protected void mergeReportPlugin_ReportSets(
ReportPlugin.Builder builder,
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);
}
builder.reportSets(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;
}
}