001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.enforcer.rules;
020
021import javax.inject.Inject;
022import javax.inject.Named;
023
024import java.io.FileInputStream;
025import java.io.IOException;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.List;
029import java.util.Map;
030import java.util.Objects;
031import java.util.Set;
032
033import org.apache.maven.enforcer.rule.api.EnforcerRuleError;
034import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
035import org.apache.maven.model.Dependency;
036import org.apache.maven.model.Model;
037import org.apache.maven.model.Profile;
038import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
039import org.apache.maven.project.MavenProject;
040import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
041
042/**
043 * Since Maven 3 'dependencies.dependency.(groupId:artifactId:type:classifier)' must be unique. Early versions of Maven
044 * 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@Named("banDuplicatePomDependencyVersions")
050public final class BanDuplicatePomDependencyVersions extends AbstractStandardEnforcerRule {
051
052    private final MavenProject project;
053
054    @Inject
055    public BanDuplicatePomDependencyVersions(MavenProject project) {
056        this.project = Objects.requireNonNull(project);
057    }
058
059    @Override
060    public void execute() throws EnforcerRuleException {
061
062        // re-read model, because M3 uses optimized model
063        MavenXpp3Reader modelReader = new MavenXpp3Reader();
064
065        Model model;
066        try (FileInputStream pomInputStream = new FileInputStream(project.getFile())) {
067            model = modelReader.read(pomInputStream, false);
068        } catch (IOException | XmlPullParserException e) {
069            throw new EnforcerRuleError("Unable to retrieve the MavenProject: ", e);
070        }
071
072        // @todo reuse ModelValidator when possible
073
074        // Object modelValidator = null;
075        // try
076        // {
077        // modelValidator = helper.getComponent( "org.apache.maven.model.validation.ModelValidator" );
078        // }
079        // catch ( ComponentLookupException e1 )
080        // {
081        // // noop
082        // }
083
084        // if( modelValidator == null )
085        // {
086        maven2Validation(model);
087        // }
088        // else
089        // {
090        // }
091    }
092
093    private void maven2Validation(Model model) throws EnforcerRuleException {
094        List<Dependency> dependencies = model.getDependencies();
095        Map<String, Integer> duplicateDependencies = validateDependencies(dependencies);
096
097        int duplicates = duplicateDependencies.size();
098
099        StringBuilder summary = new StringBuilder();
100        messageBuilder(duplicateDependencies, "dependencies.dependency", summary);
101
102        if (model.getDependencyManagement() != null) {
103            List<Dependency> managementDependencies =
104                    model.getDependencyManagement().getDependencies();
105            Map<String, Integer> duplicateManagementDependencies = validateDependencies(managementDependencies);
106            duplicates += duplicateManagementDependencies.size();
107
108            messageBuilder(duplicateManagementDependencies, "dependencyManagement.dependencies.dependency", summary);
109        }
110
111        List<Profile> profiles = model.getProfiles();
112        for (Profile profile : profiles) {
113            List<Dependency> profileDependencies = profile.getDependencies();
114
115            Map<String, Integer> duplicateProfileDependencies = validateDependencies(profileDependencies);
116
117            duplicates += duplicateProfileDependencies.size();
118
119            messageBuilder(
120                    duplicateProfileDependencies,
121                    "profiles.profile[" + profile.getId() + "].dependencies.dependency",
122                    summary);
123
124            if (profile.getDependencyManagement() != null) {
125                List<Dependency> profileManagementDependencies =
126                        profile.getDependencyManagement().getDependencies();
127
128                Map<String, Integer> duplicateProfileManagementDependencies =
129                        validateDependencies(profileManagementDependencies);
130
131                duplicates += duplicateProfileManagementDependencies.size();
132
133                messageBuilder(
134                        duplicateProfileManagementDependencies,
135                        "profiles.profile[" + profile.getId() + "].dependencyManagement.dependencies.dependency",
136                        summary);
137            }
138        }
139
140        if (summary.length() > 0) {
141            StringBuilder message = new StringBuilder();
142            message.append("Found ").append(duplicates).append(" duplicate dependency ");
143            message.append(duplicateDependencies.size() == 1 ? "declaration" : "declarations")
144                    .append(" in this project:" + System.lineSeparator());
145            message.append(summary);
146            throw new EnforcerRuleException(message.toString());
147        }
148    }
149
150    private void messageBuilder(Map<String, Integer> duplicateDependencies, String prefix, StringBuilder message) {
151        if (!duplicateDependencies.isEmpty()) {
152            for (Map.Entry<String, Integer> entry : duplicateDependencies.entrySet()) {
153                message.append(" - ")
154                        .append(prefix)
155                        .append('[')
156                        .append(entry.getKey())
157                        .append("] (")
158                        .append(entry.getValue())
159                        .append(" times)" + System.lineSeparator());
160            }
161        }
162    }
163
164    private Map<String, Integer> validateDependencies(List<Dependency> dependencies) {
165        Map<String, Integer> duplicateDeps = new HashMap<>();
166        Set<String> deps = new HashSet<>();
167        for (Dependency dependency : dependencies) {
168            String key = dependency.getManagementKey();
169
170            if (deps.contains(key)) {
171                int times = 1;
172                if (duplicateDeps.containsKey(key)) {
173                    times = duplicateDeps.get(key);
174                }
175                duplicateDeps.put(key, times + 1);
176            } else {
177                deps.add(key);
178            }
179        }
180        return duplicateDeps;
181    }
182}