Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/9A22t-SS)
Разработать standalone приложение, которое имеет следующие возможности:

Принимает на вход проект в виде .jar файла
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies {
implementation("org.ow2.asm:asm-tree:9.5")
implementation("org.ow2.asm:asm-analysis:9.5")
implementation("org.ow2.asm:asm-util:9.5")
implementation("com.fasterxml.jackson.core:jackson-databind:2.20.1")

testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
Expand Down
1 change: 1 addition & 0 deletions results/metrics.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"classCount":1852,"maxInheritanceDepth":6,"averageInheritanceDepth":1.1981641468682505,"abcAssignmentCount":9100,"abcAverageAssignmentsPerMethod":0.823231409444545,"averageOverriddenMethodsPerClass":1.82451403887689,"averageFieldCountPerClass":1.9989200863930885}
29 changes: 0 additions & 29 deletions src/main/java/org/example/Example.java

This file was deleted.

36 changes: 36 additions & 0 deletions src/main/java/org/example/MetricsApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.example;

import org.example.calculator.JarMetricsCalculator;
import org.example.dto.ProjectMetrics;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;

public final class MetricsApp {

private static final String INPUT_JAR_PATH = "src/main/resources/guava.jar";

private static final String OUTPUT_JSON_PATH = "results/metrics.json";

private MetricsApp() {
}

public static void main(String[] args) throws IOException {
Path jarPath = Path.of(INPUT_JAR_PATH);
Path outputPath = Path.of(OUTPUT_JSON_PATH);

JarMetricsCalculator calculator = new JarMetricsCalculator(jarPath);
ProjectMetrics metrics = calculator.analyze();

System.out.println(metrics.toPrettyJson());

String json = metrics.toJson();
if (outputPath.getParent() != null) {
Files.createDirectories(outputPath.getParent());
}
Files.writeString(outputPath, json, StandardCharsets.UTF_8);

}
}
251 changes: 251 additions & 0 deletions src/main/java/org/example/calculator/JarMetricsCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
package org.example.calculator;

import org.example.dto.ProjectMetrics;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public final class JarMetricsCalculator {

private final Path jarPath;

public JarMetricsCalculator(Path jarPath) {
this.jarPath = Objects.requireNonNull(jarPath);
}

public ProjectMetrics analyze() throws IOException {
Map<String, ClassInfo> classes = loadClasses();

if (classes.isEmpty()) {
return new ProjectMetrics(
0,
0,
0.0,
0,
0.0,
0.0,
0.0
);
}

Map<String, Integer> depthCache = new HashMap<>();

int classCount = 0;
double inheritanceDepthSum = 0.0;
int maxInheritanceDepth = 0;

int totalFields = 0;
int totalOverriddenMethods = 0;

int totalMethodsForAbc = 0;
int totalAssignmentsForAbc = 0;

for (ClassInfo classInfo : classes.values()) {
if (classInfo.isInterface) {
continue;
}

classCount++;

int depth = computeInheritanceDepth(classInfo.name, classes, depthCache);
inheritanceDepthSum += depth;
if (depth > maxInheritanceDepth) {
maxInheritanceDepth = depth;
}

totalFields += classInfo.fieldCount;

int overriddenHere = countOverriddenMethods(classInfo, classes);
totalOverriddenMethods += overriddenHere;

for (MethodNode methodNode : classInfo.methods) {
if (isMethodForAbc(methodNode)) {
int assignments = countAssignmentsInMethod(methodNode);
totalAssignmentsForAbc += assignments;
totalMethodsForAbc++;
}
}
}

double averageInheritanceDepth = classCount == 0 ? 0.0 : inheritanceDepthSum / classCount;
double averageFieldCountPerClass = classCount == 0 ? 0.0 : (double) totalFields / classCount;
double averageOverriddenMethodsPerClass = classCount == 0 ? 0.0 : (double) totalOverriddenMethods / classCount;
double abcAverageAssignmentsPerMethod = totalMethodsForAbc == 0 ? 0.0 : (double) totalAssignmentsForAbc / totalMethodsForAbc;

return new ProjectMetrics(
classCount,
maxInheritanceDepth,
averageInheritanceDepth,
totalAssignmentsForAbc,
abcAverageAssignmentsPerMethod,
averageOverriddenMethodsPerClass,
averageFieldCountPerClass
);
}

// Все классы в ClassInfo
private Map<String, ClassInfo> loadClasses() throws IOException {
Map<String, ClassInfo> result = new HashMap<>();

try (JarFile jarFile = new JarFile(jarPath.toFile())) {
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
if (!entry.getName().endsWith(".class")) {
continue;
}

try (InputStream is = jarFile.getInputStream(entry)) {
ClassReader classReader = new ClassReader(is);
ClassNode classNode = new ClassNode();
classReader.accept(classNode, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);

boolean isInterface = (classNode.access & Opcodes.ACC_INTERFACE) != 0;

List<MethodNode> methods = new ArrayList<>();
if (classNode.methods != null) {
for (Object m : classNode.methods) {
methods.add((MethodNode) m);
}
}

int fieldCount = classNode.fields == null ? 0 : classNode.fields.size();

ClassInfo info = new ClassInfo(
classNode.name,
classNode.superName,
isInterface,
fieldCount,
methods
);
result.put(classNode.name, info);
}
}
}

return result;
}

private int computeInheritanceDepth(
String className,
Map<String, ClassInfo> classes,
Map<String, Integer> depthCache
) {
if (className == null) {
return 0;
}
Integer cached = depthCache.get(className);
if (cached != null) {
return cached;
}

ClassInfo info = classes.get(className);
if (info == null) {
depthCache.put(className, 0);
return 0;
}

if (info.superName == null || "java/lang/Object".equals(info.superName)) {
depthCache.put(className, 0);
return 0;
}

int depth = 1 + computeInheritanceDepth(info.superName, classes, depthCache);
depthCache.put(className, depth);
return depth;
}

private int countOverriddenMethods(ClassInfo classInfo, Map<String, ClassInfo> classes) {
Set<String> superMethodSignatures = new HashSet<>();

String superName = classInfo.superName;
while (superName != null) {
ClassInfo superInfo = classes.get(superName);
if (superInfo == null) {
break;
}

for (MethodNode m : superInfo.methods) {
if (isConstructorOrClassInitializer(m)) {
continue;
}
String signature = m.name + m.desc;
superMethodSignatures.add(signature);
}

superName = superInfo.superName;
}

int overridden = 0;
for (MethodNode m : classInfo.methods) {
if (isConstructorOrClassInitializer(m)) {
continue;
}
String signature = m.name + m.desc;
if (superMethodSignatures.contains(signature)) {
overridden++;
}
}

return overridden;
}

private boolean isMethodForAbc(MethodNode methodNode) {
if ((methodNode.access & Opcodes.ACC_ABSTRACT) != 0) {
return false;
}
if ((methodNode.access & Opcodes.ACC_SYNTHETIC) != 0) {
return false;
}
return !isConstructorOrClassInitializer(methodNode);
}

private boolean isConstructorOrClassInitializer(MethodNode methodNode) {
String name = methodNode.name;
return "<init>".equals(name) || "<clinit>".equals(name);
}

private int countAssignmentsInMethod(MethodNode methodNode) {
int count = 0;
for (AbstractInsnNode insn = methodNode.instructions.getFirst();
insn != null;
insn = insn.getNext()) {

int opcode = insn.getOpcode();
if (opcode < 0) {
continue;
}

if (isStoreOpcode(opcode) || opcode == Opcodes.IINC) {
count++;
}
}
return count;
}

private boolean isStoreOpcode(int opcode) {
return opcode == Opcodes.ISTORE
|| opcode == Opcodes.LSTORE
|| opcode == Opcodes.FSTORE
|| opcode == Opcodes.DSTORE
|| opcode == Opcodes.ASTORE;
}

private record ClassInfo(
String name,
String superName,
boolean isInterface,
int fieldCount,
List<MethodNode> methods
) {
}
}
Loading