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  @SuppressWarnings( { "checkstyle:methodname" } )
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, 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 )
448             {
449                 tgt = new Site();
450                 tgt.setLocation( "", src.getLocation( "" ) );
451                 target.setSite( tgt );
452                 mergeSite( tgt, src, sourceDominant, context );
453             }
454         }
455     }
456 
457     @Override
458     protected void mergeSite_Url( Site target, Site source, boolean sourceDominant, Map<Object, Object> context )
459     {
460         String src = source.getUrl();
461         if ( src != null )
462         {
463             if ( sourceDominant )
464             {
465                 target.setUrl( src );
466                 target.setLocation( "url", source.getLocation( "url" ) );
467             }
468             else if ( target.getUrl() == null )
469             {
470                 target.setUrl( extrapolateChildUrl( src, context ) );
471                 target.setLocation( "url", source.getLocation( "url" ) );
472             }
473         }
474     }
475 
476     @Override
477     protected void mergeScm_Url( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
478     {
479         String src = source.getUrl();
480         if ( src != null )
481         {
482             if ( sourceDominant )
483             {
484                 target.setUrl( src );
485                 target.setLocation( "url", source.getLocation( "url" ) );
486             }
487             else if ( target.getUrl() == null )
488             {
489                 target.setUrl( extrapolateChildUrl( src, context ) );
490                 target.setLocation( "url", source.getLocation( "url" ) );
491             }
492         }
493     }
494 
495     @Override
496     protected void mergeScm_Connection( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
497     {
498         String src = source.getConnection();
499         if ( src != null )
500         {
501             if ( sourceDominant )
502             {
503                 target.setConnection( src );
504                 target.setLocation( "connection", source.getLocation( "connection" ) );
505             }
506             else if ( target.getConnection() == null )
507             {
508                 target.setConnection( extrapolateChildUrl( src, context ) );
509                 target.setLocation( "connection", source.getLocation( "connection" ) );
510             }
511         }
512     }
513 
514     @Override
515     protected void mergeScm_DeveloperConnection( Scm target, Scm source, boolean sourceDominant,
516                                                  Map<Object, Object> context )
517     {
518         String src = source.getDeveloperConnection();
519         if ( src != null )
520         {
521             if ( sourceDominant )
522             {
523                 target.setDeveloperConnection( src );
524                 target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
525             }
526             else if ( target.getDeveloperConnection() == null )
527             {
528                 target.setDeveloperConnection( extrapolateChildUrl( src, context ) );
529                 target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
530             }
531         }
532     }
533 
534     @Override
535     protected void mergePlugin_Executions( Plugin target, Plugin source, boolean sourceDominant,
536                                            Map<Object, Object> context )
537     {
538         List<PluginExecution> src = source.getExecutions();
539         if ( !src.isEmpty() )
540         {
541             List<PluginExecution> tgt = target.getExecutions();
542             Map<Object, PluginExecution> merged =
543                 new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
544 
545             for ( PluginExecution element : src )
546             {
547                 if ( sourceDominant
548                                 || ( element.getInherited() != null ? element.isInherited() : source.isInherited() ) )
549                 {
550                     Object key = getPluginExecutionKey( element );
551                     merged.put( key, element );
552                 }
553             }
554 
555             for ( PluginExecution element : tgt )
556             {
557                 Object key = getPluginExecutionKey( element );
558                 PluginExecution existing = merged.get( key );
559                 if ( existing != null )
560                 {
561                     mergePluginExecution( element, existing, sourceDominant, context );
562                 }
563                 merged.put( key, element );
564             }
565 
566             target.setExecutions( new ArrayList<>( merged.values() ) );
567         }
568     }
569 
570     @Override
571     protected void mergePluginExecution_Goals( PluginExecution target, PluginExecution source, boolean sourceDominant,
572                                                Map<Object, Object> context )
573     {
574         List<String> src = source.getGoals();
575         if ( !src.isEmpty() )
576         {
577             List<String> tgt = target.getGoals();
578             Set<String> excludes = new LinkedHashSet<>( tgt );
579             List<String> merged = new ArrayList<>( tgt.size() + src.size() );
580             merged.addAll( tgt );
581             for ( String s : src )
582             {
583                 if ( !excludes.contains( s ) )
584                 {
585                     merged.add( s );
586                 }
587             }
588             target.setGoals( merged );
589         }
590     }
591 
592     @Override
593     protected void mergeReportPlugin_ReportSets( ReportPlugin target, ReportPlugin source, boolean sourceDominant,
594                                                  Map<Object, Object> context )
595     {
596         List<ReportSet> src = source.getReportSets();
597         if ( !src.isEmpty() )
598         {
599             List<ReportSet> tgt = target.getReportSets();
600             Map<Object, ReportSet> merged = new LinkedHashMap<>( ( src.size() + tgt.size() ) * 2 );
601 
602             for ( ReportSet rset : src )
603             {
604                 if ( sourceDominant || ( rset.getInherited() != null ? rset.isInherited() : source.isInherited() ) )
605                 {
606                     Object key = getReportSetKey( rset );
607                     merged.put( key, rset );
608                 }
609             }
610 
611             for ( ReportSet element : tgt )
612             {
613                 Object key = getReportSetKey( element );
614                 ReportSet existing = merged.get( key );
615                 if ( existing != null )
616                 {
617                     mergeReportSet( element, existing, sourceDominant, context );
618                 }
619                 merged.put( key, element );
620             }
621 
622             target.setReportSets( new ArrayList<>( merged.values() ) );
623         }
624     }
625 
626     @Override
627     protected Object getDependencyKey( Dependency dependency )
628     {
629         return dependency.getManagementKey();
630     }
631 
632     @Override
633     protected Object getPluginKey( Plugin plugin )
634     {
635         return plugin.getKey();
636     }
637 
638     @Override
639     protected Object getPluginExecutionKey( PluginExecution pluginExecution )
640     {
641         return pluginExecution.getId();
642     }
643 
644     @Override
645     protected Object getReportPluginKey( ReportPlugin reportPlugin )
646     {
647         return reportPlugin.getKey();
648     }
649 
650     @Override
651     protected Object getReportSetKey( ReportSet reportSet )
652     {
653         return reportSet.getId();
654     }
655 
656     @Override
657     protected Object getRepositoryBaseKey( RepositoryBase repositoryBase )
658     {
659         return repositoryBase.getId();
660     }
661 
662     @Override
663     protected Object getExtensionKey( Extension extension )
664     {
665         return extension.getGroupId() + ':' + extension.getArtifactId();
666     }
667 
668     @Override
669     protected Object getExclusionKey( Exclusion exclusion )
670     {
671         return exclusion.getGroupId() + ':' + exclusion.getArtifactId();
672     }
673 
674     protected String extrapolateChildUrl( String parentUrl, Map<Object, Object> context )
675     {
676         return parentUrl;
677     }
678 
679 }