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