Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MSHARED-1407] Track dependency usage by referencing classes #16

Merged
merged 5 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,11 @@
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.project.MavenProject;
Expand All @@ -60,25 +54,27 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz
@Inject
private DependencyAnalyzer dependencyAnalyzer;

/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public ProjectDependencyAnalysis analyze(MavenProject project, Collection<String> excludedClasses)
throws ProjectDependencyAnalyzerException {
try {
ClassesPatterns excludedClassesPatterns = new ClassesPatterns(excludedClasses);
Map<Artifact, Set<String>> artifactClassMap = buildArtifactClassMap(project, excludedClassesPatterns);

Set<String> mainDependencyClasses = buildMainDependencyClasses(project, excludedClassesPatterns);
Set<String> testDependencyClasses = buildTestDependencyClasses(project, excludedClassesPatterns);
Set<DependencyUsage> mainDependencyClasses = buildMainDependencyClasses(project, excludedClassesPatterns);
Set<DependencyUsage> testDependencyClasses = buildTestDependencyClasses(project, excludedClassesPatterns);

Set<String> dependencyClasses = new HashSet<>();
Set<DependencyUsage> dependencyClasses = new HashSet<>();
dependencyClasses.addAll(mainDependencyClasses);
dependencyClasses.addAll(testDependencyClasses);

Set<String> testOnlyDependencyClasses =
Set<DependencyUsage> testOnlyDependencyClasses =
buildTestOnlyDependencyClasses(mainDependencyClasses, testDependencyClasses);

Map<Artifact, Set<String>> usedArtifacts = buildUsedArtifacts(artifactClassMap, dependencyClasses);
Map<Artifact, Set<DependencyUsage>> usedArtifacts = buildUsedArtifacts(artifactClassMap, dependencyClasses);
Set<Artifact> mainUsedArtifacts =
buildUsedArtifacts(artifactClassMap, mainDependencyClasses).keySet();

Expand All @@ -90,7 +86,7 @@ public ProjectDependencyAnalysis analyze(MavenProject project, Collection<String
Set<Artifact> usedDeclaredArtifacts = new LinkedHashSet<>(declaredArtifacts);
usedDeclaredArtifacts.retainAll(usedArtifacts.keySet());

Map<Artifact, Set<String>> usedUndeclaredArtifactsWithClasses = new LinkedHashMap<>(usedArtifacts);
Map<Artifact, Set<DependencyUsage>> usedUndeclaredArtifactsWithClasses = new LinkedHashMap<>(usedArtifacts);
Set<Artifact> usedUndeclaredArtifacts =
removeAll(usedUndeclaredArtifactsWithClasses.keySet(), declaredArtifacts);
usedUndeclaredArtifactsWithClasses.keySet().retainAll(usedUndeclaredArtifacts);
Expand Down Expand Up @@ -190,29 +186,33 @@ private Map<Artifact, Set<String>> buildArtifactClassMap(MavenProject project, C
return artifactClassMap;
}

private static Set<String> buildTestOnlyDependencyClasses(
Set<String> mainDependencyClasses, Set<String> testDependencyClasses) {
Set<String> testOnlyDependencyClasses = new HashSet<>(testDependencyClasses);
testOnlyDependencyClasses.removeAll(mainDependencyClasses);
private static Set<DependencyUsage> buildTestOnlyDependencyClasses(
Set<DependencyUsage> mainDependencyClasses, Set<DependencyUsage> testDependencyClasses) {
Set<DependencyUsage> testOnlyDependencyClasses = new HashSet<>(testDependencyClasses);
Set<String> mainDepClassNames = mainDependencyClasses.stream()
.map(DependencyUsage::getDependencyClass)
.collect(Collectors.toSet());
testOnlyDependencyClasses.removeIf(u -> mainDepClassNames.contains(u.getDependencyClass()));
return testOnlyDependencyClasses;
}

private Set<String> buildMainDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
private Set<DependencyUsage> buildMainDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
throws IOException {
String outputDirectory = project.getBuild().getOutputDirectory();
return buildDependencyClasses(outputDirectory, excludedClasses);
}

private Set<String> buildTestDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
private Set<DependencyUsage> buildTestDependencyClasses(MavenProject project, ClassesPatterns excludedClasses)
throws IOException {
String testOutputDirectory = project.getBuild().getTestOutputDirectory();
return buildDependencyClasses(testOutputDirectory, excludedClasses);
}

private Set<String> buildDependencyClasses(String path, ClassesPatterns excludedClasses) throws IOException {
private Set<DependencyUsage> buildDependencyClasses(String path, ClassesPatterns excludedClasses)
throws IOException {
URL url = new File(path).toURI().toURL();

return dependencyAnalyzer.analyze(url, excludedClasses);
return dependencyAnalyzer.analyzeUsages(url, excludedClasses);
}

private static Set<Artifact> buildDeclaredArtifacts(MavenProject project) {
Expand All @@ -225,20 +225,20 @@ private static Set<Artifact> buildDeclaredArtifacts(MavenProject project) {
return declaredArtifacts;
}

private static Map<Artifact, Set<String>> buildUsedArtifacts(
Map<Artifact, Set<String>> artifactClassMap, Set<String> dependencyClasses) {
Map<Artifact, Set<String>> usedArtifacts = new HashMap<>();
private static Map<Artifact, Set<DependencyUsage>> buildUsedArtifacts(
Map<Artifact, Set<String>> artifactClassMap, Set<DependencyUsage> dependencyClasses) {
Map<Artifact, Set<DependencyUsage>> usedArtifacts = new HashMap<>();

for (String className : dependencyClasses) {
Artifact artifact = findArtifactForClassName(artifactClassMap, className);
for (DependencyUsage classUsage : dependencyClasses) {
Artifact artifact = findArtifactForClassName(artifactClassMap, classUsage.getDependencyClass());

if (artifact != null) {
Set<String> classesFromArtifact = usedArtifacts.get(artifact);
Set<DependencyUsage> classesFromArtifact = usedArtifacts.get(artifact);
if (classesFromArtifact == null) {
classesFromArtifact = new HashSet<>();
usedArtifacts.put(artifact, classesFromArtifact);
}
classesFromArtifact.add(className);
classesFromArtifact.add(classUsage);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.io.IOException;
import java.net.URL;
import java.util.Set;
import java.util.stream.Collectors;

/**
* Gets the set of classes referenced by a library given either as a jar file or an exploded directory.
Expand Down Expand Up @@ -48,5 +49,19 @@ default Set<String> analyze(URL url) throws IOException {
* @return the set of class names referenced by the library
* @throws IOException if an error occurs reading a JAR or .class file
*/
Set<String> analyze(URL url, ClassesPatterns excludeClasses) throws IOException;
default Set<String> analyze(URL url, ClassesPatterns excludeClasses) throws IOException {
return analyzeUsages(url, excludeClasses).stream()
.map(DependencyUsage::getDependencyClass)
.collect(Collectors.toSet());
}

/**
* <p>analyzeUsages.</p>
*
* @param url the JAR file or directory to analyze
* @return the set of class names referenced by the library, paired with the
* classes declaring those references.
* @throws IOException if an error occurs reading a JAR or .class file
*/
Set<DependencyUsage> analyzeUsages(URL url, ClassesPatterns excludeClasses) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.shared.dependency.analyzer;

/**
* Usage of a dependency class by a project class.
*
* @author <a href="mailto:hijon89@gmail.com">Jonathan Haber</a>
*/
public class DependencyUsage {

private final String dependencyClass;

private final String usedBy;

public DependencyUsage(String dependencyClass, String usedBy) {
this.dependencyClass = dependencyClass;
this.usedBy = usedBy;
}

/**
* @return the dependency class used by the project class
*/
public String getDependencyClass() {
return dependencyClass;
}

/**
* @return the project class using the dependency class
*/
public String getUsedBy() {
return usedBy;
}

/*
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
int hashCode = dependencyClass.hashCode();
hashCode = (hashCode * 37) + usedBy.hashCode();

return hashCode;
}

/*
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object object) {
if (object instanceof DependencyUsage) {
DependencyUsage usage = (DependencyUsage) object;

return getDependencyClass().equals(usage.getDependencyClass())
&& getUsedBy().equals(usage.getUsedBy());
}

return false;
}

/*
* @see java.lang.Object#toString()
*/
public String toString() {
StringBuilder buffer = new StringBuilder();

buffer.append("dependencyClass=").append(getDependencyClass());
buffer.append(",");
buffer.append("usedBy=").append(getUsedBy());

buffer.insert(0, "[");
buffer.insert(0, getClass().getName());

buffer.append("]");

return buffer.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,8 @@
*/
package org.apache.maven.shared.dependency.analyzer;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.stream.Collectors;

import org.apache.maven.artifact.Artifact;

Expand All @@ -39,7 +33,7 @@ public class ProjectDependencyAnalysis {

private final Set<Artifact> usedDeclaredArtifacts;

private final Map<Artifact, Set<String>> usedUndeclaredArtifacts;
private final Map<Artifact, Set<DependencyUsage>> usedUndeclaredArtifacts;

private final Set<Artifact> unusedDeclaredArtifacts;

Expand All @@ -49,13 +43,13 @@ public class ProjectDependencyAnalysis {
* <p>Constructor for ProjectDependencyAnalysis.</p>
*/
public ProjectDependencyAnalysis() {
this(null, (Map<Artifact, Set<String>>) null, null, null);
this(null, (Map<Artifact, Set<DependencyUsage>>) null, null, null);
}

/**
* <p>Constructor for ProjectDependencyAnalysis to maintain compatibility with old API</p>
*
* @param usedDeclaredArtifacts artifacts both used and declared
* @param usedDeclaredArtifacts artifacts both used and declared
* @param usedUndeclaredArtifacts artifacts used but not declared
* @param unusedDeclaredArtifacts artifacts declared but not used
*/
Expand All @@ -69,9 +63,9 @@ public ProjectDependencyAnalysis(
/**
* <p>Constructor for ProjectDependencyAnalysis.</p>
*
* @param usedDeclaredArtifacts artifacts both used and declared
* @param usedUndeclaredArtifacts artifacts used but not declared
* @param unusedDeclaredArtifacts artifacts declared but not used
* @param usedDeclaredArtifacts artifacts both used and declared
* @param usedUndeclaredArtifacts artifacts used but not declared
* @param unusedDeclaredArtifacts artifacts declared but not used
* @param testArtifactsWithNonTestScope artifacts only used in tests but not declared with test scope
*/
public ProjectDependencyAnalysis(
Expand All @@ -88,7 +82,7 @@ public ProjectDependencyAnalysis(

public ProjectDependencyAnalysis(
Set<Artifact> usedDeclaredArtifacts,
Map<Artifact, Set<String>> usedUndeclaredArtifacts,
Map<Artifact, Set<DependencyUsage>> usedUndeclaredArtifacts,
Set<Artifact> unusedDeclaredArtifacts,
Set<Artifact> testArtifactsWithNonTestScope) {
this.usedDeclaredArtifacts = safeCopy(usedDeclaredArtifacts);
Expand Down Expand Up @@ -121,6 +115,20 @@ public Set<Artifact> getUsedUndeclaredArtifacts() {
* @return artifacts used but not declared
*/
public Map<Artifact, Set<String>> getUsedUndeclaredArtifactsWithClasses() {
Map<Artifact, Set<String>> usedUndeclaredArtifactsWithClasses = new HashMap<>();

for (Map.Entry<Artifact, Set<DependencyUsage>> entry : usedUndeclaredArtifacts.entrySet()) {
usedUndeclaredArtifactsWithClasses.put(
entry.getKey(),
entry.getValue().stream()
.map(DependencyUsage::getDependencyClass)
.collect(Collectors.toSet()));
}

return usedUndeclaredArtifactsWithClasses;
}

public Map<Artifact, Set<DependencyUsage>> getUsedUndeclaredArtifactsWithUsages() {
return safeCopy(usedUndeclaredArtifacts);
}

Expand All @@ -136,7 +144,7 @@ public Set<Artifact> getUnusedDeclaredArtifacts() {
/**
* Returns artifacts only used in tests but not declared with test scope.
*
* @return artifacts only used in tests but not declared with test scope
* @return artifacts only used in tests but not declared with test scope
*/
public Set<Artifact> getTestArtifactsWithNonTestScope() {
return safeCopy(testArtifactsWithNonTestScope);
Expand Down Expand Up @@ -228,7 +236,9 @@ public int hashCode() {
return hashCode;
}

/** {@inheritDoc} */
/**
* {@inheritDoc}
*/
@Override
public boolean equals(Object object) {
if (object instanceof ProjectDependencyAnalysis) {
Expand Down Expand Up @@ -294,29 +304,29 @@ private Set<Artifact> safeCopy(Set<Artifact> set) {
return (set == null) ? Collections.emptySet() : Collections.unmodifiableSet(new LinkedHashSet<>(set));
}

private static Map<Artifact, Set<String>> safeCopy(Map<Artifact, Set<String>> origMap) {
private static Map<Artifact, Set<DependencyUsage>> safeCopy(Map<Artifact, Set<DependencyUsage>> origMap) {
if (origMap == null) {
return Collections.emptyMap();
}

Map<Artifact, Set<String>> map = new HashMap<>();
Map<Artifact, Set<DependencyUsage>> map = new HashMap<>();

for (Map.Entry<Artifact, Set<String>> e : origMap.entrySet()) {
for (Map.Entry<Artifact, Set<DependencyUsage>> e : origMap.entrySet()) {
map.put(e.getKey(), Collections.unmodifiableSet(new LinkedHashSet<>(e.getValue())));
}

return map;
}

private static Map<Artifact, Set<String>> mapWithKeys(Set<Artifact> keys) {
private static Map<Artifact, Set<DependencyUsage>> mapWithKeys(Set<Artifact> keys) {
if (keys == null) {
return Collections.emptyMap();
}

Map<Artifact, Set<String>> map = new HashMap<>();
Map<Artifact, Set<DependencyUsage>> map = new HashMap<>();

for (Artifact k : keys) {
map.put(k, Collections.<String>emptySet());
map.put(k, Collections.<DependencyUsage>emptySet());
}

return map;
Expand Down
Loading