001 package org.apache.maven.model.merge;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import java.util.ArrayList;
023 import java.util.LinkedHashMap;
024 import java.util.LinkedHashSet;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.Set;
028
029 import org.apache.maven.model.BuildBase;
030 import org.apache.maven.model.CiManagement;
031 import org.apache.maven.model.Contributor;
032 import org.apache.maven.model.Dependency;
033 import org.apache.maven.model.DeploymentRepository;
034 import org.apache.maven.model.Developer;
035 import org.apache.maven.model.DistributionManagement;
036 import org.apache.maven.model.Exclusion;
037 import org.apache.maven.model.Extension;
038 import org.apache.maven.model.InputLocation;
039 import org.apache.maven.model.IssueManagement;
040 import org.apache.maven.model.License;
041 import org.apache.maven.model.MailingList;
042 import org.apache.maven.model.Model;
043 import org.apache.maven.model.ModelBase;
044 import org.apache.maven.model.Organization;
045 import org.apache.maven.model.Plugin;
046 import org.apache.maven.model.PluginExecution;
047 import org.apache.maven.model.ReportPlugin;
048 import org.apache.maven.model.ReportSet;
049 import org.apache.maven.model.Repository;
050 import org.apache.maven.model.RepositoryBase;
051 import org.apache.maven.model.Scm;
052 import org.apache.maven.model.Site;
053
054 /**
055 * The domain-specific model merger for the Maven POM, overriding generic code from parent class when necessary with
056 * more adapted algorithms.
057 *
058 * @author Benjamin Bentmann
059 */
060 public class MavenModelMerger
061 extends ModelMerger
062 {
063
064 /**
065 * The hint key for the child path adjustment used during inheritance for URL calculations.
066 */
067 public static final String CHILD_PATH_ADJUSTMENT = "child-path-adjustment";
068
069 /**
070 * The context key for the artifact id of the target model.
071 */
072 private static final String ARTIFACT_ID = "artifact-id";
073
074 @Override
075 protected void mergeModel( Model target, Model source, boolean sourceDominant, Map<Object, Object> context )
076 {
077 context.put( ARTIFACT_ID, target.getArtifactId() );
078
079 super.mergeModel( target, source, sourceDominant, context );
080 }
081
082 @Override
083 protected void mergeModel_Name( Model target, Model source, boolean sourceDominant, Map<Object, Object> context )
084 {
085 String src = source.getName();
086 if ( src != null )
087 {
088 if ( sourceDominant )
089 {
090 target.setName( src );
091 target.setLocation( "name", source.getLocation( "name" ) );
092 }
093 }
094 }
095
096 @Override
097 protected void mergeModel_Url( Model target, Model source, boolean sourceDominant, Map<Object, Object> context )
098 {
099 String src = source.getUrl();
100 if ( src != null )
101 {
102 if ( sourceDominant )
103 {
104 target.setUrl( src );
105 target.setLocation( "url", source.getLocation( "url" ) );
106 }
107 else if ( target.getUrl() == null )
108 {
109 target.setUrl( appendPath( src, context ) );
110 target.setLocation( "url", source.getLocation( "url" ) );
111 }
112 }
113 }
114
115 /*
116 * TODO: Whether the merge continues recursively into an existing node or not could be an option for the generated
117 * merger
118 */
119 @Override
120 protected void mergeModel_Organization( Model target, Model source, boolean sourceDominant,
121 Map<Object, Object> context )
122 {
123 Organization src = source.getOrganization();
124 if ( src != null )
125 {
126 Organization tgt = target.getOrganization();
127 if ( tgt == null )
128 {
129 tgt = new Organization();
130 tgt.setLocation( "", src.getLocation( "" ) );
131 target.setOrganization( tgt );
132 mergeOrganization( tgt, src, sourceDominant, context );
133 }
134 }
135 }
136
137 @Override
138 protected void mergeModel_IssueManagement( Model target, Model source, boolean sourceDominant,
139 Map<Object, Object> context )
140 {
141 IssueManagement src = source.getIssueManagement();
142 if ( src != null )
143 {
144 IssueManagement tgt = target.getIssueManagement();
145 if ( tgt == null )
146 {
147 tgt = new IssueManagement();
148 tgt.setLocation( "", src.getLocation( "" ) );
149 target.setIssueManagement( tgt );
150 mergeIssueManagement( tgt, src, sourceDominant, context );
151 }
152 }
153 }
154
155 @Override
156 protected void mergeModel_CiManagement( Model target, Model source, boolean sourceDominant,
157 Map<Object, Object> context )
158 {
159 CiManagement src = source.getCiManagement();
160 if ( src != null )
161 {
162 CiManagement tgt = target.getCiManagement();
163 if ( tgt == null )
164 {
165 tgt = new CiManagement();
166 tgt.setLocation( "", src.getLocation( "" ) );
167 target.setCiManagement( tgt );
168 mergeCiManagement( tgt, src, sourceDominant, context );
169 }
170 }
171 }
172
173 @Override
174 protected void mergeModel_ModelVersion( Model target, Model source, boolean sourceDominant,
175 Map<Object, Object> context )
176 {
177 // neither inherited nor injected
178 }
179
180 @Override
181 protected void mergeModel_ArtifactId( Model target, Model source, boolean sourceDominant,
182 Map<Object, Object> context )
183 {
184 // neither inherited nor injected
185 }
186
187 @Override
188 protected void mergeModel_Profiles( Model target, Model source, boolean sourceDominant,
189 Map<Object, Object> context )
190 {
191 // neither inherited nor injected
192 }
193
194 @Override
195 protected void mergeModel_Prerequisites( Model target, Model source, boolean sourceDominant,
196 Map<Object, Object> context )
197 {
198 // neither inherited nor injected
199 }
200
201 @Override
202 protected void mergeModel_Licenses( Model target, Model source, boolean sourceDominant,
203 Map<Object, Object> context )
204 {
205 if ( target.getLicenses().isEmpty() )
206 {
207 target.setLicenses( new ArrayList<License>( source.getLicenses() ) );
208 }
209 }
210
211 @Override
212 protected void mergeModel_Developers( Model target, Model source, boolean sourceDominant,
213 Map<Object, Object> context )
214 {
215 if ( target.getDevelopers().isEmpty() )
216 {
217 target.setDevelopers( new ArrayList<Developer>( source.getDevelopers() ) );
218 }
219 }
220
221 @Override
222 protected void mergeModel_Contributors( Model target, Model source, boolean sourceDominant,
223 Map<Object, Object> context )
224 {
225 if ( target.getContributors().isEmpty() )
226 {
227 target.setContributors( new ArrayList<Contributor>( source.getContributors() ) );
228 }
229 }
230
231 @Override
232 protected void mergeModel_MailingLists( Model target, Model source, boolean sourceDominant,
233 Map<Object, Object> context )
234 {
235 if ( target.getMailingLists().isEmpty() )
236 {
237 target.setMailingLists( new ArrayList<MailingList>( source.getMailingLists() ) );
238 }
239 }
240
241 @Override
242 protected void mergeModelBase_Modules( ModelBase target, ModelBase source, boolean sourceDominant,
243 Map<Object, Object> context )
244 {
245 List<String> src = source.getModules();
246 if ( !src.isEmpty() && sourceDominant )
247 {
248 List<Integer> indices = new ArrayList<Integer>();
249 List<String> tgt = target.getModules();
250 Set<String> excludes = new LinkedHashSet<String>( tgt );
251 List<String> merged = new ArrayList<String>( tgt.size() + src.size() );
252 merged.addAll( tgt );
253 for ( int i = 0, n = tgt.size(); i < n; i++ )
254 {
255 indices.add( i );
256 }
257 for ( int i = 0, n = src.size(); i < n; i++ )
258 {
259 String s = src.get( i );
260 if ( !excludes.contains( s ) )
261 {
262 merged.add( s );
263 indices.add( ~i );
264 }
265 }
266 target.setModules( merged );
267 target.setLocation( "modules", InputLocation.merge( target.getLocation( "modules" ),
268 source.getLocation( "modules" ), indices ) );
269 }
270 }
271
272 /*
273 * TODO: The order of the merged list could be controlled by an attribute in the model association: target-first,
274 * source-first, dominant-first, recessive-first
275 */
276 @Override
277 protected void mergeModelBase_Repositories( ModelBase target, ModelBase source, boolean sourceDominant,
278 Map<Object, Object> context )
279 {
280 List<Repository> src = source.getRepositories();
281 if ( !src.isEmpty() )
282 {
283 List<Repository> tgt = target.getRepositories();
284 Map<Object, Repository> merged = new LinkedHashMap<Object, Repository>( ( src.size() + tgt.size() ) * 2 );
285
286 List<Repository> dominant, recessive;
287 if ( sourceDominant )
288 {
289 dominant = src;
290 recessive = tgt;
291 }
292 else
293 {
294 dominant = tgt;
295 recessive = src;
296 }
297
298 for ( Repository element : dominant )
299 {
300 Object key = getRepositoryKey( element );
301 merged.put( key, element );
302 }
303
304 for ( Repository element : recessive )
305 {
306 Object key = getRepositoryKey( element );
307 if ( !merged.containsKey( key ) )
308 {
309 merged.put( key, element );
310 }
311 }
312
313 target.setRepositories( new ArrayList<Repository>( merged.values() ) );
314 }
315 }
316
317 protected void mergeModelBase_PluginRepositories( ModelBase target, ModelBase source, boolean sourceDominant,
318 Map<Object, Object> context )
319 {
320 List<Repository> src = source.getPluginRepositories();
321 if ( !src.isEmpty() )
322 {
323 List<Repository> tgt = target.getPluginRepositories();
324 Map<Object, Repository> merged = new LinkedHashMap<Object, Repository>( ( src.size() + tgt.size() ) * 2 );
325
326 List<Repository> dominant, recessive;
327 if ( sourceDominant )
328 {
329 dominant = src;
330 recessive = tgt;
331 }
332 else
333 {
334 dominant = tgt;
335 recessive = src;
336 }
337
338 for ( Repository element : dominant )
339 {
340 Object key = getRepositoryKey( element );
341 merged.put( key, element );
342 }
343
344 for ( Repository element : recessive )
345 {
346 Object key = getRepositoryKey( element );
347 if ( !merged.containsKey( key ) )
348 {
349 merged.put( key, element );
350 }
351 }
352
353 target.setPluginRepositories( new ArrayList<Repository>( merged.values() ) );
354 }
355 }
356
357 /*
358 * TODO: Whether duplicates should be removed looks like an option for the generated merger.
359 */
360 @Override
361 protected void mergeBuildBase_Filters( BuildBase target, BuildBase source, boolean sourceDominant,
362 Map<Object, Object> context )
363 {
364 List<String> src = source.getFilters();
365 if ( !src.isEmpty() )
366 {
367 List<String> tgt = target.getFilters();
368 Set<String> excludes = new LinkedHashSet<String>( tgt );
369 List<String> merged = new ArrayList<String>( tgt.size() + src.size() );
370 merged.addAll( tgt );
371 for ( String s : src )
372 {
373 if ( !excludes.contains( s ) )
374 {
375 merged.add( s );
376 }
377 }
378 target.setFilters( merged );
379 }
380 }
381
382 @Override
383 protected void mergeBuildBase_Resources( BuildBase target, BuildBase source, boolean sourceDominant,
384 Map<Object, Object> context )
385 {
386 if ( sourceDominant || target.getResources().isEmpty() )
387 {
388 super.mergeBuildBase_Resources( target, source, sourceDominant, context );
389 }
390 }
391
392 @Override
393 protected void mergeBuildBase_TestResources( BuildBase target, BuildBase source, boolean sourceDominant,
394 Map<Object, Object> context )
395 {
396 if ( sourceDominant || target.getTestResources().isEmpty() )
397 {
398 super.mergeBuildBase_TestResources( target, source, sourceDominant, context );
399 }
400 }
401
402 @Override
403 protected void mergeDistributionManagement_Repository( DistributionManagement target,
404 DistributionManagement source, boolean sourceDominant,
405 Map<Object, Object> context )
406 {
407 DeploymentRepository src = source.getRepository();
408 if ( src != null )
409 {
410 DeploymentRepository tgt = target.getRepository();
411 if ( sourceDominant || tgt == null )
412 {
413 tgt = new DeploymentRepository();
414 tgt.setLocation( "", src.getLocation( "" ) );
415 target.setRepository( tgt );
416 mergeDeploymentRepository( tgt, src, sourceDominant, context );
417 }
418 }
419 }
420
421 @Override
422 protected void mergeDistributionManagement_SnapshotRepository( DistributionManagement target,
423 DistributionManagement source,
424 boolean sourceDominant,
425 Map<Object, Object> context )
426 {
427 DeploymentRepository src = source.getSnapshotRepository();
428 if ( src != null )
429 {
430 DeploymentRepository tgt = target.getSnapshotRepository();
431 if ( sourceDominant || tgt == null )
432 {
433 tgt = new DeploymentRepository();
434 tgt.setLocation( "", src.getLocation( "" ) );
435 target.setSnapshotRepository( tgt );
436 mergeDeploymentRepository( tgt, src, sourceDominant, context );
437 }
438 }
439 }
440
441 @Override
442 protected void mergeDistributionManagement_Site( DistributionManagement target, DistributionManagement source,
443 boolean sourceDominant, Map<Object, Object> context )
444 {
445 Site src = source.getSite();
446 if ( src != null )
447 {
448 Site tgt = target.getSite();
449 if ( sourceDominant || tgt == null )
450 {
451 tgt = new Site();
452 tgt.setLocation( "", src.getLocation( "" ) );
453 target.setSite( tgt );
454 mergeSite( tgt, src, sourceDominant, context );
455 }
456 }
457 }
458
459 @Override
460 protected void mergeSite_Url( Site target, Site source, boolean sourceDominant, Map<Object, Object> context )
461 {
462 String src = source.getUrl();
463 if ( src != null )
464 {
465 if ( sourceDominant )
466 {
467 target.setUrl( src );
468 target.setLocation( "url", source.getLocation( "url" ) );
469 }
470 else if ( target.getUrl() == null )
471 {
472 target.setUrl( appendPath( src, context ) );
473 target.setLocation( "url", source.getLocation( "url" ) );
474 }
475 }
476 }
477
478 @Override
479 protected void mergeScm_Url( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
480 {
481 String src = source.getUrl();
482 if ( src != null )
483 {
484 if ( sourceDominant )
485 {
486 target.setUrl( src );
487 target.setLocation( "url", source.getLocation( "url" ) );
488 }
489 else if ( target.getUrl() == null )
490 {
491 target.setUrl( appendPath( src, context ) );
492 target.setLocation( "url", source.getLocation( "url" ) );
493 }
494 }
495 }
496
497 @Override
498 protected void mergeScm_Connection( Scm target, Scm source, boolean sourceDominant, Map<Object, Object> context )
499 {
500 String src = source.getConnection();
501 if ( src != null )
502 {
503 if ( sourceDominant )
504 {
505 target.setConnection( src );
506 target.setLocation( "connection", source.getLocation( "connection" ) );
507 }
508 else if ( target.getConnection() == null )
509 {
510 target.setConnection( appendPath( src, context ) );
511 target.setLocation( "connection", source.getLocation( "connection" ) );
512 }
513 }
514 }
515
516 @Override
517 protected void mergeScm_DeveloperConnection( Scm target, Scm source, boolean sourceDominant,
518 Map<Object, Object> context )
519 {
520 String src = source.getDeveloperConnection();
521 if ( src != null )
522 {
523 if ( sourceDominant )
524 {
525 target.setDeveloperConnection( src );
526 target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
527 }
528 else if ( target.getDeveloperConnection() == null )
529 {
530 target.setDeveloperConnection( appendPath( src, context ) );
531 target.setLocation( "developerConnection", source.getLocation( "developerConnection" ) );
532 }
533 }
534 }
535
536 @Override
537 protected void mergePlugin_Executions( Plugin target, Plugin source, boolean sourceDominant,
538 Map<Object, Object> context )
539 {
540 List<PluginExecution> src = source.getExecutions();
541 if ( !src.isEmpty() )
542 {
543 List<PluginExecution> tgt = target.getExecutions();
544 Map<Object, PluginExecution> merged =
545 new LinkedHashMap<Object, PluginExecution>( ( src.size() + tgt.size() ) * 2 );
546
547 for ( PluginExecution element : src )
548 {
549 if ( sourceDominant
550 || ( element.getInherited() != null ? element.isInherited() : source.isInherited() ) )
551 {
552 Object key = getPluginExecutionKey( element );
553 merged.put( key, element );
554 }
555 }
556
557 for ( PluginExecution element : tgt )
558 {
559 Object key = getPluginExecutionKey( element );
560 PluginExecution existing = merged.get( key );
561 if ( existing != null )
562 {
563 mergePluginExecution( element, existing, sourceDominant, context );
564 }
565 merged.put( key, element );
566 }
567
568 target.setExecutions( new ArrayList<PluginExecution>( merged.values() ) );
569 }
570 }
571
572 @Override
573 protected void mergePluginExecution_Goals( PluginExecution target, PluginExecution source, boolean sourceDominant,
574 Map<Object, Object> context )
575 {
576 List<String> src = source.getGoals();
577 if ( !src.isEmpty() )
578 {
579 List<String> tgt = target.getGoals();
580 Set<String> excludes = new LinkedHashSet<String>( tgt );
581 List<String> merged = new ArrayList<String>( tgt.size() + src.size() );
582 merged.addAll( tgt );
583 for ( String s : src )
584 {
585 if ( !excludes.contains( s ) )
586 {
587 merged.add( s );
588 }
589 }
590 target.setGoals( merged );
591 }
592 }
593
594 @Override
595 protected void mergeReportPlugin_ReportSets( ReportPlugin target, ReportPlugin source, boolean sourceDominant,
596 Map<Object, Object> context )
597 {
598 List<ReportSet> src = source.getReportSets();
599 if ( !src.isEmpty() )
600 {
601 List<ReportSet> tgt = target.getReportSets();
602 Map<Object, ReportSet> merged = new LinkedHashMap<Object, ReportSet>( ( src.size() + tgt.size() ) * 2 );
603
604 for ( ReportSet element : src )
605 {
606 if ( sourceDominant || ( element.getInherited() != null ? element.isInherited() : source.isInherited() ) )
607 {
608 Object key = getReportSetKey( element );
609 merged.put( key, element );
610 }
611 }
612
613 for ( ReportSet element : tgt )
614 {
615 Object key = getReportSetKey( element );
616 ReportSet existing = merged.get( key );
617 if ( existing != null )
618 {
619 mergeReportSet( element, existing, sourceDominant, context );
620 }
621 merged.put( key, element );
622 }
623
624 target.setReportSets( new ArrayList<ReportSet>( merged.values() ) );
625 }
626 }
627
628 @Override
629 protected Object getDependencyKey( Dependency dependency )
630 {
631 return dependency.getManagementKey();
632 }
633
634 @Override
635 protected Object getPluginKey( Plugin plugin )
636 {
637 return plugin.getKey();
638 }
639
640 @Override
641 protected Object getPluginExecutionKey( PluginExecution pluginExecution )
642 {
643 return pluginExecution.getId();
644 }
645
646 @Override
647 protected Object getReportPluginKey( ReportPlugin reportPlugin )
648 {
649 return reportPlugin.getKey();
650 }
651
652 @Override
653 protected Object getReportSetKey( ReportSet reportSet )
654 {
655 return reportSet.getId();
656 }
657
658 @Override
659 protected Object getRepositoryBaseKey( RepositoryBase repositoryBase )
660 {
661 return repositoryBase.getId();
662 }
663
664 @Override
665 protected Object getExtensionKey( Extension extension )
666 {
667 return extension.getGroupId() + ':' + extension.getArtifactId();
668 }
669
670 @Override
671 protected Object getExclusionKey( Exclusion exclusion )
672 {
673 return exclusion.getGroupId() + ':' + exclusion.getArtifactId();
674 }
675
676 private String appendPath( String parentPath, Map<Object, Object> context )
677 {
678 Object artifactId = context.get( ARTIFACT_ID );
679 Object childPathAdjustment = context.get( CHILD_PATH_ADJUSTMENT );
680
681 if ( artifactId != null && childPathAdjustment != null )
682 {
683 return appendPath( parentPath, artifactId.toString(), childPathAdjustment.toString() );
684 }
685 else
686 {
687 return parentPath;
688 }
689 }
690
691 private String appendPath( String parentPath, String childPath, String pathAdjustment )
692 {
693 String path = parentPath;
694 path = concatPath( path, pathAdjustment );
695 path = concatPath( path, childPath );
696 return path;
697 }
698
699 private String concatPath( String base, String path )
700 {
701 String result = base;
702
703 if ( path != null && path.length() > 0 )
704 {
705 if ( ( result.endsWith( "/" ) && !path.startsWith( "/" ) )
706 || ( !result.endsWith( "/" ) && path.startsWith( "/" ) ) )
707 {
708 result += path;
709 }
710 else if ( result.endsWith( "/" ) && path.startsWith( "/" ) )
711 {
712 result += path.substring( 1 );
713 }
714 else
715 {
716 result += '/';
717 result += path;
718 }
719 if ( base.endsWith( "/" ) && !result.endsWith( "/" ) )
720 {
721 result += '/';
722 }
723 }
724
725 return result;
726 }
727
728 }