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