From a2b386b4815953c8b12c7de6fc9863a85d9df15b Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:27: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 a753280617a12ee25dc68f1ff203fe5c56bec20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BB=D0=B0=D0=B3=D0=BE=D1=80=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=9B=D1=8E=D0=B4=D0=BC=D0=B8=D0=BB=D0=B0?= Date: Thu, 4 Dec 2025 17:18:16 +0300 Subject: [PATCH 2/2] =?UTF-8?q?LAB-3=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20standalone=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=D0=BB=D0=BE=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 6 + result.json | 7 ++ src/main/java/org/example/Example.java | 92 ++++++++++++--- src/main/java/org/example/dto/Result.java | 16 +++ .../java/org/example/model/ClassMetrics.java | 41 +++++++ .../java/org/example/model/JarMetrics.java | 105 ++++++++++++++++++ .../org/example/visitor/ClassPrinter.java | 46 -------- .../example/visitor/CustomClassVisitor.java | 70 ++++++++++++ .../example/visitor/CustomMethodVisitor.java | 81 ++++++++++++++ 9 files changed, 403 insertions(+), 61 deletions(-) create mode 100644 result.json create mode 100644 src/main/java/org/example/dto/Result.java create mode 100644 src/main/java/org/example/model/ClassMetrics.java create mode 100644 src/main/java/org/example/model/JarMetrics.java delete mode 100644 src/main/java/org/example/visitor/ClassPrinter.java create mode 100644 src/main/java/org/example/visitor/CustomClassVisitor.java create mode 100644 src/main/java/org/example/visitor/CustomMethodVisitor.java diff --git a/build.gradle.kts b/build.gradle.kts index 2b92afd..5eb4cef 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,12 @@ dependencies { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + implementation("org.slf4j:slf4j-api:2.0.16") + implementation("ch.qos.logback:logback-classic:1.5.13") + compileOnly("org.projectlombok:lombok:1.18.36") + annotationProcessor("org.projectlombok:lombok:1.18.36") + implementation("com.fasterxml.jackson.core:jackson-databind:2.0.1") } tasks.test { diff --git a/result.json b/result.json new file mode 100644 index 0000000..049e68f --- /dev/null +++ b/result.json @@ -0,0 +1,7 @@ +{ + "maxDepthOfInheritance" : 2, + "averageInheritanceDepth" : 1.5, + "averageOverriddenMethods" : 1.0, + "averageFieldCount" : 0.7, + "averageAbcScore" : 0.7234898064243891 +} \ No newline at end of file diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index 52d0abe..dc5f0e1 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -1,29 +1,91 @@ package org.example; -import org.example.visitor.ClassPrinter; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import lombok.extern.slf4j.Slf4j; +import org.example.dto.Result; +import org.example.model.JarMetrics; +import org.example.visitor.CustomClassVisitor; import org.objectweb.asm.ClassReader; import java.io.IOException; -import java.util.Enumeration; +import java.nio.file.Paths; import java.util.jar.JarEntry; import java.util.jar.JarFile; +@Slf4j public class Example { public static void main(String[] args) throws IOException { -// var printer = new ByteCodePrinter(); -// printer.printBubbleSortBytecode(); - try (JarFile sampleJar = new JarFile("src/main/resources/sample.jar")) { - Enumeration enumeration = sampleJar.entries(); - - while (enumeration.hasMoreElements()) { - JarEntry entry = enumeration.nextElement(); - if (entry.getName().endsWith(".class")) { - ClassPrinter cp = new ClassPrinter(); - ClassReader cr = new ClassReader(sampleJar.getInputStream(entry)); - cr.accept(cp, 0); - } - } + if (args.length != 2) { + log.error("Ошибка чтения аргументов"); + return; } + + var jarPath = args[0]; + var outputFileName = args[1]; + + var result = processJarFile(jarPath); + writeResultAsJson(result, outputFileName); + } + + private static Result processJarFile(String jarFilePath) throws IOException { + var jarMetrics = new JarMetrics(); + + try (var jarFile = new JarFile(jarFilePath)) { + jarFile.stream() + .filter(e -> e.getName().endsWith(".class")) + .forEach(entry -> processClass(entry, jarFile, jarMetrics)); + } + + jarMetrics.calculateMetrics(); + + var stringBuilder = new StringBuilder() + .append("Максимальная глубина наследования: ").append(jarMetrics.getMaxDepthOfInheritance()).append('\n') + .append("Средняя глубина наследования: ").append(jarMetrics.getAverageInheritanceDepth()).append('\n') + .append("Метрика ABC: ").append(jarMetrics.getAverageAbcScore()).append('\n') + .append("Сколичество переопределенных методов: ").append(jarMetrics.getAverageOverriddenMethods()).append('\n') + .append("Среднее количество полей в классе: ").append(jarMetrics.getAverageFieldCount()); + + log.info(stringBuilder.toString()); + + return buildFinalResult(jarMetrics); + } + + private static void processClass(JarEntry entry, JarFile jarFile, JarMetrics metrics) { + try { + var reader = new ClassReader(jarFile.getInputStream(entry)); + reader.accept(new CustomClassVisitor(metrics), 0); + } catch (IOException e) { + throw new RuntimeException("Ошибка чтения файла. Причина: " + entry.getName(), e); + } + } + + private static Result buildFinalResult(JarMetrics metrics) { + return Result.builder() + .maxDepthOfInheritance(metrics.getMaxDepthOfInheritance()) + .averageInheritanceDepth(metrics.getAverageInheritanceDepth()) + .averageOverriddenMethods(metrics.getAverageOverriddenMethods()) + .averageFieldCount(metrics.getAverageFieldCount()) + .averageAbcScore(metrics.getAverageAbcScore()) + .build(); + } + + private static void writeResultAsJson(Object result, String outputFileName) throws IOException { + var mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); + + var file = Paths.get(outputFileName).toFile(); + + if (file.getParentFile() != null && !file.getParentFile().exists()) { + file.getParentFile().mkdirs(); + } + + if (!file.exists()) { + file.createNewFile(); + } + + mapper.writeValue(file, result); + + log.info("Метрики сохранены в файл: {}", file.getAbsolutePath()); } } diff --git a/src/main/java/org/example/dto/Result.java b/src/main/java/org/example/dto/Result.java new file mode 100644 index 0000000..17add31 --- /dev/null +++ b/src/main/java/org/example/dto/Result.java @@ -0,0 +1,16 @@ +package org.example.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder +@ToString +public class Result { + private int maxDepthOfInheritance; + private double averageInheritanceDepth; + private double averageOverriddenMethods; + private double averageFieldCount; + private double averageAbcScore; +} 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..7d5a5a6 --- /dev/null +++ b/src/main/java/org/example/model/ClassMetrics.java @@ -0,0 +1,41 @@ +package org.example.model; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Setter +public class ClassMetrics { + + private String name; + private String parentClassName; + private int fieldCount; + private final List methodMetrics = new ArrayList<>(); + + public void addMetrics(MethodMetrics methodMetrics) { + this.methodMetrics.add(methodMetrics); + } + + @Getter + @Setter + public static class MethodMetrics { + private String signature; + private final ABCMetric abcMetric = new ABCMetric(); + + public void setMetricValue(double metricValue) { + this.getAbcMetric().setMetricValue(metricValue); + } + + @Getter + @Setter + public static class ABCMetric { + private long assignments; + private long branches; + private long conditions; + private double metricValue; + } + } +} diff --git a/src/main/java/org/example/model/JarMetrics.java b/src/main/java/org/example/model/JarMetrics.java new file mode 100644 index 0000000..ae21e09 --- /dev/null +++ b/src/main/java/org/example/model/JarMetrics.java @@ -0,0 +1,105 @@ +package org.example.model; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class JarMetrics { + + private final Map classParent = new HashMap<>(); + private final Map> classMethods = new HashMap<>(); + private final Map classMaxDepth = new HashMap<>(); + private final Map classOverriddenMethods = new HashMap<>(); + private final Map classMetrics = new HashMap<>(); + + public void addParent(String className, String parent) { + classParent.put(className, parent); + } + + public void addMethods(String className, List methodSignatures) { + classMethods.put(className, methodSignatures); + } + + public void addClassMetrics(ClassMetrics classMetrics) { + this.classMetrics.put(classMetrics.getName(), classMetrics); + } + + public void calculateMetrics() { + classParent.keySet().forEach(this::calculateDepth); + classMethods.forEach(this::calculateOverriddenMethods); + } + + public int getMaxDepthOfInheritance() { + return classMaxDepth.values().stream() + .max(Integer::compareTo) + .orElse(0); + } + + public double getAverageInheritanceDepth() { + return classMaxDepth.values().stream() + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + } + + public double getAverageAbcScore() { + return classMetrics.values().stream() + .flatMap(c -> c.getMethodMetrics().stream()) + .mapToDouble(mm -> mm.getAbcMetric().getMetricValue()) + .average() + .orElse(0.0); + } + + public double getAverageOverriddenMethods() { + return classOverriddenMethods.values().stream() + .mapToInt(Integer::intValue) + .average() + .orElse(0.0); + } + + public double getAverageFieldCount() { + return classMetrics.values().stream() + .mapToInt(ClassMetrics::getFieldCount) + .average() + .orElse(0.0); + } + + private void calculateDepth(String className) { + classMaxDepth.put(className, computeDepth(className)); + } + + private int computeDepth(String className) { + int depth = 0; + var current = className; + + while (classParent.containsKey(current)) { + depth++; + current = classParent.get(current); + } + + return depth; + } + + private void calculateOverriddenMethods(String className, List methods) { + var inherited = collectInheritedMethods(className); + var count = methods.stream().filter(inherited::contains).count(); + classOverriddenMethods.put(className, (int) count); + } + + private Set collectInheritedMethods(String className) { + var parent = classParent.get(className); + Set inherited = new HashSet<>(); + + while (parent != null) { + var parentMethods = classMethods.get(parent); + if (parentMethods != null) { + inherited.addAll(parentMethods); + } + parent = classParent.get(parent); + } + + return inherited; + } +} diff --git a/src/main/java/org/example/visitor/ClassPrinter.java b/src/main/java/org/example/visitor/ClassPrinter.java deleted file mode 100644 index ba1ab9f..0000000 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.example.visitor; - -import org.objectweb.asm.*; - -import static org.objectweb.asm.Opcodes.ASM8; - -public class ClassPrinter extends ClassVisitor { - public ClassPrinter() { - super(ASM8); - } - - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - System.out.println("\n" + name + " extends " + superName + " {"); - } - - public void visitSource(String source, String debug) { - } - - public void visitOuterClass(String owner, String name, String desc) { - } - - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - return null; - } - - public void visitAttribute(Attribute attr) { - } - - public void visitInnerClass(String name, String outerName, String innerName, int access) { - } - - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - System.out.println(" " + desc + " " + name); - return null; - } - - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - System.out.println(" " + name + desc); - return null; - } - - public void visitEnd() { - System.out.println("}"); - } -} - diff --git a/src/main/java/org/example/visitor/CustomClassVisitor.java b/src/main/java/org/example/visitor/CustomClassVisitor.java new file mode 100644 index 0000000..3f3cc78 --- /dev/null +++ b/src/main/java/org/example/visitor/CustomClassVisitor.java @@ -0,0 +1,70 @@ +package org.example.visitor; + +import lombok.extern.slf4j.Slf4j; +import org.example.model.ClassMetrics; +import org.example.model.JarMetrics; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +@Slf4j +public class CustomClassVisitor extends ClassVisitor { + + private final ClassMetrics classMetrics = new ClassMetrics(); + private final JarMetrics jarMetrics; + + public CustomClassVisitor(JarMetrics jarMetrics) { + super(Opcodes.ASM9); + this.jarMetrics = jarMetrics; + } + + @Override + public void visit(int version, int access, String className, String signature, String parentName, String[] interfaces) { + classMetrics.setName(className); + classMetrics.setParentClassName(parentName); + super.visit(version, access, className, signature, parentName, interfaces); + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + incrementFieldCount(); + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return createMethodMetrics(signature); + } + + @Override + public void visitEnd() { + saveCollectedMetrics(); + super.visitEnd(); + } + + private void incrementFieldCount() { + classMetrics.setFieldCount(classMetrics.getFieldCount() + 1); + } + + private org.objectweb.asm.MethodVisitor createMethodMetrics(String signature) { + var methodMetrics = new ClassMetrics.MethodMetrics(); + methodMetrics.setSignature(signature); + classMetrics.addMetrics(methodMetrics); + return new CustomMethodVisitor(methodMetrics); + } + + private void saveCollectedMetrics() { + jarMetrics.addParent(classMetrics.getName(), classMetrics.getParentClassName()); + + jarMetrics.addMethods( + classMetrics.getName(), + classMetrics.getMethodMetrics() + .stream() + .map(ClassMetrics.MethodMetrics::getSignature) + .toList() + ); + + jarMetrics.addClassMetrics(classMetrics); + } +} diff --git a/src/main/java/org/example/visitor/CustomMethodVisitor.java b/src/main/java/org/example/visitor/CustomMethodVisitor.java new file mode 100644 index 0000000..31440da --- /dev/null +++ b/src/main/java/org/example/visitor/CustomMethodVisitor.java @@ -0,0 +1,81 @@ +package org.example.visitor; + +import org.example.model.ClassMetrics; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class CustomMethodVisitor extends MethodVisitor { + + private final ClassMetrics.MethodMetrics metrics; + + public CustomMethodVisitor(ClassMetrics.MethodMetrics metrics) { + super(Opcodes.ASM9); + this.metrics = metrics; + } + + @Override + public void visitVarInsn(int opcode, int var) { + if (isStoreInstruction(opcode)) { + incrementAssignments(); + } + super.visitVarInsn(opcode, var); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + incrementBranches(); + + if (isConditionalJump(opcode)) { + incrementConditions(); + } + + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitEnd() { + calculateAbcMetric(); + super.visitEnd(); + } + + private boolean isStoreInstruction(int opcode) { + return opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE; + } + + private boolean isConditionalJump(int opcode) { + return (opcode >= Opcodes.IFEQ && opcode <= Opcodes.IF_ACMPNE) + || opcode == Opcodes.IFNULL + || opcode == Opcodes.IFNONNULL + || opcode == Opcodes.GOTO + || opcode == Opcodes.JSR; + } + + private void incrementAssignments() { + var abc = metrics.getAbcMetric(); + abc.setAssignments(abc.getAssignments() + 1); + } + + private void incrementBranches() { + var abc = metrics.getAbcMetric(); + abc.setBranches(abc.getBranches() + 1); + } + + private void incrementConditions() { + var abc = metrics.getAbcMetric(); + abc.setConditions(abc.getConditions() + 1); + } + + private void calculateAbcMetric() { + var abcMetric = metrics.getAbcMetric(); + var assignments = abcMetric.getAssignments(); + var branches = abcMetric.getBranches(); + var conditions = abcMetric.getConditions(); + + metrics.setMetricValue(Math.sqrt( + assignments * assignments + + branches * branches + + conditions * conditions) + ); + } +}