| /* |
| * 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; |
| } |
| } |