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