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