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