This is an automated email from the ASF dual-hosted git repository. sjaranowski pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-dependency-analyzer.git
The following commit(s) were added to refs/heads/master by this push: new 064fa0e [MSHARED-1407] Track dependency usage by referencing classes (#125) 064fa0e is described below commit 064fa0e4494b826ea58952cf63be02ce3e18bfe4 Author: Jared Stehler <jsteh...@hubspot.com> AuthorDate: Mon Jun 3 12:55:09 2024 -0400 [MSHARED-1407] Track dependency usage by referencing classes (#125) --- .../analyzer/DefaultProjectDependencyAnalyzer.java | 47 ++++++----- .../dependency/analyzer/DependencyAnalyzer.java | 17 +++- .../dependency/analyzer/DependencyUsage.java | 92 ++++++++++++++++++++++ .../analyzer/ProjectDependencyAnalysis.java | 33 +++++--- .../analyzer/asm/ASMDependencyAnalyzer.java | 5 +- .../analyzer/asm/DefaultAnnotationVisitor.java | 11 ++- .../analyzer/asm/DefaultClassVisitor.java | 24 +++--- .../analyzer/asm/DefaultFieldVisitor.java | 8 +- .../analyzer/asm/DefaultMethodVisitor.java | 38 +++++---- .../analyzer/asm/DefaultSignatureVisitor.java | 8 +- .../analyzer/asm/DependencyClassFileVisitor.java | 26 ++++-- .../dependency/analyzer/asm/ResultCollector.java | 47 +++++++---- .../analyzer/asm/DependencyVisitorTest.java | 13 +-- 13 files changed, 273 insertions(+), 96 deletions(-) diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java index c792571..b36bd54 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DefaultProjectDependencyAnalyzer.java @@ -36,6 +36,7 @@ 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; @@ -68,17 +69,17 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz 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(); @@ -90,7 +91,7 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz 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); @@ -190,29 +191,33 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz 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) { @@ -225,20 +230,20 @@ public class DefaultProjectDependencyAnalyzer implements ProjectDependencyAnalyz 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); } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java index 22a6a27..e73bc56 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyAnalyzer.java @@ -21,6 +21,7 @@ package org.apache.maven.shared.dependency.analyzer; 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. @@ -48,5 +49,19 @@ public interface DependencyAnalyzer { * @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; } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyUsage.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyUsage.java new file mode 100644 index 0000000..6b2109d --- /dev/null +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/DependencyUsage.java @@ -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:hijo...@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(); + } +} diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysis.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysis.java index 1f71550..9e63cff 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysis.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/ProjectDependencyAnalysis.java @@ -26,6 +26,7 @@ import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.maven.artifact.Artifact; @@ -39,7 +40,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; @@ -49,7 +50,7 @@ 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); } /** @@ -88,7 +89,7 @@ public class 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); @@ -121,6 +122,20 @@ public class ProjectDependencyAnalysis { * @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); } @@ -294,29 +309,29 @@ public class ProjectDependencyAnalysis { 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; diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java index 7b542cd..4e85c7b 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ASMDependencyAnalyzer.java @@ -28,6 +28,7 @@ import java.util.Set; import org.apache.maven.shared.dependency.analyzer.ClassFileVisitorUtils; import org.apache.maven.shared.dependency.analyzer.ClassesPatterns; import org.apache.maven.shared.dependency.analyzer.DependencyAnalyzer; +import org.apache.maven.shared.dependency.analyzer.DependencyUsage; /** * ASMDependencyAnalyzer @@ -39,11 +40,11 @@ import org.apache.maven.shared.dependency.analyzer.DependencyAnalyzer; public class ASMDependencyAnalyzer implements DependencyAnalyzer { @Override - public Set<String> analyze(URL url, ClassesPatterns excludeClasses) throws IOException { + public Set<DependencyUsage> analyzeUsages(URL url, ClassesPatterns excludeClasses) throws IOException { DependencyClassFileVisitor visitor = new DependencyClassFileVisitor(excludeClasses); ClassFileVisitorUtils.accept(url, visitor); - return visitor.getDependencies(); + return visitor.getDependencyUsages(); } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultAnnotationVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultAnnotationVisitor.java index b579273..562637d 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultAnnotationVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultAnnotationVisitor.java @@ -31,34 +31,37 @@ import org.objectweb.asm.Type; public class DefaultAnnotationVisitor extends AnnotationVisitor { private final ResultCollector resultCollector; + private final String usedByClass; + /** * <p>Constructor for DefaultAnnotationVisitor.</p> * * @param resultCollector a {@link org.apache.maven.shared.dependency.analyzer.asm.ResultCollector} object. */ - public DefaultAnnotationVisitor(ResultCollector resultCollector) { + public DefaultAnnotationVisitor(ResultCollector resultCollector, String usedByClass) { super(Opcodes.ASM9); this.resultCollector = resultCollector; + this.usedByClass = usedByClass; } /** {@inheritDoc} */ @Override public void visit(final String name, final Object value) { if (value instanceof Type) { - resultCollector.addType((Type) value); + resultCollector.addType(usedByClass, (Type) value); } } /** {@inheritDoc} */ @Override public void visitEnum(final String name, final String desc, final String value) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); } /** {@inheritDoc} */ @Override public AnnotationVisitor visitAnnotation(final String name, final String desc) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); return this; } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultClassVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultClassVisitor.java index 2d683e6..fe0291c 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultClassVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultClassVisitor.java @@ -44,6 +44,8 @@ public class DefaultClassVisitor extends ClassVisitor { private final MethodVisitor methodVisitor; + private final String usedByClass; + /** * <p>Constructor for DefaultClassVisitor.</p> * @@ -58,13 +60,15 @@ public class DefaultClassVisitor extends ClassVisitor { AnnotationVisitor annotationVisitor, FieldVisitor fieldVisitor, MethodVisitor methodVisitor, - ResultCollector resultCollector) { + ResultCollector resultCollector, + String usedByClass) { super(Opcodes.ASM9); this.signatureVisitor = signatureVisitor; this.annotationVisitor = annotationVisitor; this.fieldVisitor = fieldVisitor; this.methodVisitor = methodVisitor; this.resultCollector = resultCollector; + this.usedByClass = usedByClass; } /** @@ -86,8 +90,8 @@ public class DefaultClassVisitor extends ClassVisitor { final String superName, final String[] interfaces) { if (signature == null) { - resultCollector.addName(superName); - resultCollector.addNames(interfaces); + resultCollector.addName(usedByClass, superName); + resultCollector.addNames(usedByClass, interfaces); } else { addSignature(signature); } @@ -96,7 +100,7 @@ public class DefaultClassVisitor extends ClassVisitor { /** {@inheritDoc} */ @Override public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); return annotationVisitor; } @@ -106,13 +110,13 @@ public class DefaultClassVisitor extends ClassVisitor { public FieldVisitor visitField( final int access, final String name, final String desc, final String signature, final Object value) { if (signature == null) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); } else { addTypeSignature(signature); } if (value instanceof Type) { - resultCollector.addType((Type) value); + resultCollector.addType(usedByClass, (Type) value); } return fieldVisitor; @@ -132,12 +136,12 @@ public class DefaultClassVisitor extends ClassVisitor { public MethodVisitor visitMethod( final int access, final String name, final String desc, final String signature, final String[] exceptions) { if (signature == null) { - resultCollector.addMethodDesc(desc); + resultCollector.addMethodDesc(usedByClass, desc); } else { addSignature(signature); } - resultCollector.addNames(exceptions); + resultCollector.addNames(usedByClass, exceptions); return methodVisitor; } @@ -145,13 +149,13 @@ public class DefaultClassVisitor extends ClassVisitor { /** {@inheritDoc} */ @Override public void visitNestHost(final String nestHost) { - resultCollector.addName(nestHost); + resultCollector.addName(usedByClass, nestHost); } /** {@inheritDoc} */ @Override public void visitNestMember(final String nestMember) { - resultCollector.addName(nestMember); + resultCollector.addName(usedByClass, nestMember); } private void addSignature(final String signature) { diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultFieldVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultFieldVisitor.java index 6f7e140..2a791b9 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultFieldVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultFieldVisitor.java @@ -33,22 +33,26 @@ public class DefaultFieldVisitor extends FieldVisitor { private final ResultCollector resultCollector; + private final String usedByClass; + /** * <p>Constructor for DefaultFieldVisitor.</p> * * @param annotationVisitor a {@link org.objectweb.asm.AnnotationVisitor} object. * @param resultCollector a {@link org.apache.maven.shared.dependency.analyzer.asm.ResultCollector} object. */ - public DefaultFieldVisitor(AnnotationVisitor annotationVisitor, ResultCollector resultCollector) { + public DefaultFieldVisitor( + AnnotationVisitor annotationVisitor, ResultCollector resultCollector, String usedByClass) { super(Opcodes.ASM9); this.annotationVisitor = annotationVisitor; this.resultCollector = resultCollector; + this.usedByClass = usedByClass; } /** {@inheritDoc} */ @Override public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); return annotationVisitor; } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultMethodVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultMethodVisitor.java index d1b8d94..a61bb04 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultMethodVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultMethodVisitor.java @@ -43,6 +43,8 @@ public class DefaultMethodVisitor extends MethodVisitor { private final ResultCollector resultCollector; + private final String usedByClass; + /** * <p>Constructor for DefaultMethodVisitor.</p> * @@ -51,17 +53,21 @@ public class DefaultMethodVisitor extends MethodVisitor { * @param resultCollector a {@link org.apache.maven.shared.dependency.analyzer.asm.ResultCollector} object. */ public DefaultMethodVisitor( - AnnotationVisitor annotationVisitor, SignatureVisitor signatureVisitor, ResultCollector resultCollector) { + AnnotationVisitor annotationVisitor, + SignatureVisitor signatureVisitor, + ResultCollector resultCollector, + String usedByClass) { super(Opcodes.ASM9); this.annotationVisitor = annotationVisitor; this.signatureVisitor = signatureVisitor; this.resultCollector = resultCollector; + this.usedByClass = usedByClass; } /** {@inheritDoc} */ @Override public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); return annotationVisitor; } @@ -69,7 +75,7 @@ public class DefaultMethodVisitor extends MethodVisitor { /** {@inheritDoc} */ @Override public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); return annotationVisitor; } @@ -77,7 +83,7 @@ public class DefaultMethodVisitor extends MethodVisitor { /** {@inheritDoc} */ @Override public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); return annotationVisitor; } @@ -86,7 +92,7 @@ public class DefaultMethodVisitor extends MethodVisitor { @Override public AnnotationVisitor visitLocalVariableAnnotation( int typeRef, TypePath typePath, Label[] start, Label[] end, int[] index, String desc, boolean visible) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); return annotationVisitor; } @@ -95,16 +101,16 @@ public class DefaultMethodVisitor extends MethodVisitor { @Override public void visitTypeInsn(final int opcode, final String desc) { if (desc.charAt(0) == '[') { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); } else { - resultCollector.addName(desc); + resultCollector.addName(usedByClass, desc); } } /** {@inheritDoc} */ @Override public void visitFieldInsn(final int opcode, final String owner, final String name, final String desc) { - resultCollector.addName(owner); + resultCollector.addName(usedByClass, owner); /* * NOTE: Merely accessing a field does not impose a direct dependency on its type. For example, the code line * <code>java.lang.Object var = bean.field;</code> does not directly depend on the type of the field. A direct @@ -116,30 +122,32 @@ public class DefaultMethodVisitor extends MethodVisitor { /** {@inheritDoc} */ @Override public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - resultCollector.addName(owner); + resultCollector.addName(usedByClass, owner); } /** {@inheritDoc} */ @Override public void visitLdcInsn(final Object cst) { if (cst instanceof Type) { - resultCollector.addType((Type) cst); + resultCollector.addType(usedByClass, (Type) cst); } } /** {@inheritDoc} */ @Override public void visitMultiANewArrayInsn(final String desc, final int dims) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); } /** {@inheritDoc} */ @Override public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) { - resultCollector.addName(type); + resultCollector.addName(usedByClass, type); } - /** {@inheritDoc} */ + /** + * {@inheritDoc} + */ @Override public void visitLocalVariable( final String name, @@ -149,7 +157,7 @@ public class DefaultMethodVisitor extends MethodVisitor { final Label end, final int index) { if (signature == null) { - resultCollector.addDesc(desc); + resultCollector.addDesc(usedByClass, desc); } else { addTypeSignature(signature); } @@ -167,6 +175,6 @@ public class DefaultMethodVisitor extends MethodVisitor { Arrays.stream(bootstrapMethodArguments) .filter(Type.class::isInstance) .map(Type.class::cast) - .forEach(resultCollector::addType); + .forEach(t -> resultCollector.addType(usedByClass, t)); } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultSignatureVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultSignatureVisitor.java index d09bca3..60e7dc6 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultSignatureVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DefaultSignatureVisitor.java @@ -29,26 +29,28 @@ import org.objectweb.asm.signature.SignatureVisitor; */ public class DefaultSignatureVisitor extends SignatureVisitor { private final ResultCollector resultCollector; + private final String usedByClass; /** * <p>Constructor for DefaultSignatureVisitor.</p> * * @param resultCollector a {@link org.apache.maven.shared.dependency.analyzer.asm.ResultCollector} object. */ - public DefaultSignatureVisitor(ResultCollector resultCollector) { + public DefaultSignatureVisitor(ResultCollector resultCollector, String usedByClass) { super(Opcodes.ASM9); this.resultCollector = resultCollector; + this.usedByClass = usedByClass; } /** {@inheritDoc} */ @Override public void visitClassType(final String name) { - resultCollector.addName(name); + resultCollector.addName(usedByClass, name); } /** {@inheritDoc} */ @Override public void visitInnerClassType(final String name) { - resultCollector.addName(name); + resultCollector.addName(usedByClass, name); } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java index 34dac5c..3d16943 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyClassFileVisitor.java @@ -26,6 +26,7 @@ import java.util.Set; import org.apache.maven.shared.dependency.analyzer.ClassFileVisitor; import org.apache.maven.shared.dependency.analyzer.ClassesPatterns; +import org.apache.maven.shared.dependency.analyzer.DependencyUsage; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -76,15 +77,16 @@ public class DependencyClassFileVisitor implements ClassFileVisitor { final Set<String> constantPoolClassRefs = ConstantPoolParser.getConstantPoolClassReferences(byteCode); for (String string : constantPoolClassRefs) { - resultCollector.addName(string); + resultCollector.addName(className, string); } - AnnotationVisitor annotationVisitor = new DefaultAnnotationVisitor(resultCollector); - SignatureVisitor signatureVisitor = new DefaultSignatureVisitor(resultCollector); - FieldVisitor fieldVisitor = new DefaultFieldVisitor(annotationVisitor, resultCollector); - MethodVisitor mv = new DefaultMethodVisitor(annotationVisitor, signatureVisitor, resultCollector); - ClassVisitor classVisitor = - new DefaultClassVisitor(signatureVisitor, annotationVisitor, fieldVisitor, mv, resultCollector); + AnnotationVisitor annotationVisitor = new DefaultAnnotationVisitor(resultCollector, className); + SignatureVisitor signatureVisitor = new DefaultSignatureVisitor(resultCollector, className); + FieldVisitor fieldVisitor = new DefaultFieldVisitor(annotationVisitor, resultCollector, className); + MethodVisitor mv = + new DefaultMethodVisitor(annotationVisitor, signatureVisitor, resultCollector, className); + ClassVisitor classVisitor = new DefaultClassVisitor( + signatureVisitor, annotationVisitor, fieldVisitor, mv, resultCollector, className); reader.accept(classVisitor, 0); } catch (IOException exception) { @@ -116,4 +118,14 @@ public class DependencyClassFileVisitor implements ClassFileVisitor { public Set<String> getDependencies() { return resultCollector.getDependencies(); } + + /** + * <p>getDependencyUsages.</p> + * + * @return the set of classes referenced by visited class files, paired with + * classes declaring the references. + */ + public Set<DependencyUsage> getDependencyUsages() { + return resultCollector.getDependencyUsages(); + } } diff --git a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollector.java b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollector.java index ca9e8ee..65e3937 100644 --- a/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollector.java +++ b/src/main/java/org/apache/maven/shared/dependency/analyzer/asm/ResultCollector.java @@ -20,7 +20,9 @@ package org.apache.maven.shared.dependency.analyzer.asm; import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; +import org.apache.maven.shared.dependency.analyzer.DependencyUsage; import org.objectweb.asm.Type; /** @@ -30,7 +32,7 @@ import org.objectweb.asm.Type; */ public class ResultCollector { - private final Set<String> classes = new HashSet<>(); + private final Set<DependencyUsage> classUsages = new HashSet<>(); /** * <p>getDependencies.</p> @@ -38,7 +40,18 @@ public class ResultCollector { * @return a {@link java.util.Set} object. */ public Set<String> getDependencies() { - return classes; + return getDependencyUsages().stream() + .map(DependencyUsage::getDependencyClass) + .collect(Collectors.toSet()); + } + + /** + * <p>getDependencyUsages.</p> + * + * @return a {@link java.util.Set} object. + */ + public Set<DependencyUsage> getDependencyUsages() { + return classUsages; } /** @@ -46,7 +59,7 @@ public class ResultCollector { * * @param name a {@link java.lang.String} object. */ - public void addName(String name) { + public void addName(final String usedByClass, String name) { if (name == null) { return; } @@ -65,25 +78,25 @@ public class ResultCollector { } // decode internal representation - add(name.replace('/', '.')); + add(usedByClass, name.replace('/', '.')); } - void addDesc(final String desc) { - addType(Type.getType(desc)); + void addDesc(final String usedByClass, final String desc) { + addType(usedByClass, Type.getType(desc)); } - void addType(final Type t) { + void addType(final String usedByClass, final Type t) { switch (t.getSort()) { case Type.ARRAY: - addType(t.getElementType()); + addType(usedByClass, t.getElementType()); break; case Type.METHOD: - addMethodDesc(t.getDescriptor()); + addMethodDesc(usedByClass, t.getDescriptor()); break; case Type.OBJECT: - addName(t.getClassName()); + addName(usedByClass, t.getClassName()); break; default: } @@ -94,30 +107,30 @@ public class ResultCollector { * * @param name a {@link java.lang.String} object. */ - public void add(String name) { + public void add(final String usedByClass, final String name) { // inner classes have equivalent compilation requirement as container class if (name.indexOf('$') < 0) { - classes.add(name); + classUsages.add(new DependencyUsage(name, usedByClass)); } } - void addNames(final String[] names) { + void addNames(final String usedByClass, final String[] names) { if (names == null) { return; } for (String name : names) { - addName(name); + addName(usedByClass, name); } } - void addMethodDesc(final String desc) { - addType(Type.getReturnType(desc)); + void addMethodDesc(final String usedByClass, final String desc) { + addType(usedByClass, Type.getReturnType(desc)); Type[] types = Type.getArgumentTypes(desc); for (Type type : types) { - addType(type); + addType(usedByClass, type); } } } diff --git a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java index cad3a2a..7d814e7 100644 --- a/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java +++ b/src/test/java/org/apache/maven/shared/dependency/analyzer/asm/DependencyVisitorTest.java @@ -41,13 +41,16 @@ class DependencyVisitorTest { private DefaultClassVisitor visitor; private MethodVisitor mv; + private String usedByClass = "com.example.MyClass"; + @BeforeEach void setUp() { - AnnotationVisitor annotationVisitor = new DefaultAnnotationVisitor(resultCollector); - SignatureVisitor signatureVisitor = new DefaultSignatureVisitor(resultCollector); - FieldVisitor fieldVisitor = new DefaultFieldVisitor(annotationVisitor, resultCollector); - mv = new DefaultMethodVisitor(annotationVisitor, signatureVisitor, resultCollector); - visitor = new DefaultClassVisitor(signatureVisitor, annotationVisitor, fieldVisitor, mv, resultCollector); + AnnotationVisitor annotationVisitor = new DefaultAnnotationVisitor(resultCollector, usedByClass); + SignatureVisitor signatureVisitor = new DefaultSignatureVisitor(resultCollector, usedByClass); + FieldVisitor fieldVisitor = new DefaultFieldVisitor(annotationVisitor, resultCollector, usedByClass); + mv = new DefaultMethodVisitor(annotationVisitor, signatureVisitor, resultCollector, usedByClass); + visitor = new DefaultClassVisitor( + signatureVisitor, annotationVisitor, fieldVisitor, mv, resultCollector, usedByClass); } @Test