View Javadoc
1   package org.apache.maven.model.merge;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.ArrayList;
23  import java.util.LinkedHashMap;
24  import java.util.LinkedHashSet;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import org.apache.maven.model.BuildBase;
30  import org.apache.maven.model.CiManagement;
31  import org.apache.maven.model.Dependency;
32  import org.apache.maven.model.DeploymentRepository;
33  import org.apache.maven.model.DistributionManagement;
34  import org.apache.maven.model.Exclusion;
35  import org.apache.maven.model.Extension;
36  import org.apache.maven.model.InputLocation;
37  import org.apache.maven.model.IssueManagement;
38  import org.apache.maven.model.Model;
39  import org.apache.maven.model.ModelBase;
40  import org.apache.maven.model.Organization;
41  import org.apache.maven.model.Plugin;
42  import org.apache.maven.model.PluginExecution;
43  import org.apache.maven.model.ReportPlugin;
44  import org.apache.maven.model.ReportSet;
45  import org.apache.maven.model.Repository;
46  import org.apache.maven.model.RepositoryBase;
47  import org.apache.maven.model.Scm;
48  import org.apache.maven.model.Site;
49  import org.codehaus.plexus.util.StringUtils;
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   * @author Benjamin Bentmann
56   */
57  public class MavenModelMerger
58      extends ModelMerger
59  {
60  
61      /**
62       * The hint key for the child path adjustment used during inheritance for URL calculations.
63       */
64      public static final String CHILD_PATH_ADJUSTMENT = "child-path-adjustment";
65  
66      /**
67       * The context key for the artifact id of the target model.
68       */
69      public static final String ARTIFACT_ID = "artifact-id";
70  
71      @Override
72      protected void mergeModel( Model target, Model source, boolean sourceDominant, Map<Object, Object> context )
73      {
74          context.put( ARTIFACT_ID, target.getArtifactId() );
75  
76          super.mergeModel( target, source, sourceDominant, context );
77      }
78  
79      @Override
80      protected void mergeModel_Name( Model target, Model source, boolean sourceDominant, Map<Object, Object> context )
81      {
82          String src = source.getName();
83          if ( src != null )
84          {
85              if ( sourceDominant )
86              {
87                  target.setName( src );
88                  target.setLocation( "name", source.getLocation( "name" ) );
89              }
90          }
91      }
92  
93      @Override
94      protected void mergeModel_Url( Model target, Model source, boolean sourceDominant, Map<Object, Object> context )
95      {
96          String src = source.getUrl();
97          if ( src != null )
98          {
99              if ( sourceDominant )
100             {
101                 target.setUrl( src );
102                 target.setLocation( "url", source.getLocation( "url" ) );
103             }
104             else if ( target.getUrl() == null )
105             {
106                 target.setUrl( extrapolateChildUrl( src, source.isChildProjectUrlInheritAppendPath(), context ) );
107                 target.setLocation( "url", source.getLocation( "url" ) );
108             }
109         }
110     }
111 
112     /*
113      * TODO: Whether the merge continues recursively into an existing node or not could be an option for the generated
114      * merger
115      */
116     @Override
117     protected void mergeModel_Organization( Model target, Model source, boolean sourceDominant,
118                                             Map<Object, Object> context )
119     {
120         Organization src = source.getOrganization();
121         if ( src != null )
122         {
123             Organization tgt = target.getOrganization();
124             if ( tgt == null )
125             {
126                 tgt = new Organization();
127                 tgt.setLocation( "", src.getLocation( "" ) );
128                 target.setOrganization( tgt );
129                 mergeOrganization( tgt, src, sourceDominant, context );
130             }
131         }
132     }
133 
134     @Override
135     protected void mergeModel_IssueManagement( Model target, Model source, boolean sourceDominant,
136                                                Map<Object, Object> context )
137     {
138         IssueManagement src = source.getIssueManagement();
139         if ( src != null )
140         {
141             IssueManagement tgt = target.getIssueManagement();
142             if ( tgt == null )
143             {
144                 tgt = new IssueManagement();
145                 tgt.setLocation( "", src.getLocation( "" ) );
146                 target.setIssueManagement( tgt );
147                 mergeIssueManagement( tgt, src, sourceDominant, context );
148             }
149         }
150     }
151 
152     @Override
153     protected void mergeModel_CiManagement( Model target, Model source, boolean sourceDominant,
154                                             Map<Object, Object> context )
155     {
156         CiManagement src = source.getCiManagement();
157         if ( src != null )
158         {
159             CiManagement tgt = target.getCiManagement();
160             if ( tgt == null )
161             {
162                 tgt = new CiManagement();
163                 tgt.setLocation( "", src.getLocation( "" ) );
164                 target.setCiManagement( tgt );
165                 mergeCiManagement( tgt, src, sourceDominant, context );
166             }
167         }
168     }
169 
170     @Override
171     protected void mergeModel_ModelVersion( Model target, Model source, boolean sourceDominant,
172                                             Map<Object, Object> context )
173     {
174         // neither inherited nor injected
175     }
176 
177     @Override
178     protected void mergeModel_ArtifactId( Model target, Model source, boolean sourceDominant,
179                                           Map<Object, Object> context )
180     {
181         // neither inherited nor injected
182     }
183 
184     @Override
185     protected void mergeModel_Profiles( Model target, Model source, boolean sourceDominant,
186                                         Map<Object, Object> context )
187     {
188         // neither inherited nor injected
189     }
190 
191     @Override
192     protected void mergeModel_Prerequisites( Model target, Model source, boolean sourceDominant,
193                                              Map<Object, Object> context )
194     {
195         // neither inherited nor injected
196     }
197 
198     @Override
199     protected void mergeModel_Licenses( Model target, Model source, boolean sourceDominant,
200                                         Map<Object, Object> context )
201     {
202         if ( target.getLicenses().isEmpty() )
203         {
204             target.setLicenses( new ArrayList<>( source.getLicenses() ) );
205         }
206     }
207 
208     @Override
209     protected void mergeModel_Developers( Model target, Model source, boolean sourceDominant,
210                                           Map<Object, Object> context )
211     {
212         if ( target.getDevelopers().isEmpty() )
213         {
214             target.setDevelopers( new ArrayList<>( source.getDevelopers() ) );
215         }
216     }
217 
218     @Override
219     protected void mergeModel_Contributors( Model target, Model source, boolean sourceDominant,
220                                             Map<Object, Object> context )
221     {
222         if ( target.getContributors().isEmpty() )
223         {
224             target.setContributors( new ArrayList<>( source.getContributors() ) );
225         }
226     }
227 
228     @Override
229     protected void mergeModel_MailingLists( Model target, Model source, boolean sourceDominant,
230                                             Map<Object, Object> context )
231     {
232         if ( target.getMailingLists().isEmpty() )
233         {
234             target.setMailingLists( new ArrayList<>( source.getMailingLists() ) );
235         }
236     }
237 
238     @Override
239     protected void mergeModelBase_Modules( ModelBase target, ModelBase source, boolean sourceDominant,
240                                            Map<Object, Object> context )
241     {
242         List<String> src = source.getModules();
243         if ( !src.isEmpty() && sourceDominant )
244         {
245             List<Integer> indices = new ArrayList<>();
246             List<String> tgt = target.getModules();
247             Set<String> excludes = new LinkedHashSet<>( tgt );
248             List<String> merged = new ArrayList<>( tgt.size() + src.size() );
249             merged.addAll( tgt );
250             for ( int i = 0, n = tgt.size(); i < n; i++ )
251             {
252                 indices.add( i );
253             }
254             for ( int i = 0, n = src.size(); i < n; i++ )
255             {
256                 String s = src.get( i );
257                 if ( !excludes.contains( s ) )
258                 {
259                     merged.add( s );
260                     indices.add( ~i );
261                 }
262             }
263             target.setModules( merged );
264             target.setLocation( "modules", InputLocation.merge( target.getLocation( "modules" ),
265                                                                 source.getLocation( "modules" ), indices ) );
266         }
267     }
268 
269     /*
270      * TODO: The order of the merged list could be controlled by an attribute in the model association: target-first,
271      * source-first, dominant-first, recessive-first
272      */
273     @Override
274     protected void mergeModelBase_Repositories( ModelBase target, ModelBase source, boolean sourceDominant,
275                                                 Map<Object, Object> context )
276     {
277         List<Repository> src = source.getRepositories();
278         if ( !src.isEmpty() )
279         {
280             List<Repository> tgt = target.getRepositories();
281             Map<Object, Repository> merged = new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
282 
283             List<Repository> dominant, recessive;
284             if ( sourceDominant )
285             {
286                 dominant = src;
287                 recessive = tgt;
288             }
289             else
290             {
291                 dominant = tgt;
292                 recessive = src;
293             }
294 
295             for ( Repository element : dominant )
296             {
297                 Object key = getRepositoryKey( element );
298                 merged.put( key, element );
299             }
300 
301             for ( Repository element : recessive )
302             {
303                 Object key = getRepositoryKey( element );
304                 if ( !merged.containsKey( key ) )
305                 {
306                     merged.put( key, element );
307                 }
308             }
309 
310             target.setRepositories( new ArrayList<>( merged.values() ) );
311         }
312     }
313 
314     @Override
315     protected void mergeModelBase_PluginRepositories( ModelBase target, ModelBase source, boolean sourceDominant,
316                                                       Map<Object, Object> context )
317     {
318         List<Repository> src = source.getPluginRepositories();
319         if ( !src.isEmpty() )
320         {
321             List<Repository> tgt = target.getPluginRepositories();
322             Map<Object, Repository> merged = new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
323 
324             List<Repository> dominant, recessive;
325             if ( sourceDominant )
326             {
327                 dominant = src;
328                 recessive = tgt;
329             }
330             else
331             {
332                 dominant = tgt;
333                 recessive = src;
334             }
335 
336             for ( Repository element : dominant )
337             {
338                 Object key = getRepositoryKey( element );
339                 merged.put( key, element );
340             }
341 
342             for ( Repository element : recessive )
343             {
344                 Object key = getRepositoryKey( element );
345                 if ( !merged.containsKey( key ) )
346                 {
347                     merged.put( key, element );
348                 }
349             }
350 
351             target.setPluginRepositories( new ArrayList<>( merged.values() ) );
352         }
353     }
354 
355     /*
356      * TODO: Whether duplicates should be removed looks like an option for the generated merger.
357      */
358     @Override
359     protected void mergeBuildBase_Filters( BuildBase target, BuildBase source, boolean sourceDominant,
360                                            Map<Object, Object> context )
361     {
362         List<String> src = source.getFilters();
363         if ( !src.isEmpty() )
364         {
365             List<String> tgt = target.getFilters();
366             Set<String> excludes = new LinkedHashSet<>( tgt );
367             List<String> merged = new ArrayList<>( tgt.size() + src.size() );
368             merged.addAll( tgt );
369             for ( String s : src )
370             {
371                 if ( !excludes.contains( s ) )
372                 {
373                     merged.add( s );
374                 }
375             }
376             target.setFilters( merged );
377         }
378     }
379 
380     @Override
381     protected void mergeBuildBase_Resources( BuildBase target, BuildBase source, boolean sourceDominant,
382                                              Map<Object, Object> context )
383     {
384         if ( sourceDominant || target.getResources().isEmpty() )
385         {
386             super.mergeBuildBase_Resources( target, source, sourceDominant, context );
387         }
388     }
389 
390     @Override
391     protected void mergeBuildBase_TestResources( BuildBase target, BuildBase source, boolean sourceDominant,
392                                                  Map<Object, Object> context )
393     {
394         if ( sourceDominant || target.getTestResources().isEmpty() )
395         {
396             super.mergeBuildBase_TestResources( target, source, sourceDominant, context );
397         }
398     }
399 
400     @Override
401     protected void mergeDistributionManagement_Repository( DistributionManagement target,
402                                                            DistributionManagement source, boolean sourceDominant,
403                                                            Map<Object, Object> context )
404     {
405         DeploymentRepository src = source.getRepository();
406         if ( src != null )
407         {
408             DeploymentRepository tgt = target.getRepository();
409             if ( sourceDominant || tgt == null )
410             {
411                 tgt = new DeploymentRepository();
412                 tgt.setLocation( "", src.getLocation( "" ) );
413                 target.setRepository( tgt );
414                 mergeDeploymentRepository( tgt, src, sourceDominant, context );
415             }
416         }
417     }
418 
419     @Override
420     protected void mergeDistributionManagement_SnapshotRepository( DistributionManagement target,
421                                                                    DistributionManagement source,
422                                                                    boolean sourceDominant,
423                                                                    Map<Object, Object> context )
424     {
425         DeploymentRepository src = source.getSnapshotRepository();
426         if ( src != null )
427         {
428             DeploymentRepository tgt = target.getSnapshotRepository();
429             if ( sourceDominant || tgt == null )
430             {
431                 tgt = new DeploymentRepository();
432                 tgt.setLocation( "", src.getLocation( "" ) );
433                 target.setSnapshotRepository( tgt );
434                 mergeDeploymentRepository( tgt, src, sourceDominant, context );
435             }
436         }
437     }
438 
439     @Override
440     protected void mergeDistributionManagement_Site( DistributionManagement target, DistributionManagement source,
441                                                      boolean sourceDominant, Map<Object, Object> context )
442     {
443         Site src = source.getSite();
444         if ( src != null )
445         {
446             Site tgt = target.getSite();
447             if ( sourceDominant || tgt == null || isSiteEmpty( tgt ) )
448             {
449                 if ( tgt == null )
450                 {
451                     tgt = new Site();
452                 }
453                 tgt.setLocation( "", src.getLocation( "" ) );
454                 target.setSite( tgt );
455                 mergeSite( tgt, src, sourceDominant, context );
456             }
457             mergeSite_ChildSiteUrlInheritAppendPath( tgt, src, sourceDominant, context );
458         }
459     }
460 
461     @Override
462     protected void mergeSite( Site target, Site source, boolean sourceDominant, Map<Object, Object> context )
463     {
464         mergeSite_Id( target, source, sourceDominant, context );
465         mergeSite_Name( target, source, sourceDominant, context );
466         mergeSite_Url( target, source, sourceDominant, context );
467     }
468 
469     protected boolean isSiteEmpty( Site site )
470     {
471         return StringUtils.isEmpty( site.getId() ) && StringUtils.isEmpty( site.getName() )
472             && StringUtils.isEmpty( site.getUrl() );
473     }
474 
475     @Override
476     protected void mergeSite_Url( Site target, Site source, boolean sourceDominant, Map<Object, Object> context )
477     {
478         String src = source.getUrl();
479         if ( src != null )
480         {
481             if ( sourceDominant )
482             {
483                 target.setUrl( src );
484                 target.setLocation( "url", source.getLocation( "url" ) );
485             }
486             else if ( target.getUrl() == null )
487             {
488                 target.setUrl( extrapolateChildUrl( src, source.isChildSiteUrlInheritAppendPath(), context ) );
489                 target.setLocation( "url", source.getLocation( "url" ) );
490             }
491         }
492     }
493 
494     @Override
495     protected void mergeScm_Url( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
496     {
497         String src = source.getUrl();
498         if ( src != null )
499         {
500             if ( sourceDominant )
501             {
502                 target.setUrl( src );
503                 target.setLocation( "url", source.getLocation( "url" ) );
504             }
505             else if ( target.getUrl() == null )
506             {
507                 target.setUrl( extrapolateChildUrl( src, source.isChildScmUrlInheritAppendPath(), context ) );
508                 target.setLocation( "url", source.getLocation( "url" ) );
509             }
510         }
511     }
512 
513     @Override
514     protected void mergeScm_Connection( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
515     {
516         String src = source.getConnection();
517         if ( src != null )
518         {
519             if ( sourceDominant )
520             {
521                 target.setConnection( src );
522                 target.setLocation( "connection", source.getLocation( "connection" ) );
523             }
524             else if ( target.getConnection() == null )
525             {
526                 target.setConnection( extrapolateChildUrl( src, source.isChildScmConnectionInheritAppendPath(),
527                                                            context ) );
528                 target.setLocation( "connection", source.getLocation( "connection" ) );
529             }
530         }
531     }
532 
533     @Override
534     protected void mergeScm_DeveloperConnection( Scm target, Scm source, boolean sourceDominant,
535                                                  Map<Object, Object> context )
536     {
537         String src = source.getDeveloperConnection();
538         if ( src != null )
539         {
540             if ( sourceDominant )
541             {
542                 target.setDeveloperConnection( src );
543                 target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
544             }
545             else if ( target.getDeveloperConnection() == null )
546             {
547                 String e = extrapolateChildUrl( src, source.isChildScmDeveloperConnectionInheritAppendPath(), context );
548                 target.setDeveloperConnection( e );
549                 target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
550             }
551         }
552     }
553 
554     @Override
555     protected void mergePlugin_Executions( Plugin target, Plugin source, boolean sourceDominant,
556                                            Map<Object, Object> context )
557     {
558         List<PluginExecution> src = source.getExecutions();
559         if ( !src.isEmpty() )
560         {
561             List<PluginExecution> tgt = target.getExecutions();
562             Map<Object, PluginExecution> merged =
563                 new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
564 
565             for ( PluginExecution element : src )
566             {
567                 if ( sourceDominant
568                                 || ( element.getInherited() != null ? element.isInherited() : source.isInherited() ) )
569                 {
570                     Object key = getPluginExecutionKey( element );
571                     merged.put( key, element );
572                 }
573             }
574 
575             for ( PluginExecution element : tgt )
576             {
577                 Object key = getPluginExecutionKey( element );
578                 PluginExecution existing = merged.get( key );
579                 if ( existing != null )
580                 {
581                     mergePluginExecution( element, existing, sourceDominant, context );
582                 }
583                 merged.put( key, element );
584             }
585 
586             target.setExecutions( new ArrayList<>( merged.values() ) );
587         }
588     }
589 
590     @Override
591     protected void mergePluginExecution_Goals( PluginExecution target, PluginExecution source, boolean sourceDominant,
592                                                Map<Object, Object> context )
593     {
594         List<String> src = source.getGoals();
595         if ( !src.isEmpty() )
596         {
597             List<String> tgt = target.getGoals();
598             Set<String> excludes = new LinkedHashSet<>( tgt );
599             List<String> merged = new ArrayList<>( tgt.size() + src.size() );
600             merged.addAll( tgt );
601             for ( String s : src )
602             {
603                 if ( !excludes.contains( s ) )
604                 {
605                     merged.add( s );
606                 }
607             }
608             target.setGoals( merged );
609         }
610     }
611 
612     @Override
613     protected void mergeReportPlugin_ReportSets( ReportPlugin target, ReportPlugin source, boolean sourceDominant,
614                                                  Map<Object, Object> context )
615     {
616         List<ReportSet> src = source.getReportSets();
617         if ( !src.isEmpty() )
618         {
619             List<ReportSet> tgt = target.getReportSets();
620             Map<Object, ReportSet> merged = new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
621 
622             for ( ReportSet rset : src )
623             {
624                 if ( sourceDominant || ( rset.getInherited() != null ? rset.isInherited() : source.isInherited() ) )
625                 {
626                     Object key = getReportSetKey( rset );
627                     merged.put( key, rset );
628                 }
629             }
630 
631             for ( ReportSet element : tgt )
632             {
633                 Object key = getReportSetKey( element );
634                 ReportSet existing = merged.get( key );
635                 if ( existing != null )
636                 {
637                     mergeReportSet( element, existing, sourceDominant, context );
638                 }
639                 merged.put( key, element );
640             }
641 
642             target.setReportSets( new ArrayList<>( merged.values() ) );
643         }
644     }
645 
646     @Override
647     protected Object getDependencyKey( Dependency dependency )
648     {
649         return dependency.getManagementKey();
650     }
651 
652     @Override
653     protected Object getPluginKey( Plugin plugin )
654     {
655         return plugin.getKey();
656     }
657 
658     @Override
659     protected Object getPluginExecutionKey( PluginExecution pluginExecution )
660     {
661         return pluginExecution.getId();
662     }
663 
664     @Override
665     protected Object getReportPluginKey( ReportPlugin reportPlugin )
666     {
667         return reportPlugin.getKey();
668     }
669 
670     @Override
671     protected Object getReportSetKey( ReportSet reportSet )
672     {
673         return reportSet.getId();
674     }
675 
676     @Override
677     protected Object getRepositoryBaseKey( RepositoryBase repositoryBase )
678     {
679         return repositoryBase.getId();
680     }
681 
682     @Override
683     protected Object getExtensionKey( Extension extension )
684     {
685         return extension.getGroupId() + ':' + extension.getArtifactId();
686     }
687 
688     @Override
689     protected Object getExclusionKey( Exclusion exclusion )
690     {
691         return exclusion.getGroupId() + ':' + exclusion.getArtifactId();
692     }
693 
694     protected String extrapolateChildUrl( String parentUrl, boolean appendPath, Map<Object, Object> context )
695     {
696         return parentUrl;
697     }
698 
699 }