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.internal.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         // neither inherited nor injected
169     }
170 
171     @Override
172     protected void mergeModel_Prerequisites(
173             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
174         // neither inherited nor injected
175     }
176 
177     @Override
178     protected void mergeModel_Licenses(
179             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
180         builder.licenses(target.getLicenses().isEmpty() ? source.getLicenses() : target.getLicenses());
181     }
182 
183     @Override
184     protected void mergeModel_Developers(
185             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
186         builder.developers(target.getDevelopers().isEmpty() ? source.getDevelopers() : target.getDevelopers());
187     }
188 
189     @Override
190     protected void mergeModel_Contributors(
191             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
192         builder.contributors(target.getContributors().isEmpty() ? source.getContributors() : target.getContributors());
193     }
194 
195     @Override
196     protected void mergeModel_MailingLists(
197             Model.Builder builder, Model target, Model source, boolean sourceDominant, Map<Object, Object> context) {
198         if (target.getMailingLists().isEmpty()) {
199             builder.mailingLists(source.getMailingLists());
200         }
201     }
202 
203     @Override
204     protected void mergeModelBase_Modules(
205             ModelBase.Builder builder,
206             ModelBase target,
207             ModelBase source,
208             boolean sourceDominant,
209             Map<Object, Object> context) {
210         List<String> src = source.getModules();
211         if (!src.isEmpty() && sourceDominant) {
212             List<Integer> indices = new ArrayList<>();
213             List<String> tgt = target.getModules();
214             Set<String> excludes = new LinkedHashSet<>(tgt);
215             List<String> merged = new ArrayList<>(tgt.size() + src.size());
216             merged.addAll(tgt);
217             for (int i = 0, n = tgt.size(); i < n; i++) {
218                 indices.add(i);
219             }
220             for (int i = 0, n = src.size(); i < n; i++) {
221                 String s = src.get(i);
222                 if (!excludes.contains(s)) {
223                     merged.add(s);
224                     indices.add(~i);
225                 }
226             }
227             builder.modules(merged);
228             builder.location(
229                     "modules",
230                     InputLocation.merge(target.getLocation("modules"), source.getLocation("modules"), indices));
231         }
232     }
233 
234     @Override
235     protected void mergeModelBase_Subprojects(
236             ModelBase.Builder builder,
237             ModelBase target,
238             ModelBase source,
239             boolean sourceDominant,
240             Map<Object, Object> context) {
241         List<String> src = source.getSubprojects();
242         if (!src.isEmpty() && sourceDominant) {
243             List<Integer> indices = new ArrayList<>();
244             List<String> tgt = target.getSubprojects();
245             Set<String> excludes = new LinkedHashSet<>(tgt);
246             List<String> merged = new ArrayList<>(tgt.size() + src.size());
247             merged.addAll(tgt);
248             for (int i = 0, n = tgt.size(); i < n; i++) {
249                 indices.add(i);
250             }
251             for (int i = 0, n = src.size(); i < n; i++) {
252                 String s = src.get(i);
253                 if (!excludes.contains(s)) {
254                     merged.add(s);
255                     indices.add(~i);
256                 }
257             }
258             builder.subprojects(merged);
259             builder.location(
260                     "subprojects",
261                     InputLocation.merge(target.getLocation("subprojects"), source.getLocation("subprojects"), indices));
262         }
263     }
264 
265     /*
266      * TODO: The order of the merged list could be controlled by an attribute in the model association: target-first,
267      * source-first, dominant-first, recessive-first
268      */
269     @Override
270     protected void mergeModelBase_Repositories(
271             ModelBase.Builder builder,
272             ModelBase target,
273             ModelBase source,
274             boolean sourceDominant,
275             Map<Object, Object> context) {
276         List<Repository> src = source.getRepositories();
277         if (!src.isEmpty()) {
278             List<Repository> tgt = target.getRepositories();
279             Map<Object, Repository> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
280 
281             List<Repository> dominant, recessive;
282             if (sourceDominant) {
283                 dominant = src;
284                 recessive = tgt;
285             } else {
286                 dominant = tgt;
287                 recessive = src;
288             }
289 
290             for (Repository element : dominant) {
291                 Object key = getRepositoryKey().apply(element);
292                 merged.put(key, element);
293             }
294 
295             for (Repository element : recessive) {
296                 Object key = getRepositoryKey().apply(element);
297                 if (!merged.containsKey(key)) {
298                     merged.put(key, element);
299                 }
300             }
301 
302             builder.repositories(merged.values());
303         }
304     }
305 
306     @Override
307     protected void mergeModelBase_PluginRepositories(
308             ModelBase.Builder builder,
309             ModelBase target,
310             ModelBase source,
311             boolean sourceDominant,
312             Map<Object, Object> context) {
313         List<Repository> src = source.getPluginRepositories();
314         if (!src.isEmpty()) {
315             List<Repository> tgt = target.getPluginRepositories();
316             Map<Object, Repository> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
317 
318             List<Repository> dominant, recessive;
319             if (sourceDominant) {
320                 dominant = src;
321                 recessive = tgt;
322             } else {
323                 dominant = tgt;
324                 recessive = src;
325             }
326 
327             for (Repository element : dominant) {
328                 Object key = getRepositoryKey().apply(element);
329                 merged.put(key, element);
330             }
331 
332             for (Repository element : recessive) {
333                 Object key = getRepositoryKey().apply(element);
334                 if (!merged.containsKey(key)) {
335                     merged.put(key, element);
336                 }
337             }
338 
339             builder.pluginRepositories(merged.values());
340         }
341     }
342 
343     /*
344      * TODO: Whether duplicates should be removed looks like an option for the generated merger.
345      */
346     @Override
347     protected void mergeBuildBase_Filters(
348             BuildBase.Builder builder,
349             BuildBase target,
350             BuildBase source,
351             boolean sourceDominant,
352             Map<Object, Object> context) {
353         List<String> src = source.getFilters();
354         if (!src.isEmpty()) {
355             List<String> tgt = target.getFilters();
356             Set<String> excludes = new LinkedHashSet<>(tgt);
357             List<String> merged = new ArrayList<>(tgt.size() + src.size());
358             merged.addAll(tgt);
359             for (String s : src) {
360                 if (!excludes.contains(s)) {
361                     merged.add(s);
362                 }
363             }
364             builder.filters(merged);
365         }
366     }
367 
368     @Override
369     protected void mergeBuildBase_Resources(
370             BuildBase.Builder builder,
371             BuildBase target,
372             BuildBase source,
373             boolean sourceDominant,
374             Map<Object, Object> context) {
375         if (sourceDominant || target.getResources().isEmpty()) {
376             super.mergeBuildBase_Resources(builder, target, source, sourceDominant, context);
377         }
378     }
379 
380     @Override
381     protected void mergeBuildBase_TestResources(
382             BuildBase.Builder builder,
383             BuildBase target,
384             BuildBase source,
385             boolean sourceDominant,
386             Map<Object, Object> context) {
387         if (sourceDominant || target.getTestResources().isEmpty()) {
388             super.mergeBuildBase_TestResources(builder, target, source, sourceDominant, context);
389         }
390     }
391 
392     @Override
393     protected void mergeDistributionManagement_Relocation(
394             DistributionManagement.Builder builder,
395             DistributionManagement target,
396             DistributionManagement source,
397             boolean sourceDominant,
398             Map<Object, Object> context) {}
399 
400     @Override
401     protected void mergeDistributionManagement_Repository(
402             DistributionManagement.Builder builder,
403             DistributionManagement target,
404             DistributionManagement source,
405             boolean sourceDominant,
406             Map<Object, Object> context) {
407         DeploymentRepository src = source.getRepository();
408         if (src != null) {
409             DeploymentRepository tgt = target.getRepository();
410             if (sourceDominant || tgt == null) {
411                 tgt = DeploymentRepository.newInstance(false);
412                 builder.repository(mergeDeploymentRepository(tgt, src, sourceDominant, context));
413             }
414         }
415     }
416 
417     @Override
418     protected void mergeDistributionManagement_SnapshotRepository(
419             DistributionManagement.Builder builder,
420             DistributionManagement target,
421             DistributionManagement source,
422             boolean sourceDominant,
423             Map<Object, Object> context) {
424         DeploymentRepository src = source.getSnapshotRepository();
425         if (src != null) {
426             DeploymentRepository tgt = target.getSnapshotRepository();
427             if (sourceDominant || tgt == null) {
428                 tgt = DeploymentRepository.newInstance(false);
429                 builder.snapshotRepository(mergeDeploymentRepository(tgt, src, sourceDominant, context));
430             }
431         }
432     }
433 
434     @Override
435     protected void mergeDistributionManagement_Site(
436             DistributionManagement.Builder builder,
437             DistributionManagement target,
438             DistributionManagement source,
439             boolean sourceDominant,
440             Map<Object, Object> context) {
441         Site src = source.getSite();
442         if (src != null) {
443             Site tgt = target.getSite();
444             if (tgt == null) {
445                 tgt = Site.newBuilder(false).build();
446             }
447             Site.Builder sbuilder = Site.newBuilder(tgt);
448             if (sourceDominant || tgt == null || isSiteEmpty(tgt)) {
449                 mergeSite(sbuilder, tgt, src, sourceDominant, context);
450             }
451             super.mergeSite_ChildSiteUrlInheritAppendPath(sbuilder, tgt, src, sourceDominant, context);
452             builder.site(sbuilder.build());
453         }
454     }
455 
456     @Override
457     protected void mergeSite_ChildSiteUrlInheritAppendPath(
458             Site.Builder builder, Site target, Site source, boolean sourceDominant, Map<Object, Object> context) {}
459 
460     protected boolean isSiteEmpty(Site site) {
461         return (site.getId() == null || site.getId().isEmpty())
462                 && (site.getName() == null || site.getName().isEmpty())
463                 && (site.getUrl() == null || site.getUrl().isEmpty());
464     }
465 
466     @Override
467     protected void mergeSite_Url(
468             Site.Builder builder, Site target, Site source, boolean sourceDominant, Map<Object, Object> context) {
469         String src = source.getUrl();
470         if (src != null) {
471             if (sourceDominant) {
472                 builder.url(src);
473                 builder.location("url", source.getLocation("url"));
474             } else if (target.getUrl() == null) {
475                 builder.url(extrapolateChildUrl(src, source.isChildSiteUrlInheritAppendPath(), context));
476                 builder.location("url", source.getLocation("url"));
477             }
478         }
479     }
480 
481     @Override
482     protected void mergeScm_Url(
483             Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
484         String src = source.getUrl();
485         if (src != null) {
486             if (sourceDominant) {
487                 builder.url(src);
488                 builder.location("url", source.getLocation("url"));
489             } else if (target.getUrl() == null) {
490                 builder.url(extrapolateChildUrl(src, source.isChildScmUrlInheritAppendPath(), context));
491                 builder.location("url", source.getLocation("url"));
492             }
493         }
494     }
495 
496     @Override
497     protected void mergeScm_Connection(
498             Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
499         String src = source.getConnection();
500         if (src != null) {
501             if (sourceDominant) {
502                 builder.connection(src);
503                 builder.location("connection", source.getLocation("connection"));
504             } else if (target.getConnection() == null) {
505                 builder.connection(extrapolateChildUrl(src, source.isChildScmConnectionInheritAppendPath(), context));
506                 builder.location("connection", source.getLocation("connection"));
507             }
508         }
509     }
510 
511     @Override
512     protected void mergeScm_DeveloperConnection(
513             Scm.Builder builder, Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context) {
514         String src = source.getDeveloperConnection();
515         if (src != null) {
516             if (sourceDominant) {
517                 builder.developerConnection(src);
518                 builder.location("developerConnection", source.getLocation("developerConnection"));
519             } else if (target.getDeveloperConnection() == null) {
520                 String e = extrapolateChildUrl(src, source.isChildScmDeveloperConnectionInheritAppendPath(), context);
521                 builder.developerConnection(e);
522                 builder.location("developerConnection", source.getLocation("developerConnection"));
523             }
524         }
525     }
526 
527     @Override
528     protected void mergePlugin_Executions(
529             Plugin.Builder builder, Plugin target, Plugin source, boolean sourceDominant, Map<Object, Object> context) {
530         List<PluginExecution> src = source.getExecutions();
531         if (!src.isEmpty()) {
532             List<PluginExecution> tgt = target.getExecutions();
533             Map<Object, PluginExecution> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
534 
535             for (PluginExecution element : src) {
536                 if (sourceDominant || (element.getInherited() != null ? element.isInherited() : source.isInherited())) {
537                     Object key = getPluginExecutionKey().apply(element);
538                     merged.put(key, element);
539                 }
540             }
541 
542             for (PluginExecution element : tgt) {
543                 Object key = getPluginExecutionKey().apply(element);
544                 PluginExecution existing = merged.get(key);
545                 if (existing != null) {
546                     element = mergePluginExecution(element, existing, sourceDominant, context);
547                 }
548                 merged.put(key, element);
549             }
550 
551             builder.executions(merged.values());
552         }
553     }
554 
555     @Override
556     protected void mergePluginExecution_Goals(
557             PluginExecution.Builder builder,
558             PluginExecution target,
559             PluginExecution source,
560             boolean sourceDominant,
561             Map<Object, Object> context) {
562         List<String> src = source.getGoals();
563         if (!src.isEmpty()) {
564             List<String> tgt = target.getGoals();
565             Set<String> excludes = new LinkedHashSet<>(tgt);
566             List<String> merged = new ArrayList<>(tgt.size() + src.size());
567             merged.addAll(tgt);
568             for (String s : src) {
569                 if (!excludes.contains(s)) {
570                     merged.add(s);
571                 }
572             }
573             builder.goals(merged);
574         }
575     }
576 
577     @Override
578     protected void mergeReportPlugin_ReportSets(
579             ReportPlugin.Builder builder,
580             ReportPlugin target,
581             ReportPlugin source,
582             boolean sourceDominant,
583             Map<Object, Object> context) {
584         List<ReportSet> src = source.getReportSets();
585         if (!src.isEmpty()) {
586             List<ReportSet> tgt = target.getReportSets();
587             Map<Object, ReportSet> merged = new LinkedHashMap<>((src.size() + tgt.size()) * 2);
588 
589             for (ReportSet rset : src) {
590                 if (sourceDominant || (rset.getInherited() != null ? rset.isInherited() : source.isInherited())) {
591                     Object key = getReportSetKey().apply(rset);
592                     merged.put(key, rset);
593                 }
594             }
595 
596             for (ReportSet element : tgt) {
597                 Object key = getReportSetKey().apply(element);
598                 ReportSet existing = merged.get(key);
599                 if (existing != null) {
600                     mergeReportSet(element, existing, sourceDominant, context);
601                 }
602                 merged.put(key, element);
603             }
604 
605             builder.reportSets(merged.values());
606         }
607     }
608 
609     @Override
610     protected KeyComputer<Dependency> getDependencyKey() {
611         return Dependency::getManagementKey;
612     }
613 
614     @Override
615     protected KeyComputer<Plugin> getPluginKey() {
616         return Plugin::getKey;
617     }
618 
619     @Override
620     protected KeyComputer<PluginExecution> getPluginExecutionKey() {
621         return PluginExecution::getId;
622     }
623 
624     @Override
625     protected KeyComputer<ReportPlugin> getReportPluginKey() {
626         return ReportPlugin::getKey;
627     }
628 
629     @Override
630     protected KeyComputer<ReportSet> getReportSetKey() {
631         return ReportSet::getId;
632     }
633 
634     @Override
635     protected KeyComputer<RepositoryBase> getRepositoryBaseKey() {
636         return RepositoryBase::getId;
637     }
638 
639     @Override
640     protected KeyComputer<Extension> getExtensionKey() {
641         return e -> e.getGroupId() + ':' + e.getArtifactId();
642     }
643 
644     @Override
645     protected KeyComputer<Exclusion> getExclusionKey() {
646         return e -> e.getGroupId() + ':' + e.getArtifactId();
647     }
648 
649     protected String extrapolateChildUrl(String parentUrl, boolean appendPath, Map<Object, Object> context) {
650         return parentUrl;
651     }
652 }