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.util.ArrayList;
025import java.util.Collection;
026import java.util.HashSet;
027import java.util.LinkedHashMap;
028import java.util.List;
029import java.util.Map;
030import java.util.Objects;
031import java.util.Set;
032import java.util.function.Function;
033import java.util.regex.Pattern;
034
035import org.apache.maven.artifact.Artifact;
036import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
037import org.apache.maven.execution.MavenSession;
038import org.apache.maven.model.Plugin;
039import org.apache.maven.model.PluginManagement;
040import org.apache.maven.model.ReportPlugin;
041import org.apache.maven.project.MavenProject;
042
043/**
044 * @author Robert Scholte
045 * @since 1.3
046 */
047@Named("requireSameVersions")
048public final class RequireSameVersions extends AbstractStandardEnforcerRule {
049    private boolean uniqueVersions;
050
051    private Set<String> dependencies = new HashSet<>();
052
053    private Set<String> plugins = new HashSet<>();
054
055    private Set<String> buildPlugins = new HashSet<>();
056
057    private Set<String> reportPlugins = new HashSet<>();
058
059    private boolean sameModuleVersions;
060
061    private final MavenProject project;
062
063    private final MavenSession session;
064
065    @Inject
066    public RequireSameVersions(MavenProject project, MavenSession session) {
067        this.project = Objects.requireNonNull(project);
068        this.session = Objects.requireNonNull(session);
069    }
070
071    @Override
072    public void execute() throws EnforcerRuleException {
073        // consider including profile based artifacts
074        Map<String, List<String>> versionMembers = new LinkedHashMap<>();
075
076        Set<String> allBuildPlugins = new HashSet<>(buildPlugins);
077        allBuildPlugins.addAll(plugins);
078        Set<String> allReportPlugins = new HashSet<>(reportPlugins);
079        allReportPlugins.addAll(plugins);
080        // CHECKSTYLE_OFF: LineLength
081        versionMembers.putAll(collectVersionMembers(
082                project.getArtifacts(),
083                dependencies,
084                " (dependency)",
085                artifact -> uniqueVersions ? artifact.getVersion() : artifact.getBaseVersion()));
086
087        versionMembers.putAll(collectVersionMembers(
088                project.getPluginArtifacts(),
089                allBuildPlugins,
090                " (buildPlugin)",
091                artifact -> uniqueVersions ? artifact.getVersion() : artifact.getBaseVersion()));
092
093        versionMembers.putAll(collectVersionMembers(
094                project.getReportArtifacts(), allReportPlugins, " (reportPlugin)", this::getReportPluginVersion));
095        // CHECKSTYLE_ON: LineLength
096
097        if (versionMembers.size() > 1) {
098            StringBuilder builder = new StringBuilder();
099            if (getMessage() != null) {
100                builder.append(getMessage()).append("\n");
101            }
102            builder.append("Found entries with different versions" + "\n");
103            for (Map.Entry<String, List<String>> entry : versionMembers.entrySet()) {
104                builder.append("Entries with version ").append(entry.getKey()).append("\n");
105                for (String conflictId : entry.getValue()) {
106                    builder.append("- ").append(conflictId).append("\n");
107                }
108            }
109            throw new EnforcerRuleException(builder.toString());
110        }
111
112        if (sameModuleVersions) {
113            MavenProject topLevelProject = session.getTopLevelProject();
114            if (!Objects.equals(topLevelProject.getVersion(), project.getVersion())) {
115                StringBuilder builder = new StringBuilder();
116                if (getMessage() != null) {
117                    builder.append(getMessage()).append("\n");
118                }
119                builder.append("Top level project has version " + topLevelProject.getVersion()
120                        + " but current module has different version " + project.getVersion());
121                throw new EnforcerRuleException(builder.toString());
122            }
123        }
124    }
125
126    private Map<String, List<String>> collectVersionMembers(
127            Set<Artifact> artifacts,
128            Collection<String> patterns,
129            String source,
130            Function<Artifact, String> versionResolver) {
131        Map<String, List<String>> versionMembers = new LinkedHashMap<>();
132
133        List<Pattern> regExs = new ArrayList<>();
134        for (String pattern : patterns) {
135            String regex = pattern.replace(".", "\\.")
136                    .replace("*", ".*")
137                    .replace(":", "\\:")
138                    .replace('?', '.');
139
140            // pattern is groupId[:artifactId[:type[:classifier]]]
141            regExs.add(Pattern.compile(regex + "(\\:.+)?"));
142        }
143
144        for (Artifact artifact : artifacts) {
145            for (Pattern regEx : regExs) {
146                if (regEx.matcher(artifact.getDependencyConflictId()).matches()) {
147                    String version = versionResolver.apply(artifact);
148                    versionMembers
149                            .computeIfAbsent(version, unused -> new ArrayList<>())
150                            .add(artifact.getDependencyConflictId() + source);
151                }
152            }
153        }
154        return versionMembers;
155    }
156
157    private String getReportPluginVersion(Artifact artifact) {
158        for (ReportPlugin reportPlugin : project.getReportPlugins()) {
159            if (Objects.equals(artifact.getGroupId(), reportPlugin.getGroupId())
160                    && Objects.equals(artifact.getArtifactId(), reportPlugin.getArtifactId())
161                    && reportPlugin.getVersion() != null) {
162                return reportPlugin.getVersion();
163            }
164        }
165        if (project.getBuild() != null) {
166            for (Plugin plugin : project.getBuild().getPlugins()) {
167                if (Objects.equals(artifact.getGroupId(), plugin.getGroupId())
168                        && Objects.equals(artifact.getArtifactId(), plugin.getArtifactId())
169                        && plugin.getVersion() != null) {
170                    return plugin.getVersion();
171                }
172            }
173        }
174        PluginManagement pluginManagement =
175                project.getBuild() != null ? project.getBuild().getPluginManagement() : null;
176
177        if (pluginManagement != null) {
178            for (Plugin plugin : pluginManagement.getPlugins()) {
179                if (Objects.equals(artifact.getGroupId(), plugin.getGroupId())
180                        && Objects.equals(artifact.getArtifactId(), plugin.getArtifactId())
181                        && plugin.getVersion() != null) {
182                    return plugin.getVersion();
183                }
184            }
185        }
186
187        return uniqueVersions ? artifact.getVersion() : artifact.getBaseVersion();
188    }
189
190    void addDependency(String dependency) {
191        dependencies.add(dependency);
192    }
193
194    void addPlugin(String plugin) {
195        plugins.add(plugin);
196    }
197
198    void addBuildPlugin(String buildPlugin) {
199        buildPlugins.add(buildPlugin);
200    }
201
202    void addReportPlugin(String reportPlugin) {
203        reportPlugins.add(reportPlugin);
204    }
205
206    @Override
207    public String toString() {
208        return String.format(
209                "RequireSameVersions[dependencies=%s, buildPlugins=%s, reportPlugins=%s, plugins=%s, uniqueVersions=%b, sameModuleVersions=%b]",
210                dependencies, buildPlugins, reportPlugins, plugins, uniqueVersions, sameModuleVersions);
211    }
212}