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