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