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