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