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