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