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     /*
317      * TODO: Whether duplicates should be removed looks like an option for the generated merger.
318      */
319     @Override
320     protected void mergeBuildBase_Filters( BuildBase target, BuildBase source, boolean sourceDominant,
321                                            Map<Object, Object> context )
322     {
323         List<String> src = source.getFilters();
324         if ( !src.isEmpty() )
325         {
326             List<String> tgt = target.getFilters();
327             Set<String> excludes = new LinkedHashSet<String>( tgt );
328             List<String> merged = new ArrayList<String>( tgt.size() + src.size() );
329             merged.addAll( tgt );
330             for ( String s : src )
331             {
332                 if ( !excludes.contains( s ) )
333                 {
334                     merged.add( s );
335                 }
336             }
337             target.setFilters( merged );
338         }
339     }
340 
341     @Override
342     protected void mergeBuildBase_Resources( BuildBase target, BuildBase source, boolean sourceDominant,
343                                              Map<Object, Object> context )
344     {
345         if ( sourceDominant || target.getResources().isEmpty() )
346         {
347             super.mergeBuildBase_Resources( target, source, sourceDominant, context );
348         }
349     }
350 
351     @Override
352     protected void mergeBuildBase_TestResources( BuildBase target, BuildBase source, boolean sourceDominant,
353                                                  Map<Object, Object> context )
354     {
355         if ( sourceDominant || target.getTestResources().isEmpty() )
356         {
357             super.mergeBuildBase_TestResources( target, source, sourceDominant, context );
358         }
359     }
360 
361     @Override
362     protected void mergeDistributionManagement_Repository( DistributionManagement target,
363                                                            DistributionManagement source, boolean sourceDominant,
364                                                            Map<Object, Object> context )
365     {
366         DeploymentRepository src = source.getRepository();
367         if ( src != null )
368         {
369             DeploymentRepository tgt = target.getRepository();
370             if ( sourceDominant || tgt == null )
371             {
372                 tgt = new DeploymentRepository();
373                 tgt.setLocation( "", src.getLocation( "" ) );
374                 target.setRepository( tgt );
375                 mergeDeploymentRepository( tgt, src, sourceDominant, context );
376             }
377         }
378     }
379 
380     @Override
381     protected void mergeDistributionManagement_SnapshotRepository( DistributionManagement target,
382                                                                    DistributionManagement source,
383                                                                    boolean sourceDominant,
384                                                                    Map<Object, Object> context )
385     {
386         DeploymentRepository src = source.getSnapshotRepository();
387         if ( src != null )
388         {
389             DeploymentRepository tgt = target.getSnapshotRepository();
390             if ( sourceDominant || tgt == null )
391             {
392                 tgt = new DeploymentRepository();
393                 tgt.setLocation( "", src.getLocation( "" ) );
394                 target.setSnapshotRepository( tgt );
395                 mergeDeploymentRepository( tgt, src, sourceDominant, context );
396             }
397         }
398     }
399 
400     @Override
401     protected void mergeDistributionManagement_Site( DistributionManagement target, DistributionManagement source,
402                                                      boolean sourceDominant, Map<Object, Object> context )
403     {
404         Site src = source.getSite();
405         if ( src != null )
406         {
407             Site tgt = target.getSite();
408             if ( sourceDominant || tgt == null )
409             {
410                 tgt = new Site();
411                 tgt.setLocation( "", src.getLocation( "" ) );
412                 target.setSite( tgt );
413                 mergeSite( tgt, src, sourceDominant, context );
414             }
415         }
416     }
417 
418     @Override
419     protected void mergeSite_Url( Site target, Site source, boolean sourceDominant, Map<Object, Object> context )
420     {
421         String src = source.getUrl();
422         if ( src != null )
423         {
424             if ( sourceDominant )
425             {
426                 target.setUrl( src );
427                 target.setLocation( "url", source.getLocation( "url" ) );
428             }
429             else if ( target.getUrl() == null )
430             {
431                 target.setUrl( appendPath( src, context ) );
432                 target.setLocation( "url", source.getLocation( "url" ) );
433             }
434         }
435     }
436 
437     @Override
438     protected void mergeScm_Url( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
439     {
440         String src = source.getUrl();
441         if ( src != null )
442         {
443             if ( sourceDominant )
444             {
445                 target.setUrl( src );
446                 target.setLocation( "url", source.getLocation( "url" ) );
447             }
448             else if ( target.getUrl() == null )
449             {
450                 target.setUrl( appendPath( src, context ) );
451                 target.setLocation( "url", source.getLocation( "url" ) );
452             }
453         }
454     }
455 
456     @Override
457     protected void mergeScm_Connection( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
458     {
459         String src = source.getConnection();
460         if ( src != null )
461         {
462             if ( sourceDominant )
463             {
464                 target.setConnection( src );
465                 target.setLocation( "connection", source.getLocation( "connection" ) );
466             }
467             else if ( target.getConnection() == null )
468             {
469                 target.setConnection( appendPath( src, context ) );
470                 target.setLocation( "connection", source.getLocation( "connection" ) );
471             }
472         }
473     }
474 
475     @Override
476     protected void mergeScm_DeveloperConnection( Scm target, Scm source, boolean sourceDominant,
477                                                  Map<Object, Object> context )
478     {
479         String src = source.getDeveloperConnection();
480         if ( src != null )
481         {
482             if ( sourceDominant )
483             {
484                 target.setDeveloperConnection( src );
485                 target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
486             }
487             else if ( target.getDeveloperConnection() == null )
488             {
489                 target.setDeveloperConnection( appendPath( src, context ) );
490                 target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
491             }
492         }
493     }
494 
495     @Override
496     protected void mergePlugin_Executions( Plugin target, Plugin source, boolean sourceDominant,
497                                            Map<Object, Object> context )
498     {
499         List<PluginExecution> src = source.getExecutions();
500         if ( !src.isEmpty() )
501         {
502             List<PluginExecution> tgt = target.getExecutions();
503             Map<Object, PluginExecution> merged =
504                 new LinkedHashMap<Object, PluginExecution>( ( src.size() + tgt.size() ) * 2 );
505 
506             for ( PluginExecution element : src )
507             {
508                 if ( sourceDominant
509                                 || ( element.getInherited() != null ? element.isInherited() : source.isInherited() ) )
510                 {
511                     Object key = getPluginExecutionKey( element );
512                     merged.put( key, element );
513                 }
514             }
515 
516             for ( PluginExecution element : tgt )
517             {
518                 Object key = getPluginExecutionKey( element );
519                 PluginExecution existing = merged.get( key );
520                 if ( existing != null )
521                 {
522                     mergePluginExecution( element, existing, sourceDominant, context );
523                 }
524                 merged.put( key, element );
525             }
526 
527             target.setExecutions( new ArrayList<PluginExecution>( merged.values() ) );
528         }
529     }
530 
531     @Override
532     protected void mergePluginExecution_Goals( PluginExecution target, PluginExecution source, boolean sourceDominant,
533                                                Map<Object, Object> context )
534     {
535         List<String> src = source.getGoals();
536         if ( !src.isEmpty() )
537         {
538             List<String> tgt = target.getGoals();
539             Set<String> excludes = new LinkedHashSet<String>( tgt );
540             List<String> merged = new ArrayList<String>( tgt.size() + src.size() );
541             merged.addAll( tgt );
542             for ( String s : src )
543             {
544                 if ( !excludes.contains( s ) )
545                 {
546                     merged.add( s );
547                 }
548             }
549             target.setGoals( merged );
550         }
551     }
552 
553     @Override
554     protected void mergeReportPlugin_ReportSets( ReportPlugin target, ReportPlugin source, boolean sourceDominant,
555                                                  Map<Object, Object> context )
556     {
557         List<ReportSet> src = source.getReportSets();
558         if ( !src.isEmpty() )
559         {
560             List<ReportSet> tgt = target.getReportSets();
561             Map<Object, ReportSet> merged = new LinkedHashMap<Object, ReportSet>( ( src.size() + tgt.size() ) * 2 );
562 
563             for ( ReportSet element : src )
564             {
565                 if ( sourceDominant || ( element.getInherited() != null ? element.isInherited() : source.isInherited() ) )
566                 {
567                     Object key = getReportSetKey( element );
568                     merged.put( key, element );
569                 }
570             }
571 
572             for ( ReportSet element : tgt )
573             {
574                 Object key = getReportSetKey( element );
575                 ReportSet existing = merged.get( key );
576                 if ( existing != null )
577                 {
578                     mergeReportSet( element, existing, sourceDominant, context );
579                 }
580                 merged.put( key, element );
581             }
582 
583             target.setReportSets( new ArrayList<ReportSet>( merged.values() ) );
584         }
585     }
586 
587     @Override
588     protected Object getDependencyKey( Dependency dependency )
589     {
590         return dependency.getManagementKey();
591     }
592 
593     @Override
594     protected Object getPluginKey( Plugin object )
595     {
596         return object.getKey();
597     }
598 
599     @Override
600     protected Object getPluginExecutionKey( PluginExecution object )
601     {
602         return object.getId();
603     }
604 
605     @Override
606     protected Object getReportPluginKey( ReportPlugin object )
607     {
608         return object.getKey();
609     }
610 
611     @Override
612     protected Object getReportSetKey( ReportSet object )
613     {
614         return object.getId();
615     }
616 
617     @Override
618     protected Object getRepositoryBaseKey( RepositoryBase object )
619     {
620         return object.getId();
621     }
622 
623     @Override
624     protected Object getExtensionKey( Extension object )
625     {
626         return object.getGroupId() + ':' + object.getArtifactId();
627     }
628 
629     @Override
630     protected Object getExclusionKey( Exclusion object )
631     {
632         return object.getGroupId() + ':' + object.getArtifactId();
633     }
634 
635     private String appendPath( String parentPath, Map<Object, Object> context )
636     {
637         Object artifactId = context.get( ARTIFACT_ID );
638         Object childPathAdjustment = context.get( CHILD_PATH_ADJUSTMENT );
639 
640         if ( artifactId != null && childPathAdjustment != null )
641         {
642             return appendPath( parentPath, artifactId.toString(), childPathAdjustment.toString() );
643         }
644         else
645         {
646             return parentPath;
647         }
648     }
649 
650     private String appendPath( String parentPath, String childPath, String pathAdjustment )
651     {
652         String path = parentPath;
653         path = concatPath( path, pathAdjustment );
654         path = concatPath( path, childPath );
655         return path;
656     }
657 
658     private String concatPath( String base, String path )
659     {
660         String result = base;
661 
662         if ( path != null && path.length() > 0 )
663         {
664             if ( ( result.endsWith( "/" ) && !path.startsWith( "/" ) )
665                 || ( !result.endsWith( "/" ) && path.startsWith( "/" ) ) )
666             {
667                 result += path;
668             }
669             else if ( result.endsWith( "/" ) && path.startsWith( "/" ) )
670             {
671                 result += path.substring( 1 );
672             }
673             else
674             {
675                 result += '/';
676                 result += path;
677             }
678             if ( base.endsWith( "/" ) && !result.endsWith( "/" ) )
679             {
680                 result += '/';
681             }
682         }
683 
684         return result;
685     }
686 
687 }