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