View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.model.merge;
20  
21  import java.util.ArrayList;
22  import java.util.LinkedHashMap;
23  import java.util.LinkedHashSet;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Objects;
27  import java.util.Set;
28  
29  import org.apache.maven.api.model.BuildBase;
30  import org.apache.maven.api.model.CiManagement;
31  import org.apache.maven.api.model.Dependency;
32  import org.apache.maven.api.model.DeploymentRepository;
33  import org.apache.maven.api.model.DistributionManagement;
34  import org.apache.maven.api.model.Exclusion;
35  import org.apache.maven.api.model.Extension;
36  import org.apache.maven.api.model.InputLocation;
37  import org.apache.maven.api.model.IssueManagement;
38  import org.apache.maven.api.model.Model;
39  import org.apache.maven.api.model.ModelBase;
40  import org.apache.maven.api.model.Organization;
41  import org.apache.maven.api.model.Plugin;
42  import org.apache.maven.api.model.PluginExecution;
43  import org.apache.maven.api.model.ReportPlugin;
44  import org.apache.maven.api.model.ReportSet;
45  import org.apache.maven.api.model.Repository;
46  import org.apache.maven.api.model.RepositoryBase;
47  import org.apache.maven.api.model.Scm;
48  import org.apache.maven.api.model.Site;
49  import org.apache.maven.model.v4.MavenMerger;
50  import org.codehaus.plexus.util.StringUtils;
51  
52  /**
53   * The domain-specific model merger for the Maven POM, overriding generic code from parent class when necessary with
54   * more adapted algorithms.
55   *
56   * @author Benjamin Bentmann
57   */
58  public class MavenModelMerger extends MavenMerger {
59  
60      /**
61       * The hint key for the child path adjustment used during inheritance for URL calculations.
62       */
63      public static final String CHILD_PATH_ADJUSTMENT = "child-path-adjustment";
64  
65      /**
66       * The context key for the artifact id of the target model.
67       */
68      public static final String ARTIFACT_ID = "artifact-id";
69  
70      public MavenModelMerger() {
71          super(false);
72      }
73  
74      /**
75       * Merges the specified source object into the given target object.
76       *
77       * @param target The target object whose existing contents should be merged with the source, must not be
78       *            <code>null</code>.
79       * @param source The (read-only) source object that should be merged into the target object, may be
80       *            <code>null</code>.
81       * @param sourceDominant A flag indicating whether either the target object or the source object provides the
82       *            dominant data.
83       * @param hints A set of key-value pairs that customized merger implementations can use to carry domain-specific
84       *            information along, may be <code>null</code>.
85       */
86      public void merge(
87              org.apache.maven.model.Model target,
88              org.apache.maven.model.Model source,
89              boolean sourceDominant,
90              Map<?, ?> hints) {
91          Objects.requireNonNull(target, "target cannot be null");
92          if (source == null) {
93              return;
94          }
95          target.update(merge(target.getDelegate(), source.getDelegate(), sourceDominant, hints));
96      }
97  
98      @Override
99      public Model merge(Model target, Model source, boolean sourceDominant, Map<?, ?> hints) {
100         return super.merge(target, source, sourceDominant, hints);
101     }
102 
103     @Override
104     protected Model mergeModel(Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
105         context.put(ARTIFACT_ID, target.getArtifactId());
106 
107         return super.mergeModel(target, source, sourceDominant, context);
108     }
109 
110     @Override
111     protected void mergeModel_Name(
112             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
113         String src = source.getName();
114         if (src != null) {
115             if (sourceDominant) {
116                 builder.name(src);
117                 builder.location("name", source.getLocation("name"));
118             }
119         }
120     }
121 
122     @Override
123     protected void mergeModel_Url(
124             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
125         String src = source.getUrl();
126         if (src != null) {
127             if (sourceDominant) {
128                 builder.url(src);
129                 builder.location("url", source.getLocation("url"));
130             } else if (target.getUrl() == null) {
131                 builder.url(extrapolateChildUrl(src, source.isChildProjectUrlInheritAppendPath(), context));
132                 builder.location("url", source.getLocation("url"));
133             }
134         }
135     }
136 
137     /*
138      * TODO: Whether the merge continues recursively into an existing node or not could be an option for the generated
139      * merger
140      */
141     @Override
142     protected void mergeModel_Organization(
143             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
144         Organization src = source.getOrganization();
145         if (src != null) {
146             Organization tgt = target.getOrganization();
147             if (tgt == null) {
148                 builder.organization(src);
149                 builder.location("organisation", source.getLocation("organisation"));
150             }
151         }
152     }
153 
154     @Override
155     protected void mergeModel_IssueManagement(
156             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
157         IssueManagement src = source.getIssueManagement();
158         if (src != null) {
159             IssueManagement tgt = target.getIssueManagement();
160             if (tgt == null) {
161                 builder.issueManagement(src);
162                 builder.location("issueManagement", source.getLocation("issueManagement"));
163             }
164         }
165     }
166 
167     @Override
168     protected void mergeModel_CiManagement(
169             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
170         CiManagement src = source.getCiManagement();
171         if (src != null) {
172             CiManagement tgt = target.getCiManagement();
173             if (tgt == null) {
174                 builder.ciManagement(src);
175                 builder.location("ciManagement", source.getLocation("ciManagement"));
176             }
177         }
178     }
179 
180     @Override
181     protected void mergeModel_ModelVersion(
182             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
183         // neither inherited nor injected
184     }
185 
186     @Override
187     protected void mergeModel_ArtifactId(
188             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
189         // neither inherited nor injected
190     }
191 
192     @Override
193     protected void mergeModel_Profiles(
194             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
195         // neither inherited nor injected
196     }
197 
198     @Override
199     protected void mergeModel_Prerequisites(
200             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
201         // neither inherited nor injected
202     }
203 
204     @Override
205     protected void mergeModel_Licenses(
206             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
207         builder.licenses(target.getLicenses().isEmpty() ? source.getLicenses() : target.getLicenses());
208     }
209 
210     @Override
211     protected void mergeModel_Developers(
212             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
213         builder.developers(target.getDevelopers().isEmpty() ? source.getDevelopers() : target.getDevelopers());
214     }
215 
216     @Override
217     protected void mergeModel_Contributors(
218             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
219         builder.contributors(target.getContributors().isEmpty() ? source.getContributors() : target.getContributors());
220     }
221 
222     @Override
223     protected void mergeModel_MailingLists(
224             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
225         if (target.getMailingLists().isEmpty()) {
226             builder.mailingLists(source.getMailingLists());
227         }
228     }
229 
230     @Override
231     protected void mergeModelBase_Modules(
232             ModelBase.Builder builder,
233             ModelBase target,
234             ModelBase source,
235             boolean sourceDominant,
236             Map<Object, Object> context) {
237         List<String> src = source.getModules();
238         if (!src.isEmpty() && sourceDominant) {
239             List<Integer> indices = new ArrayList<>();
240             List<String> tgt = target.getModules();
241             Set<String> excludes = new LinkedHashSet<>(tgt);
242             List<String> merged = new ArrayList<>(tgt.size() + src.size());
243             merged.addAll(tgt);
244             for (int i = 0, n = tgt.size(); i < n; i++) {
245                 indices.add(i);
246             }
247             for (int i = 0, n = src.size(); i < n; i++) {
248                 String s = src.get(i);
249                 if (!excludes.contains(s)) {
250                     merged.add(s);
251                     indices.add(~i);
252                 }
253             }
254             builder.modules(merged);
255             builder.location(
256                     "modules",
257                     InputLocation.merge(target.getLocation("modules"), source.getLocation("modules"), indices));
258         }
259     }
260 
261     /*
262      * TODO: The order of the merged list could be controlled by an attribute in the model association: target-first,
263      * source-first, dominant-first, recessive-first
264      */
265     @Override
266     protected void mergeModelBase_Repositories(
267             ModelBase.Builder builder,
268             ModelBase target,
269             ModelBase source,
270             boolean sourceDominant,
271             Map<Object, Object> context) {
272         List<Repository> src = source.getRepositories();
273         if (!src.isEmpty()) {
274             List<Repository> tgt = target.getRepositories();
275             Map<Object, Repository> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
276 
277             List<Repository> dominant, recessive;
278             if (sourceDominant) {
279                 dominant = src;
280                 recessive = tgt;
281             } else {
282                 dominant = tgt;
283                 recessive = src;
284             }
285 
286             for (Repository element : dominant) {
287                 Object key = getRepositoryKey().apply(element);
288                 merged.put(key, element);
289             }
290 
291             for (Repository element : recessive) {
292                 Object key = getRepositoryKey().apply(element);
293                 if (!merged.containsKey(key)) {
294                     merged.put(key, element);
295                 }
296             }
297 
298             builder.repositories(merged.values());
299         }
300     }
301 
302     @Override
303     protected void mergeModelBase_PluginRepositories(
304             ModelBase.Builder builder,
305             ModelBase target,
306             ModelBase source,
307             boolean sourceDominant,
308             Map<Object, Object> context) {
309         List<Repository> src = source.getPluginRepositories();
310         if (!src.isEmpty()) {
311             List<Repository> tgt = target.getPluginRepositories();
312             Map<Object, Repository> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
313 
314             List<Repository> dominant, recessive;
315             if (sourceDominant) {
316                 dominant = src;
317                 recessive = tgt;
318             } else {
319                 dominant = tgt;
320                 recessive = src;
321             }
322 
323             for (Repository element : dominant) {
324                 Object key = getRepositoryKey().apply(element);
325                 merged.put(key, element);
326             }
327 
328             for (Repository element : recessive) {
329                 Object key = getRepositoryKey().apply(element);
330                 if (!merged.containsKey(key)) {
331                     merged.put(key, element);
332                 }
333             }
334 
335             builder.pluginRepositories(merged.values());
336         }
337     }
338 
339     /*
340      * TODO: Whether duplicates should be removed looks like an option for the generated merger.
341      */
342     @Override
343     protected void mergeBuildBase_Filters(
344             BuildBase.Builder builder,
345             BuildBase target,
346             BuildBase source,
347             boolean sourceDominant,
348             Map<Object, Object> context) {
349         List<String> src = source.getFilters();
350         if (!src.isEmpty()) {
351             List<String> tgt = target.getFilters();
352             Set<String> excludes = new LinkedHashSet<>(tgt);
353             List<String> merged = new ArrayList<>(tgt.size() + src.size());
354             merged.addAll(tgt);
355             for (String s : src) {
356                 if (!excludes.contains(s)) {
357                     merged.add(s);
358                 }
359             }
360             builder.filters(merged);
361         }
362     }
363 
364     @Override
365     protected void mergeBuildBase_Resources(
366             BuildBase.Builder builder,
367             BuildBase target,
368             BuildBase source,
369             boolean sourceDominant,
370             Map<Object, Object> context) {
371         if (sourceDominant || target.getResources().isEmpty()) {
372             super.mergeBuildBase_Resources(builder, target, source, sourceDominant, context);
373         }
374     }
375 
376     @Override
377     protected void mergeBuildBase_TestResources(
378             BuildBase.Builder builder,
379             BuildBase target,
380             BuildBase source,
381             boolean sourceDominant,
382             Map<Object, Object> context) {
383         if (sourceDominant || target.getTestResources().isEmpty()) {
384             super.mergeBuildBase_TestResources(builder, target, source, sourceDominant, context);
385         }
386     }
387 
388     @Override
389     protected void mergeDistributionManagement_Relocation(
390             DistributionManagement.Builder builder,
391             DistributionManagement target,
392             DistributionManagement source,
393             boolean sourceDominant,
394             Map<Object, Object> context) {}
395 
396     @Override
397     protected void mergeDistributionManagement_Repository(
398             DistributionManagement.Builder builder,
399             DistributionManagement target,
400             DistributionManagement source,
401             boolean sourceDominant,
402             Map<Object, Object> context) {
403         DeploymentRepository src = source.getRepository();
404         if (src != null) {
405             DeploymentRepository tgt = target.getRepository();
406             if (sourceDominant || tgt == null) {
407                 tgt = DeploymentRepository.newInstance(false);
408                 builder.repository(mergeDeploymentRepository(tgt, src, sourceDominant, context));
409             }
410         }
411     }
412 
413     @Override
414     protected void mergeDistributionManagement_SnapshotRepository(
415             DistributionManagement.Builder builder,
416             DistributionManagement target,
417             DistributionManagement source,
418             boolean sourceDominant,
419             Map<Object, Object> context) {
420         DeploymentRepository src = source.getSnapshotRepository();
421         if (src != null) {
422             DeploymentRepository tgt = target.getSnapshotRepository();
423             if (sourceDominant || tgt == null) {
424                 tgt = DeploymentRepository.newInstance(false);
425                 builder.snapshotRepository(mergeDeploymentRepository(tgt, src, sourceDominant, context));
426             }
427         }
428     }
429 
430     @Override
431     protected void mergeDistributionManagement_Site(
432             DistributionManagement.Builder builder,
433             DistributionManagement target,
434             DistributionManagement source,
435             boolean sourceDominant,
436             Map<Object, Object> context) {
437         Site src = source.getSite();
438         if (src != null) {
439             Site tgt = target.getSite();
440             if (tgt == null) {
441                 tgt = Site.newBuilder(false).build();
442             }
443             Site.Builder sbuilder = Site.newBuilder(tgt);
444             if (sourceDominant || tgt == null || isSiteEmpty(tgt)) {
445                 mergeSite(sbuilder, tgt, src, sourceDominant, context);
446             }
447             super.mergeSite_ChildSiteUrlInheritAppendPath(sbuilder, tgt, src, sourceDominant, context);
448             builder.site(sbuilder.build());
449         }
450     }
451 
452     @Override
453     protected void mergeSite_ChildSiteUrlInheritAppendPath(
454             Site.Builder builder, Site target, Site source, boolean sourceDominant, Map<Object, Object> context) {}
455 
456     protected boolean isSiteEmpty(Site site) {
457         return StringUtils.isEmpty(site.getId())
458                 && StringUtils.isEmpty(site.getName())
459                 && StringUtils.isEmpty(site.getUrl());
460     }
461 
462     @Override
463     protected void mergeSite_Url(
464             Site.Builder builder, Site target, Site source, boolean sourceDominant, Map<Object, Object> context) {
465         String src = source.getUrl();
466         if (src != null) {
467             if (sourceDominant) {
468                 builder.url(src);
469                 builder.location("url", source.getLocation("url"));
470             } else if (target.getUrl() == null) {
471                 builder.url(extrapolateChildUrl(src, source.isChildSiteUrlInheritAppendPath(), context));
472                 builder.location("url", source.getLocation("url"));
473             }
474         }
475     }
476 
477     @Override
478     protected void mergeScm_Url(
479             Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
480         String src = source.getUrl();
481         if (src != null) {
482             if (sourceDominant) {
483                 builder.url(src);
484                 builder.location("url", source.getLocation("url"));
485             } else if (target.getUrl() == null) {
486                 builder.url(extrapolateChildUrl(src, source.isChildScmUrlInheritAppendPath(), context));
487                 builder.location("url", source.getLocation("url"));
488             }
489         }
490     }
491 
492     @Override
493     protected void mergeScm_Connection(
494             Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
495         String src = source.getConnection();
496         if (src != null) {
497             if (sourceDominant) {
498                 builder.connection(src);
499                 builder.location("connection", source.getLocation("connection"));
500             } else if (target.getConnection() == null) {
501                 builder.connection(extrapolateChildUrl(src, source.isChildScmConnectionInheritAppendPath(), context));
502                 builder.location("connection", source.getLocation("connection"));
503             }
504         }
505     }
506 
507     @Override
508     protected void mergeScm_DeveloperConnection(
509             Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
510         String src = source.getDeveloperConnection();
511         if (src != null) {
512             if (sourceDominant) {
513                 builder.developerConnection(src);
514                 builder.location("developerConnection", source.getLocation("developerConnection"));
515             } else if (target.getDeveloperConnection() == null) {
516                 String e = extrapolateChildUrl(src, source.isChildScmDeveloperConnectionInheritAppendPath(), context);
517                 builder.developerConnection(e);
518                 builder.location("developerConnection", source.getLocation("developerConnection"));
519             }
520         }
521     }
522 
523     @Override
524     protected void mergePlugin_Executions(
525             Plugin.Builder builder, Plugin target, Plugin source, boolean sourceDominant, Map<Object, Object> context) {
526         List<PluginExecution> src = source.getExecutions();
527         if (!src.isEmpty()) {
528             List<PluginExecution> tgt = target.getExecutions();
529             Map<Object, PluginExecution> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
530 
531             for (PluginExecution element : src) {
532                 if (sourceDominant || (element.getInherited() != null ? element.isInherited() : source.isInherited())) {
533                     Object key = getPluginExecutionKey().apply(element);
534                     merged.put(key, element);
535                 }
536             }
537 
538             for (PluginExecution element : tgt) {
539                 Object key = getPluginExecutionKey().apply(element);
540                 PluginExecution existing = merged.get(key);
541                 if (existing != null) {
542                     element = mergePluginExecution(element, existing, sourceDominant, context);
543                 }
544                 merged.put(key, element);
545             }
546 
547             builder.executions(merged.values());
548         }
549     }
550 
551     @Override
552     protected void mergePluginExecution_Goals(
553             PluginExecution.Builder builder,
554             PluginExecution target,
555             PluginExecution source,
556             boolean sourceDominant,
557             Map<Object, Object> context) {
558         List<String> src = source.getGoals();
559         if (!src.isEmpty()) {
560             List<String> tgt = target.getGoals();
561             Set<String> excludes = new LinkedHashSet<>(tgt);
562             List<String> merged = new ArrayList<>(tgt.size() + src.size());
563             merged.addAll(tgt);
564             for (String s : src) {
565                 if (!excludes.contains(s)) {
566                     merged.add(s);
567                 }
568             }
569             builder.goals(merged);
570         }
571     }
572 
573     @Override
574     protected void mergeReportPlugin_ReportSets(
575             ReportPlugin.Builder builder,
576             ReportPlugin target,
577             ReportPlugin source,
578             boolean sourceDominant,
579             Map<Object, Object> context) {
580         List<ReportSet> src = source.getReportSets();
581         if (!src.isEmpty()) {
582             List<ReportSet> tgt = target.getReportSets();
583             Map<Object, ReportSet> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
584 
585             for (ReportSet rset : src) {
586                 if (sourceDominant || (rset.getInherited() != null ? rset.isInherited() : source.isInherited())) {
587                     Object key = getReportSetKey().apply(rset);
588                     merged.put(key, rset);
589                 }
590             }
591 
592             for (ReportSet element : tgt) {
593                 Object key = getReportSetKey().apply(element);
594                 ReportSet existing = merged.get(key);
595                 if (existing != null) {
596                     mergeReportSet(element, existing, sourceDominant, context);
597                 }
598                 merged.put(key, element);
599             }
600 
601             builder.reportSets(merged.values());
602         }
603     }
604 
605     @Override
606     protected KeyComputer<Dependency> getDependencyKey() {
607         return Dependency::getManagementKey;
608     }
609 
610     @Override
611     protected KeyComputer<Plugin> getPluginKey() {
612         return Plugin::getKey;
613     }
614 
615     @Override
616     protected KeyComputer<PluginExecution> getPluginExecutionKey() {
617         return PluginExecution::getId;
618     }
619 
620     @Override
621     protected KeyComputer<ReportPlugin> getReportPluginKey() {
622         return ReportPlugin::getKey;
623     }
624 
625     @Override
626     protected KeyComputer<ReportSet> getReportSetKey() {
627         return ReportSet::getId;
628     }
629 
630     @Override
631     protected KeyComputer<RepositoryBase> getRepositoryBaseKey() {
632         return RepositoryBase::getId;
633     }
634 
635     @Override
636     protected KeyComputer<Extension> getExtensionKey() {
637         return e -> e.getGroupId() + ':' + e.getArtifactId();
638     }
639 
640     @Override
641     protected KeyComputer<Exclusion> getExclusionKey() {
642         return e -> e.getGroupId() + ':' + e.getArtifactId();
643     }
644 
645     protected String extrapolateChildUrl(String parentUrl, boolean appendPath, Map<Object, Object> context) {
646         return parentUrl;
647     }
648 }