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