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