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