001package org.apache.maven.plugins.enforcer;
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.io.FileNotFoundException;
023import java.io.FileReader;
024import java.io.IOException;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
032import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
033import org.apache.maven.model.Dependency;
034import org.apache.maven.model.Model;
035import org.apache.maven.model.Profile;
036import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
037import org.apache.maven.project.MavenProject;
038import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluationException;
039import org.codehaus.plexus.util.IOUtil;
040import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
041
042/**
043 * Since Maven 3 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique.
044 * Early versions of Maven 3 already warn, this rule can force to break a build for this reason. 
045 * 
046 * @author Robert Scholte
047 * @since 1.3
048 *
049 */
050public class BanDuplicatePomDependencyVersions
051    extends AbstractNonCacheableEnforcerRule
052{
053
054    public void execute( EnforcerRuleHelper helper )
055        throws EnforcerRuleException
056    {
057        // get the project
058        MavenProject project;
059        try
060        {
061            project = (MavenProject) helper.evaluate( "${project}" );
062        }
063        catch ( ExpressionEvaluationException eee )
064        {
065            throw new EnforcerRuleException( "Unable to retrieve the MavenProject: ", eee );
066        }
067        
068        
069        // re-read model, because M3 uses optimized model
070        MavenXpp3Reader modelReader = new MavenXpp3Reader();
071        FileReader pomReader = null;
072        Model model;
073        try
074        {
075            pomReader = new FileReader( project.getFile() );
076
077            model = modelReader.read( pomReader );
078        }
079        catch ( FileNotFoundException e )
080        {
081            throw new EnforcerRuleException( "Unable to retrieve the MavenProject: ", e );
082        }
083        catch ( IOException e )
084        {
085            throw new EnforcerRuleException( "Unable to retrieve the MavenProject: ", e );
086        }
087        catch ( XmlPullParserException e )
088        {
089            throw new EnforcerRuleException( "Unable to retrieve the MavenProject: ", e );
090        }
091        finally
092        {
093            IOUtil.close( pomReader );
094        }
095
096        // @todo reuse ModelValidator when possible
097        
098//        Object modelValidator = null;
099//        try
100//        {
101//            modelValidator = helper.getComponent( "org.apache.maven.model.validation.ModelValidator" );
102//        }
103//        catch ( ComponentLookupException e1 )
104//        {
105//            // noop
106//        }
107
108
109//        if( modelValidator == null )
110//        {
111            maven2Validation( helper, model );            
112//        }
113//        else
114//        {
115//        }
116    }
117
118    private void maven2Validation( EnforcerRuleHelper helper, Model model )
119        throws EnforcerRuleException
120    {
121        @SuppressWarnings( "unchecked" )
122        List<Dependency> dependencies = model.getDependencies();
123        Map<String, Integer> duplicateDependencies = validateDependencies( dependencies );
124
125        int duplicates = duplicateDependencies.size();
126        
127        StringBuilder summary = new StringBuilder();
128        messageBuilder( duplicateDependencies, "dependencies.dependency", summary );
129
130        if ( model.getDependencyManagement() != null )
131        {
132            @SuppressWarnings( "unchecked" )
133            List<Dependency> managementDependencies = model.getDependencies();
134            Map<String, Integer> duplicateManagementDependencies = validateDependencies( managementDependencies );
135            duplicates += duplicateManagementDependencies.size();
136
137            messageBuilder( duplicateManagementDependencies, "dependencyManagement.dependencies.dependency", summary );
138        }
139        
140        
141        @SuppressWarnings( "unchecked" )
142        List<Profile> profiles = model.getProfiles();
143        for ( Profile profile : profiles )
144        {
145            @SuppressWarnings( "unchecked" )
146            List<Dependency> profileDependencies = profile.getDependencies();
147
148            Map<String, Integer> duplicateProfileDependencies = validateDependencies( profileDependencies );
149
150            duplicates += duplicateProfileDependencies.size();
151            
152            messageBuilder( duplicateProfileDependencies, "profiles.profile[" + profile.getId()
153                + "].dependencies.dependency", summary );
154
155            if ( model.getDependencyManagement() != null )
156            {
157                @SuppressWarnings( "unchecked" )
158                List<Dependency> profileManagementDependencies = profile.getDependencies();
159                
160                Map<String, Integer> duplicateProfileManagementDependencies =
161                    validateDependencies( profileManagementDependencies );
162
163                duplicates += duplicateProfileManagementDependencies.size();
164                
165                messageBuilder( duplicateProfileManagementDependencies, "profiles.profile[" + profile.getId()
166                    + "].dependencyManagement.dependencies.dependency", summary );
167            }
168        }
169            
170        if ( summary.length() > 0 )
171        {
172            StringBuilder message = new StringBuilder();
173            message.append( "Found " ).append( duplicates ).append( " duplicate dependency " );
174            message.append( duplicateDependencies.size() == 1 ? "declaration" : "declarations" ).append( " in this project:\n" );
175            message.append( summary );
176            throw new EnforcerRuleException( message.toString() );
177        }
178    }
179
180    private void messageBuilder( Map<String, Integer> duplicateDependencies, String prefix, StringBuilder message )
181    {
182        if ( !duplicateDependencies.isEmpty() )
183        {
184            for ( Map.Entry<String, Integer> entry : duplicateDependencies.entrySet() )
185            {
186                message.append( " - " ).append( prefix ).append( '[' ).append( entry.getKey() ).append( "] ( " ).append( entry.getValue() ).append( " times )\n" );
187            }
188        }
189    }
190    
191
192    private Map<String, Integer> validateDependencies( List<Dependency> dependencies )
193        throws EnforcerRuleException
194    {
195        Map<String, Integer> duplicateDeps = new HashMap<String, Integer>();
196        Set<String> deps = new HashSet<String>();
197        for ( Dependency dependency : dependencies )
198        {
199            String key = dependency.getManagementKey();
200
201            if ( deps.contains( key ) )
202            {
203                int times = 1;
204                if ( duplicateDeps.containsKey( key ) )
205                {
206                    times = duplicateDeps.get( key );
207                }
208                duplicateDeps.put( key, times + 1 );
209            }
210            else
211            {
212                deps.add( key );
213            }
214        }
215        return duplicateDeps;
216    }
217
218}