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    }