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 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}