Skip to content

Commit 08a59d8

Browse files
committed
implement analyzer
1 parent f383db8 commit 08a59d8

File tree

13 files changed

+534
-59
lines changed

13 files changed

+534
-59
lines changed

src/main/java/org/example/Analyzer.java

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,14 @@
33
import java.io.IOException;
44
import java.io.InputStream;
55
import java.nio.file.Path;
6+
import java.util.List;
67
import java.util.jar.JarFile;
8+
import org.example.model.Statistic;
9+
import org.example.util.ClassRepository;
10+
import org.example.util.provider.ClassProvider;
11+
import org.example.util.provider.impl.CompositeClassProvider;
12+
import org.example.util.provider.impl.JarClassProvider;
13+
import org.example.util.provider.impl.SystemClassProvider;
714
import org.example.visitor.ClassStatisticVisitor;
815
import org.objectweb.asm.ClassReader;
916
import org.objectweb.asm.ClassVisitor;
@@ -14,32 +21,37 @@ public static void main(String[] args) throws IOException {
1421
var path = Path.of("src/main/resources/sample.jar");
1522
var analyzer = new Analyzer();
1623

17-
analyzer.analyzeJar(path);
24+
var statistic = analyzer.analyzeJar(path);
25+
statistic.printStatistic();
1826
}
1927

20-
public void analyzeJar(Path path) throws IOException {
28+
public Statistic analyzeJar(Path path) throws IOException {
29+
Statistic statistic = new Statistic();
2130
try (JarFile jar = new JarFile(path.toFile())) {
2231

23-
Statistic statistic = new Statistic();
32+
ClassProvider jarProvider = new JarClassProvider(jar);
33+
ClassProvider systemProvider = new SystemClassProvider();
34+
ClassProvider compositeProvider = new CompositeClassProvider(List.of(jarProvider, systemProvider));
35+
36+
ClassRepository repository = new ClassRepository(compositeProvider);
37+
2438

2539
jar.stream()
2640
.filter(entry -> entry.getName().endsWith(".class"))
2741
.forEach(entry -> {
28-
// Should be safe for Java classes, since they always in the class with same name
29-
// unlike kotlin classes
30-
String className = entry.getName().replace(".class", "");
31-
32-
try (InputStream is = jar.getInputStream(entry)) {
33-
ClassReader cr = new ClassReader(is);
34-
ClassVisitor visitor = new ClassStatisticVisitor(statistic);
35-
cr.accept(visitor, 0);
36-
} catch (IOException e) {
37-
throw new RuntimeException(e);
38-
}
39-
}
40-
);
41-
42-
statistic.printStatistic();
42+
String internalName = entry.getName().replace(".class", "");
43+
44+
try (InputStream is = jar.getInputStream(entry)) {
45+
ClassReader cr = new ClassReader(is);
46+
47+
ClassVisitor visitor = new ClassStatisticVisitor(statistic, repository);
48+
49+
cr.accept(visitor, 0);
50+
} catch (IOException e) {
51+
throw new RuntimeException(e);
52+
}
53+
});
4354
}
55+
return statistic;
4456
}
4557
}

src/main/java/org/example/Statistic.java

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package org.example.model;
2+
3+
/*
4+
ABC rules for Java
5+
The following rules give the count of Assignments, Branches, Conditionals in the ABC metric for Java:
6+
7+
1. Add one to the assignment count when:
8+
Occurrence of an assignment operator (exclude constant declarations and default parameter assignments) (**=, *=, /=, %=, +=, -=, <<=, >>=, &=, !=, ^=, >>>=**).
9+
Occurrence of an increment or a decrement operator (prefix or postfix) (++, --).
10+
2. Add one to branch count when
11+
Occurrence of a function call or a class method call.
12+
Occurrence of a ‘new’ operator.
13+
3. Add one to condition count when:
14+
Occurrence of a conditional operator (<, >, <=, >=, ==, !=).
15+
Occurrence of the following keywords (‘else’, ‘case’, ‘default’, ‘?’, ‘try’, ‘catch’).
16+
Occurrence of a unary conditional operator.
17+
*/
18+
public class AbcMetric {
19+
20+
private long assignment;
21+
private long branch;
22+
private long condition;
23+
24+
public AbcMetric(long assignment, long branch, long condition) {
25+
this.assignment = assignment;
26+
this.branch = branch;
27+
this.condition = condition;
28+
}
29+
30+
public AbcMetric(AbcMetric other) {
31+
this.assignment = other.assignment;
32+
this.branch = other.branch;
33+
this.condition = other.condition;
34+
}
35+
36+
public void add(AbcMetric other) {
37+
this.assignment += other.assignment;
38+
this.branch += other.branch;
39+
this.condition += other.condition;
40+
}
41+
42+
public double getAbc() {
43+
return Math.sqrt(assignment * assignment + branch * branch + condition * condition);
44+
}
45+
46+
@Override
47+
public boolean equals(Object o) {
48+
if (o == null || getClass() != o.getClass()) return false;
49+
50+
AbcMetric abcMetric = (AbcMetric) o;
51+
return assignment == abcMetric.assignment && branch == abcMetric.branch && condition == abcMetric.condition;
52+
}
53+
54+
@Override
55+
public int hashCode() {
56+
int result = Long.hashCode(assignment);
57+
result = 31 * result + Long.hashCode(branch);
58+
result = 31 * result + Long.hashCode(condition);
59+
return result;
60+
}
61+
}

src/main/java/org/example/util/AverageHolder.java renamed to src/main/java/org/example/model/AverageHolder.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.example.util;
1+
package org.example.model;
22

33
public class AverageHolder {
44
private final Object lock = new Object();
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package org.example.model;
2+
3+
public class Statistic {
4+
5+
private final AverageHolder fieldAverageHolder = new AverageHolder();
6+
private final AverageHolder averageInheritanceDepthHolder = new AverageHolder();
7+
private final AverageHolder averageOverriddenCountHolder = new AverageHolder();
8+
private int maxInheritanceDepth = 0;
9+
private final AbcMetric abcMetric = new AbcMetric(0, 0, 0);
10+
11+
public void updateFieldStatistic(Integer fieldCount) {
12+
fieldAverageHolder.updateAverage(fieldCount);
13+
}
14+
15+
public void writeDepth(int depth) {
16+
averageInheritanceDepthHolder.updateAverage(depth);
17+
maxInheritanceDepth = Math.max(maxInheritanceDepth, depth);
18+
}
19+
20+
public void writeOverriddenCount(int overriddenCount) {
21+
averageOverriddenCountHolder.updateAverage(overriddenCount);
22+
}
23+
24+
public void printStatistic() {
25+
System.out.println("Average field count: " + fieldAverageHolder.getAverage());
26+
System.out.println("ABC: " + abcMetric.getAbc());
27+
System.out.println("Inheritance max depth: " + maxInheritanceDepth);
28+
System.out.println("Inheritance average depth: " + averageInheritanceDepthHolder.getAverage());
29+
System.out.println("Average override count: " + averageOverriddenCountHolder.getAverage());
30+
}
31+
32+
public AbcMetric getAbcMetric() {
33+
return abcMetric;
34+
}
35+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package org.example.model.hierarchy;
2+
3+
import java.util.Collections;
4+
import java.util.Set;
5+
6+
public class ClassHierarchyNode {
7+
8+
private final String name;
9+
private final String superName;
10+
private final Set<String> methods; // name + descriptor
11+
12+
private Integer cachedDepth = null;
13+
14+
public ClassHierarchyNode(String name, String superName, Set<String> methods) {
15+
if (name == null || superName == null) {
16+
throw new IllegalArgumentException("Name and superName must not be null");
17+
}
18+
this.name = name;
19+
this.superName = superName;
20+
this.methods = methods != null ? methods : Collections.emptySet();
21+
}
22+
23+
public String getName() {
24+
return name;
25+
}
26+
27+
public String getSuperName() {
28+
return superName;
29+
}
30+
31+
public boolean hasMethod(String signature) {
32+
return methods.contains(signature);
33+
}
34+
35+
public void setCachedDepth(int depth) {
36+
this.cachedDepth = depth;
37+
}
38+
39+
public Integer getCachedDepth() {
40+
return cachedDepth;
41+
}
42+
43+
@Override
44+
public boolean equals(Object o) {
45+
if (o == null || getClass() != o.getClass()) return false;
46+
47+
ClassHierarchyNode that = (ClassHierarchyNode) o;
48+
return name.equals(that.name);
49+
}
50+
51+
@Override
52+
public int hashCode() {
53+
return name.hashCode();
54+
}
55+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package org.example.util;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
import java.util.HashMap;
6+
import java.util.HashSet;
7+
import java.util.Map;
8+
import java.util.Set;
9+
import org.example.model.hierarchy.ClassHierarchyNode;
10+
import org.example.util.provider.ClassProvider;
11+
import org.objectweb.asm.ClassReader;
12+
import org.objectweb.asm.ClassVisitor;
13+
import org.objectweb.asm.MethodVisitor;
14+
import org.objectweb.asm.Opcodes;
15+
16+
public class ClassRepository {
17+
18+
private static final String OBJECT_CLASS_NAME = "java/lang/Object";
19+
20+
private final Map<String, ClassHierarchyNode> cache = new HashMap<>();
21+
private final ClassProvider classProvider;
22+
23+
public ClassRepository(ClassProvider classProvider) {
24+
this.classProvider = classProvider;
25+
}
26+
27+
public ClassHierarchyNode getNode(String className) {
28+
if (className == null) {
29+
throw new IllegalArgumentException("Class name must not be null");
30+
}
31+
32+
if (cache.containsKey(className)) {
33+
return cache.get(className);
34+
}
35+
36+
try {
37+
ClassHierarchyNode node = loadNode(className);
38+
cache.put(className, node);
39+
return node;
40+
} catch (IOException e) {
41+
System.err.println("WARN: Unable to load class " + className);
42+
cache.put(className, null); // remember failed attempt
43+
return null;
44+
}
45+
}
46+
47+
public int getDepth(String className) {
48+
if (OBJECT_CLASS_NAME.equals(className) || className == null) {
49+
return 0;
50+
}
51+
52+
ClassHierarchyNode node = getNode(className);
53+
54+
if (node == null) {
55+
return 0;
56+
}
57+
58+
if (node.getCachedDepth() != null) {
59+
return node.getCachedDepth();
60+
}
61+
62+
int depth = 1 + getDepth(node.getSuperName());
63+
64+
node.setCachedDepth(depth);
65+
return depth;
66+
}
67+
68+
public boolean isOverridden(String className, String methodSignature) {
69+
ClassHierarchyNode node = getNode(className);
70+
71+
if (node == null) {
72+
return false;
73+
}
74+
75+
return checkParentHierarchy(node.getSuperName(), methodSignature);
76+
}
77+
78+
private boolean checkParentHierarchy(String currentClassName, String methodSignature) {
79+
if (currentClassName == null || OBJECT_CLASS_NAME.equals(currentClassName)) {
80+
return false;
81+
}
82+
83+
ClassHierarchyNode node = getNode(currentClassName);
84+
if (node == null) {
85+
return false;
86+
}
87+
88+
if (node.hasMethod(methodSignature)) {
89+
return true;
90+
}
91+
92+
return checkParentHierarchy(node.getSuperName(), methodSignature);
93+
}
94+
95+
private ClassHierarchyNode loadNode(String className) throws IOException {
96+
try (InputStream in = classProvider.getClassInputStream(className)) {
97+
if (in == null) return null;
98+
99+
ClassReader cr = new ClassReader(in);
100+
Set<String> methods = new HashSet<>();
101+
final String[] superNameHolder = new String[1];
102+
103+
// only reading superclass and method signature
104+
// flags needed for performance
105+
cr.accept(new ClassVisitor(Opcodes.ASM9) {
106+
107+
@Override
108+
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
109+
superNameHolder[0] = superName;
110+
}
111+
112+
@Override
113+
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
114+
boolean isPrivateOrStatic = (access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0;
115+
boolean isConstructor = name.startsWith("<");
116+
117+
if (!isPrivateOrStatic && !isConstructor) {
118+
methods.add(name + descriptor);
119+
}
120+
return null; // not entering method body
121+
}
122+
}, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
123+
124+
return new ClassHierarchyNode(className, superNameHolder[0], methods);
125+
}
126+
}
127+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.example.util.provider;
2+
3+
import java.io.IOException;
4+
import java.io.InputStream;
5+
6+
@FunctionalInterface
7+
public interface ClassProvider {
8+
9+
/**
10+
* @param internalName class name in ASM format, for example "java/lang/String"
11+
* @return InputStream or null
12+
*/
13+
InputStream getClassInputStream(String internalName) throws IOException;
14+
}

0 commit comments

Comments
 (0)