From 4f49a19921f9936daac8693f54d8fd5f193a5b27 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Fri, 31 Oct 2025 10:48:48 +0000 Subject: [PATCH 1/2] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e3d9c8a..2486cde 100644 --- a/README.md +++ b/README.md @@ -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 файла From 280f4413d2f5f599c99bfedc761c3d66a4e32681 Mon Sep 17 00:00:00 2001 From: "a.k.lysenko" Date: Mon, 1 Dec 2025 16:47:56 +0300 Subject: [PATCH 2/2] done lab --- build.gradle.kts | 9 +- src/main/java/org/example/Main.java | 67 ++++++++++ .../ObjectMapperConfiguration.java | 12 ++ .../java/org/example/model/ABCMetric.java | 11 ++ .../java/org/example/model/AnalyzeResult.java | 12 ++ .../java/org/example/model/ClassMetrics.java | 18 +++ .../org/example/model/JarFileMetrics.java | 123 ++++++++++++++++++ .../java/org/example/model/MethodMetrics.java | 25 ++++ .../example/service/ClassAnalyzerService.java | 59 +++++++++ .../service/MethodAnalyzerService.java | 56 ++++++++ .../service/util/ObjectMapperUtil.java | 15 +++ 11 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/example/Main.java create mode 100644 src/main/java/org/example/configuration/ObjectMapperConfiguration.java create mode 100644 src/main/java/org/example/model/ABCMetric.java create mode 100644 src/main/java/org/example/model/AnalyzeResult.java create mode 100644 src/main/java/org/example/model/ClassMetrics.java create mode 100644 src/main/java/org/example/model/JarFileMetrics.java create mode 100644 src/main/java/org/example/model/MethodMetrics.java create mode 100644 src/main/java/org/example/service/ClassAnalyzerService.java create mode 100644 src/main/java/org/example/service/MethodAnalyzerService.java create mode 100644 src/main/java/org/example/service/util/ObjectMapperUtil.java diff --git a/build.gradle.kts b/build.gradle.kts index 2b92afd..e77fd18 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,7 @@ plugins { - id("java") + application + java + id("io.freefair.lombok") version "8.13.1" } group = "org.example" @@ -10,11 +12,16 @@ repositories { } dependencies { + implementation("com.fasterxml.jackson.core:jackson-databind:2.0.1") + implementation("org.slf4j:slf4j-api:2.0.17") + implementation("ch.qos.logback:logback-classic:1.5.18") implementation("org.ow2.asm:asm:9.5") implementation("org.ow2.asm:asm-tree:9.5") implementation("org.ow2.asm:asm-analysis:9.5") implementation("org.ow2.asm:asm-util:9.5") +} +dependencies { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/src/main/java/org/example/Main.java b/src/main/java/org/example/Main.java new file mode 100644 index 0000000..e0c9ca2 --- /dev/null +++ b/src/main/java/org/example/Main.java @@ -0,0 +1,67 @@ +package org.example; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.example.configuration.ObjectMapperConfiguration; +import org.example.model.AnalyzeResult; +import org.example.model.JarFileMetrics; +import org.example.service.ClassAnalyzerService; +import org.example.service.util.ObjectMapperUtil; +import org.objectweb.asm.ClassReader; + +import java.io.IOException; +import java.util.jar.JarFile; + +@Slf4j +public class Main { + public static void main(String[] args) { + var jarPath = "src/main/resources/simple.jar"; + + if (args.length != 1) { + log.error("Wrong arguments. First should be a jar file to analyze"); + System.exit(1); + } + + var objectMapper = ObjectMapperConfiguration.getObjectMapper(); + jarPath = args[0]; + + var finalResult = new AnalyzeResult(); + + processJarFile(jarPath, finalResult, objectMapper); + + log.info("Final result is: {}", objectMapper.writeValueAsString(finalResult)); + } + + @SneakyThrows + private static void processJarFile(String jarFilePath, AnalyzeResult finalResult, ObjectMapperUtil objectMapperUtil) { + var jarMetrics = new JarFileMetrics(); + try (var jarFile = new JarFile(jarFilePath)) { + jarFile.stream() + .filter(it -> it.getName().endsWith(".class")) + .forEach(it -> { + ClassReader reader; + try { + reader = new ClassReader(jarFile.getInputStream(it)); + } catch (IOException e) { + throw new RuntimeException(e); + } + reader.accept(new ClassAnalyzerService(jarMetrics), 0); + }); + } + + jarMetrics.calculateMetrics(); + log.info("Метрики по jar архиву: {}", objectMapperUtil.writeValueAsString(jarMetrics)); + + log.info("Максимальная глубина наследования: {}", jarMetrics.getMaxInheritanceDepth()); + log.info("Средняя глубина наследования: {}", jarMetrics.getAvgInheritanceDepth()); + log.info("Средняя метрика ABC: {}", jarMetrics.getAvgAbcMetric()); + log.info("Среднее количество переопределенных методов: {}", jarMetrics.getAvgOverriddenMethods()); + log.info("Среднее количество полей в классе: {}", jarMetrics.getAvgFieldCount()); + + finalResult.setMaxInheritanceDepth(jarMetrics.getMaxInheritanceDepth()); + finalResult.setAverageExtendsLength(jarMetrics.getAvgInheritanceDepth()); + finalResult.setAverageOverridesMethods(jarMetrics.getAvgOverriddenMethods()); + finalResult.setAverageFieldsInClass(jarMetrics.getAvgFieldCount()); + finalResult.setAverageAbcMetric(jarMetrics.getAvgAbcMetric()); + } +} diff --git a/src/main/java/org/example/configuration/ObjectMapperConfiguration.java b/src/main/java/org/example/configuration/ObjectMapperConfiguration.java new file mode 100644 index 0000000..0e1ff1c --- /dev/null +++ b/src/main/java/org/example/configuration/ObjectMapperConfiguration.java @@ -0,0 +1,12 @@ +package org.example.configuration; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.experimental.UtilityClass; +import org.example.service.util.ObjectMapperUtil; + +@UtilityClass +public class ObjectMapperConfiguration { + public static ObjectMapperUtil getObjectMapper() { + return new ObjectMapperUtil(new ObjectMapper()); + } +} diff --git a/src/main/java/org/example/model/ABCMetric.java b/src/main/java/org/example/model/ABCMetric.java new file mode 100644 index 0000000..9957b0c --- /dev/null +++ b/src/main/java/org/example/model/ABCMetric.java @@ -0,0 +1,11 @@ +package org.example.model; + +import lombok.Data; + +@Data +public class ABCMetric { + private long assignments; + private long branches; + private long conditions; + private double metricValue; +} diff --git a/src/main/java/org/example/model/AnalyzeResult.java b/src/main/java/org/example/model/AnalyzeResult.java new file mode 100644 index 0000000..7f46978 --- /dev/null +++ b/src/main/java/org/example/model/AnalyzeResult.java @@ -0,0 +1,12 @@ +package org.example.model; + +import lombok.Data; + +@Data +public class AnalyzeResult { + private int maxInheritanceDepth; + private double averageExtendsLength; + private double averageOverridesMethods; + private double averageFieldsInClass; + private double averageAbcMetric; +} diff --git a/src/main/java/org/example/model/ClassMetrics.java b/src/main/java/org/example/model/ClassMetrics.java new file mode 100644 index 0000000..a48a603 --- /dev/null +++ b/src/main/java/org/example/model/ClassMetrics.java @@ -0,0 +1,18 @@ +package org.example.model; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ClassMetrics { + private String name; + private String parentClassName; + private int fieldCount; + private List methodMetrics = new ArrayList<>(); + + public void addMetrics(MethodMetrics methodMetrics) { + this.methodMetrics.add(methodMetrics); + } +} diff --git a/src/main/java/org/example/model/JarFileMetrics.java b/src/main/java/org/example/model/JarFileMetrics.java new file mode 100644 index 0000000..f9ff7c3 --- /dev/null +++ b/src/main/java/org/example/model/JarFileMetrics.java @@ -0,0 +1,123 @@ +package org.example.model; + +import lombok.Data; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Data +public class JarFileMetrics { + private Map classNameToParentName; + private Map> classNameToMethodSignatures; + private Map classNameToMaxDepth; + private Map classNameToOverriddenMethodsCount; + private Map classNameToClassMetrics; + + public JarFileMetrics() { + this.classNameToParentName = new HashMap<>(); + this.classNameToMethodSignatures = new HashMap<>(); + this.classNameToMaxDepth = new HashMap<>(); + this.classNameToOverriddenMethodsCount = new HashMap<>(); + this.classNameToClassMetrics = new HashMap<>(); + } + + public void addClassNameToParentName(String className, String classParentName) { + this.classNameToParentName.put(className, classParentName); + } + + public void addClassNameToMethodsSignatures(String className, List methodSignatures) { + this.classNameToMethodSignatures.put(className, methodSignatures); + } + + public void addClassNameToClassMetrics(ClassMetrics classMetrics) { + this.classNameToClassMetrics.put(classMetrics.getName(), classMetrics); + } + + public void calculateMetrics() { + for (var className : classNameToParentName.keySet()) { + calculateMaxDepth(className); + } + + for (var className : classNameToMethodSignatures.keySet()) { + var methods = classNameToMethodSignatures.get(className); + countOverriddenMethods(className, methods); + } + } + + public int getMaxInheritanceDepth() { + return classNameToMaxDepth.values().stream() + .max(Integer::compareTo) + .orElse(0); + } + + public double getAvgInheritanceDepth() { + return classNameToMaxDepth.values().stream() + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + } + + public double getAvgAbcMetric() { + return classNameToClassMetrics.values().stream() + .flatMap(classMetrics -> classMetrics.getMethodMetrics().stream()) + .map(MethodMetrics::getAbcMetric) + .mapToDouble(ABCMetric::getMetricValue) + .average() + .orElse(0.0); + } + + public double getAvgOverriddenMethods() { + return classNameToOverriddenMethodsCount.values().stream() + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + } + + public double getAvgFieldCount() { + return classNameToClassMetrics.values().stream() + .mapToInt(ClassMetrics::getFieldCount) + .average() + .orElse(0.0); + } + + private void calculateMaxDepth(String className) { + var depth = 0; + var currentClassName = className; + + while (classNameToParentName.containsKey(currentClassName)) { + depth++; + currentClassName = classNameToParentName.get(currentClassName); + } + + classNameToMaxDepth.put(className, depth); + } + + private void countOverriddenMethods(String className, List methods) { + if (!classNameToParentName.containsKey(className)) { + classNameToOverriddenMethodsCount.put(className, 0); + return; + } + + var parent = classNameToParentName.get(className); + Set inheritedMethods = new HashSet<>(); + + // Собираем все методы всех родительских классов + while (parent != null) { + var parentMethods = classNameToMethodSignatures.get(parent); + if (parentMethods != null) { + inheritedMethods.addAll(parentMethods); + } + parent = classNameToParentName.get(parent); + } + + // Считаем переопределения + var overriddenCount = methods.stream() + .filter(inheritedMethods::contains) + .count(); + + classNameToOverriddenMethodsCount.put(className, (int) overriddenCount); + } +} diff --git a/src/main/java/org/example/model/MethodMetrics.java b/src/main/java/org/example/model/MethodMetrics.java new file mode 100644 index 0000000..dd26307 --- /dev/null +++ b/src/main/java/org/example/model/MethodMetrics.java @@ -0,0 +1,25 @@ +package org.example.model; + +import lombok.Data; + +@Data +public class MethodMetrics { + private String signature; + private ABCMetric abcMetric = new ABCMetric(); + + public void setAssignments(long assignments) { + this.getAbcMetric().setAssignments(assignments); + } + + public void setBranches(long branches) { + this.getAbcMetric().setBranches(branches); + } + + public void setConditions(long conditions) { + this.getAbcMetric().setConditions(conditions); + } + + public void setMetricValue(double metricValue) { + this.getAbcMetric().setMetricValue(metricValue); + } +} diff --git a/src/main/java/org/example/service/ClassAnalyzerService.java b/src/main/java/org/example/service/ClassAnalyzerService.java new file mode 100644 index 0000000..28622e5 --- /dev/null +++ b/src/main/java/org/example/service/ClassAnalyzerService.java @@ -0,0 +1,59 @@ +package org.example.service; + +import lombok.extern.slf4j.Slf4j; +import org.example.model.ClassMetrics; +import org.example.model.JarFileMetrics; +import org.example.model.MethodMetrics; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * @see org.objectweb.asm.ClassVisitor docs + */ +@Slf4j +public class ClassAnalyzerService extends ClassVisitor { + private final ClassMetrics classMetrics; + private final JarFileMetrics jarFileMetrics; + + public ClassAnalyzerService(JarFileMetrics jarFileMetrics) { + super(Opcodes.ASM9); + this.jarFileMetrics = jarFileMetrics; + this.classMetrics = new ClassMetrics(); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + classMetrics.setName(name); + classMetrics.setParentClassName(superName); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + classMetrics.setFieldCount(classMetrics.getFieldCount() + 1); + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + var methodMetrics = new MethodMetrics(); + methodMetrics.setSignature(signature); + classMetrics.addMetrics(methodMetrics); + return new MethodAnalyzerService(methodMetrics); + } + + @Override + public void visitEnd() { + jarFileMetrics.addClassNameToParentName(classMetrics.getName(), classMetrics.getParentClassName()); + jarFileMetrics.addClassNameToMethodsSignatures( + classMetrics.getName(), + classMetrics.getMethodMetrics().stream() + .map(MethodMetrics::getSignature) + .toList() + ); + jarFileMetrics.addClassNameToClassMetrics(classMetrics); + super.visitEnd(); + } +} diff --git a/src/main/java/org/example/service/MethodAnalyzerService.java b/src/main/java/org/example/service/MethodAnalyzerService.java new file mode 100644 index 0000000..9526f99 --- /dev/null +++ b/src/main/java/org/example/service/MethodAnalyzerService.java @@ -0,0 +1,56 @@ +package org.example.service; + +import org.example.model.MethodMetrics; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.EnumSet; +import java.util.Set; + +/** + * @see org.objectweb.asm.ClassVisitor docs + */ +public class MethodAnalyzerService extends MethodVisitor { + + private final MethodMetrics methodMetrics; + + public MethodAnalyzerService(MethodMetrics methodMetrics) { + super(Opcodes.ASM9); + this.methodMetrics = methodMetrics; + } + + // Visits a local variable instruction. + @Override + public void visitVarInsn(int opcode, int varIndex) { + if (opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE) { + methodMetrics.setAssignments(methodMetrics.getAbcMetric().getAssignments() + 1); + } + super.visitVarInsn(opcode, varIndex); + } + + // Visits a jump instruction. + @Override + public void visitJumpInsn(int opcode, Label label) { + methodMetrics.setBranches(methodMetrics.getAbcMetric().getBranches() + 1); + if ( + (opcode >= Opcodes.IFEQ && opcode <= Opcodes.IF_ACMPNE) || + opcode == Opcodes.IFNONNULL || + opcode == Opcodes.IFNULL || + opcode == Opcodes.GOTO || + opcode == Opcodes.JSR + ) { + methodMetrics.setConditions(methodMetrics.getAbcMetric().getConditions() + 1); + } + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitEnd() { + var a = methodMetrics.getAbcMetric().getAssignments(); + var b = methodMetrics.getAbcMetric().getBranches(); + var c = methodMetrics.getAbcMetric().getConditions(); + methodMetrics.setMetricValue(Math.sqrt(a * a + b * b + c * c)); + super.visitEnd(); + } +} diff --git a/src/main/java/org/example/service/util/ObjectMapperUtil.java b/src/main/java/org/example/service/util/ObjectMapperUtil.java new file mode 100644 index 0000000..8990d36 --- /dev/null +++ b/src/main/java/org/example/service/util/ObjectMapperUtil.java @@ -0,0 +1,15 @@ +package org.example.service.util; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; + +@RequiredArgsConstructor +public class ObjectMapperUtil { + private final ObjectMapper objectMapper; + + @SneakyThrows + public String writeValueAsString(T value) { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value); + } +}