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    }