From c10897e3454be3fc09f7af098783fb18521added Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue, 23 Dec 2025 16:25:17 +0000 Subject: [PATCH 1/3] 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 592051f35727dbcbd3f13ed29ef99d835e3fd5f1 Mon Sep 17 00:00:00 2001 From: Vladislav Bandurin Date: Wed, 14 Jan 2026 19:10:59 +0300 Subject: [PATCH 2/3] add lab3 --- build.gradle.kts | 2 + output.json | 8 ++ src/main/java/analyzer/ClassInfo.java | 18 +++ src/main/java/analyzer/JarAnalyzer.java | 106 ++++++++++++++++++ src/main/java/analyzer/MethodSignature.java | 18 +++ src/main/java/metrics/AbcCalculator.java | 18 +++ .../java/metrics/FieldMetricsCalculator.java | 20 ++++ .../metrics/InheritanceDepthCalculator.java | 42 +++++++ src/main/java/metrics/InheritanceStats.java | 4 + .../metrics/OverrideMethodsCalculator.java | 62 ++++++++++ src/main/java/output/MetricsResult.java | 30 +++++ src/main/java/visitor/AbcMethodVisitor.java | 24 ++++ .../java/visitor/MetricsClassVisitor.java | 77 +++++++++++++ 13 files changed, 429 insertions(+) create mode 100644 output.json create mode 100644 src/main/java/analyzer/ClassInfo.java create mode 100644 src/main/java/analyzer/JarAnalyzer.java create mode 100644 src/main/java/analyzer/MethodSignature.java create mode 100644 src/main/java/metrics/AbcCalculator.java create mode 100644 src/main/java/metrics/FieldMetricsCalculator.java create mode 100644 src/main/java/metrics/InheritanceDepthCalculator.java create mode 100644 src/main/java/metrics/InheritanceStats.java create mode 100644 src/main/java/metrics/OverrideMethodsCalculator.java create mode 100644 src/main/java/output/MetricsResult.java create mode 100644 src/main/java/visitor/AbcMethodVisitor.java create mode 100644 src/main/java/visitor/MetricsClassVisitor.java diff --git a/build.gradle.kts b/build.gradle.kts index 2b92afd..f8a3e78 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,8 @@ dependencies { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + implementation("com.fasterxml.jackson.core:jackson-databind:2.17.0") } tasks.test { diff --git a/output.json b/output.json new file mode 100644 index 0000000..83562e5 --- /dev/null +++ b/output.json @@ -0,0 +1,8 @@ +{ + "classesAnalyzed" : 330, + "abcAssignments" : 1655, + "maxInheritanceDepth" : 5, + "avgInheritanceDepth" : 1.4393939393939394, + "avgOverriddenMethods" : 2.2790697674418605, + "avgFieldsPerClass" : 2.609090909090909 +} \ No newline at end of file diff --git a/src/main/java/analyzer/ClassInfo.java b/src/main/java/analyzer/ClassInfo.java new file mode 100644 index 0000000..dceeaf0 --- /dev/null +++ b/src/main/java/analyzer/ClassInfo.java @@ -0,0 +1,18 @@ +package analyzer; + +import java.util.HashSet; +import java.util.Set; + +public class ClassInfo { + public final String name; + public final String superName; + public final Set methods = new HashSet<>(); + + public int fieldsCount = 0; + public int assignmentCount = 0; + + public ClassInfo(String name, String superName) { + this.name = name; + this.superName = superName; + } +} diff --git a/src/main/java/analyzer/JarAnalyzer.java b/src/main/java/analyzer/JarAnalyzer.java new file mode 100644 index 0000000..f1a251f --- /dev/null +++ b/src/main/java/analyzer/JarAnalyzer.java @@ -0,0 +1,106 @@ +package analyzer; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.objectweb.asm.ClassReader; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; + +import metrics.AbcCalculator; +import metrics.FieldMetricsCalculator; +import metrics.InheritanceDepthCalculator; +import metrics.InheritanceStats; +import metrics.OverrideMethodsCalculator; +import output.MetricsResult; +import visitor.MetricsClassVisitor; + +public class JarAnalyzer { + + public static void main(String[] args) throws IOException { + + if (args.length < 2) { + System.err.println("Expected 2 arguments: "); + System.exit(1); + } + + String jarPath = args[0]; + String jsonOutputPath = args[1]; + + Map classes = new HashMap<>(); + + try (JarFile jarFile = new JarFile(jarPath)) { + Enumeration entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + + if (!entry.getName().endsWith(".class")) { + continue; + } + + try (InputStream is = jarFile.getInputStream(entry)) { + ClassReader reader = new ClassReader(is); + MetricsClassVisitor visitor = new MetricsClassVisitor(); + reader.accept(visitor, ClassReader.SKIP_DEBUG); + + ClassInfo info = visitor.getClassInfo(); + classes.put(info.name, info); + } + } + } + + + InheritanceStats depthStats = + InheritanceDepthCalculator.calculate(classes); + + int totalAssignments = + AbcCalculator.totalAssignments(classes); + + double avgOverriddenMethods = + OverrideMethodsCalculator.averageOverriddenMethods(classes); + + double avgFields = + FieldMetricsCalculator.averageFields(classes); + + MetricsResult result = new MetricsResult( + classes.size(), + totalAssignments, + depthStats, + avgOverriddenMethods, + avgFields + ); + + printToConsole(result); + + writeJson(result, jsonOutputPath); + } + + private static void printToConsole(MetricsResult r) { + System.out.println("====== Metrics ======"); + System.out.println("Classes analyzed: " + r.classesAnalyzed); + System.out.println("ABC (assignments): " + r.abcAssignments); + System.out.println("Max inheritance depth: " + r.maxInheritanceDepth); + System.out.println("Avg inheritance depth: " + r.avgInheritanceDepth); + System.out.println("Avg overridden methods: " + r.avgOverriddenMethods); + System.out.println("Avg fields per class: " + r.avgFieldsPerClass); + } + + private static void writeJson( + MetricsResult result, + String outputPath + ) throws IOException { + + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + + mapper.writeValue(new File(outputPath), result); + } +} diff --git a/src/main/java/analyzer/MethodSignature.java b/src/main/java/analyzer/MethodSignature.java new file mode 100644 index 0000000..0f0dd88 --- /dev/null +++ b/src/main/java/analyzer/MethodSignature.java @@ -0,0 +1,18 @@ +package analyzer; + +public record MethodSignature(String name, String descriptor) { + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof MethodSignature)) { + return false; + } + MethodSignature that = (MethodSignature) o; + return name.equals(that.name) + && descriptor.equals(that.descriptor); + } + +} diff --git a/src/main/java/metrics/AbcCalculator.java b/src/main/java/metrics/AbcCalculator.java new file mode 100644 index 0000000..bcf1c4b --- /dev/null +++ b/src/main/java/metrics/AbcCalculator.java @@ -0,0 +1,18 @@ +package metrics; + +import java.util.Map; + +import analyzer.ClassInfo; + +public class AbcCalculator { + + public static int totalAssignments(Map classes) { + int sum = 0; + + for (ClassInfo cls : classes.values()) { + sum += cls.assignmentCount; + } + + return sum; + } +} diff --git a/src/main/java/metrics/FieldMetricsCalculator.java b/src/main/java/metrics/FieldMetricsCalculator.java new file mode 100644 index 0000000..8d0eb83 --- /dev/null +++ b/src/main/java/metrics/FieldMetricsCalculator.java @@ -0,0 +1,20 @@ +package metrics; + +import java.util.Map; + +import analyzer.ClassInfo; + +public class FieldMetricsCalculator { + + public static double averageFields(Map classes) { + if (classes.isEmpty()) return 0.0; + + int sum = 0; + + for (ClassInfo cls : classes.values()) { + sum += cls.fieldsCount; + } + + return (double) sum / classes.size(); + } +} diff --git a/src/main/java/metrics/InheritanceDepthCalculator.java b/src/main/java/metrics/InheritanceDepthCalculator.java new file mode 100644 index 0000000..b973ded --- /dev/null +++ b/src/main/java/metrics/InheritanceDepthCalculator.java @@ -0,0 +1,42 @@ +package metrics; + +import java.util.Map; + +import analyzer.ClassInfo; + +public class InheritanceDepthCalculator { + + public static InheritanceStats calculate(Map classes) { + if (classes.isEmpty()) { + return new InheritanceStats(0, 0.0); + } + + int max = 0; + int sum = 0; + + for (ClassInfo cls : classes.values()) { + int depth = calculateDepth(cls, classes); + sum += depth; + max = Math.max(max, depth); + } + + double avg = (double) sum / classes.size(); + + return new InheritanceStats(max, avg); + } + + private static int calculateDepth( + ClassInfo cls, + Map classes + ) { + int depth = 1; + String current = cls.superName; + + while (current != null && classes.containsKey(current)) { + depth++; + current = classes.get(current).superName; + } + + return depth; + } +} diff --git a/src/main/java/metrics/InheritanceStats.java b/src/main/java/metrics/InheritanceStats.java new file mode 100644 index 0000000..faf7fad --- /dev/null +++ b/src/main/java/metrics/InheritanceStats.java @@ -0,0 +1,4 @@ +package metrics; + +public record InheritanceStats(int maxDepth, double avgDepth) { +} diff --git a/src/main/java/metrics/OverrideMethodsCalculator.java b/src/main/java/metrics/OverrideMethodsCalculator.java new file mode 100644 index 0000000..3e8cf14 --- /dev/null +++ b/src/main/java/metrics/OverrideMethodsCalculator.java @@ -0,0 +1,62 @@ +package metrics; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import analyzer.ClassInfo; +import analyzer.MethodSignature; + +public class OverrideMethodsCalculator { + + public static double averageOverriddenMethods(Map classes) { + int totalOverrides = 0; + int classCount = 0; + + for (ClassInfo cls : classes.values()) { + if (cls.superName == null) continue; + + ClassInfo superCls = classes.get(cls.superName); + if (superCls == null) continue; + + int overridden = countOverrides(cls, classes); + totalOverrides += overridden; + classCount = overridden > 0 ? classCount + 1 : classCount; + } + + if (classCount == 0) return 0.0; + + return (double) totalOverrides / classCount; + } + + public static int countOverrides(ClassInfo cls, Map classes) { + int count = 0; + Set seen = new HashSet<>(); + + String superName = cls.superName; + + while (superName != null && classes.containsKey(superName)) { + ClassInfo superCls = classes.get(superName); + + for (MethodSignature m : cls.methods) { + if (!seen.contains(m) && (isObjectMethod(m) || superCls.methods.contains(m))) { + count++; + seen.add(m); + } + } + + superName = superCls.superName; + } + + return count; + } + + private static boolean isObjectMethod(MethodSignature method) { + String methodSignature = method.name() + method.descriptor(); + return methodSignature.equals("toString()Ljava/lang/String;") || + methodSignature.equals("equals(Ljava/lang/Object;)Z") || + methodSignature.equals("hashCode()I") || + methodSignature.equals("clone()Ljava/lang/Object;") || + methodSignature.equals("finalize()V"); + } +} diff --git a/src/main/java/output/MetricsResult.java b/src/main/java/output/MetricsResult.java new file mode 100644 index 0000000..c56c4b2 --- /dev/null +++ b/src/main/java/output/MetricsResult.java @@ -0,0 +1,30 @@ +package output; + +import metrics.InheritanceStats; + +public class MetricsResult { + + public int classesAnalyzed; + public int abcAssignments; + + public int maxInheritanceDepth; + public double avgInheritanceDepth; + + public double avgOverriddenMethods; + public double avgFieldsPerClass; + + public MetricsResult( + int classesAnalyzed, + int abcAssignments, + InheritanceStats inheritanceStats, + double avgOverriddenMethods, + double avgFieldsPerClass + ) { + this.classesAnalyzed = classesAnalyzed; + this.abcAssignments = abcAssignments; + this.maxInheritanceDepth = inheritanceStats.maxDepth(); + this.avgInheritanceDepth = inheritanceStats.avgDepth(); + this.avgOverriddenMethods = avgOverriddenMethods; + this.avgFieldsPerClass = avgFieldsPerClass; + } +} diff --git a/src/main/java/visitor/AbcMethodVisitor.java b/src/main/java/visitor/AbcMethodVisitor.java new file mode 100644 index 0000000..ce07043 --- /dev/null +++ b/src/main/java/visitor/AbcMethodVisitor.java @@ -0,0 +1,24 @@ +package visitor; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import analyzer.ClassInfo; + +public class AbcMethodVisitor extends MethodVisitor { + + private final ClassInfo classInfo; + + public AbcMethodVisitor(MethodVisitor mv, ClassInfo classInfo) { + super(Opcodes.ASM9, mv); + this.classInfo = classInfo; + } + + @Override + public void visitVarInsn(int opcode, int var) { + if (opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE) { + classInfo.assignmentCount++; + } + super.visitVarInsn(opcode, var); + } +} diff --git a/src/main/java/visitor/MetricsClassVisitor.java b/src/main/java/visitor/MetricsClassVisitor.java new file mode 100644 index 0000000..6530636 --- /dev/null +++ b/src/main/java/visitor/MetricsClassVisitor.java @@ -0,0 +1,77 @@ +package visitor; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import analyzer.ClassInfo; +import analyzer.MethodSignature; + +public class MetricsClassVisitor extends ClassVisitor { + + private ClassInfo classInfo; + + public MetricsClassVisitor() { + super(Opcodes.ASM9); + } + + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces + ) { + this.classInfo = new ClassInfo(name, superName); + } + + @Override + public FieldVisitor visitField( + int access, + String name, + String descriptor, + String signature, + Object value + ) { + classInfo.fieldsCount++; + return super.visitField(access, name, descriptor, signature, value); + } + + @Override + public MethodVisitor visitMethod( + int access, + String name, + String descriptor, + String signature, + String[] exceptions + ) { + if (isEligibleForOverride(access, name)) { + classInfo.methods.add(new MethodSignature(name, descriptor)); + } + + return new AbcMethodVisitor( + super.visitMethod(access, name, descriptor, signature, exceptions), + classInfo + ); + } + + private boolean isEligibleForOverride(int access, String name) { + if (name.equals("") || name.equals("")) { + return false; + } + if ((access & Opcodes.ACC_PRIVATE) != 0) { + return false; + } + if ((access & Opcodes.ACC_STATIC) != 0) { + return false; + } + return true; + } + + public ClassInfo getClassInfo() { + return classInfo; + } +} From 1d0e827394d6c8821654dda23189543fbe154372 Mon Sep 17 00:00:00 2001 From: Vladislav Bandurin Date: Wed, 14 Jan 2026 20:07:17 +0300 Subject: [PATCH 3/3] Supported branches and conditions --- output.json | 2 +- src/main/java/analyzer/ClassInfo.java | 2 ++ src/main/java/analyzer/JarAnalyzer.java | 6 ++-- src/main/java/metrics/AbcCalculator.java | 12 +++---- src/main/java/output/MetricsResult.java | 6 ++-- src/main/java/visitor/AbcMethodVisitor.java | 38 +++++++++++++++++++++ 6 files changed, 52 insertions(+), 14 deletions(-) diff --git a/output.json b/output.json index 83562e5..22783fb 100644 --- a/output.json +++ b/output.json @@ -1,6 +1,6 @@ { "classesAnalyzed" : 330, - "abcAssignments" : 1655, + "abc" : 2577.6365143285816, "maxInheritanceDepth" : 5, "avgInheritanceDepth" : 1.4393939393939394, "avgOverriddenMethods" : 2.2790697674418605, diff --git a/src/main/java/analyzer/ClassInfo.java b/src/main/java/analyzer/ClassInfo.java index dceeaf0..f2a39fd 100644 --- a/src/main/java/analyzer/ClassInfo.java +++ b/src/main/java/analyzer/ClassInfo.java @@ -10,6 +10,8 @@ public class ClassInfo { public int fieldsCount = 0; public int assignmentCount = 0; + public int branchCount = 0; + public int conditionCount = 0; public ClassInfo(String name, String superName) { this.name = name; diff --git a/src/main/java/analyzer/JarAnalyzer.java b/src/main/java/analyzer/JarAnalyzer.java index f1a251f..562a28e 100644 --- a/src/main/java/analyzer/JarAnalyzer.java +++ b/src/main/java/analyzer/JarAnalyzer.java @@ -61,7 +61,7 @@ public static void main(String[] args) throws IOException { InheritanceStats depthStats = InheritanceDepthCalculator.calculate(classes); - int totalAssignments = + double abc = AbcCalculator.totalAssignments(classes); double avgOverriddenMethods = @@ -72,7 +72,7 @@ public static void main(String[] args) throws IOException { MetricsResult result = new MetricsResult( classes.size(), - totalAssignments, + abc, depthStats, avgOverriddenMethods, avgFields @@ -86,7 +86,7 @@ public static void main(String[] args) throws IOException { private static void printToConsole(MetricsResult r) { System.out.println("====== Metrics ======"); System.out.println("Classes analyzed: " + r.classesAnalyzed); - System.out.println("ABC (assignments): " + r.abcAssignments); + System.out.println("ABC (assignments): " + r.abc); System.out.println("Max inheritance depth: " + r.maxInheritanceDepth); System.out.println("Avg inheritance depth: " + r.avgInheritanceDepth); System.out.println("Avg overridden methods: " + r.avgOverriddenMethods); diff --git a/src/main/java/metrics/AbcCalculator.java b/src/main/java/metrics/AbcCalculator.java index bcf1c4b..1cb5c96 100644 --- a/src/main/java/metrics/AbcCalculator.java +++ b/src/main/java/metrics/AbcCalculator.java @@ -6,13 +6,11 @@ public class AbcCalculator { - public static int totalAssignments(Map classes) { - int sum = 0; + public static double totalAssignments(Map classes) { + int totalA = classes.values().stream().mapToInt(c -> c.assignmentCount).sum(); + int totalB = classes.values().stream().mapToInt(c -> c.branchCount).sum(); + int totalC = classes.values().stream().mapToInt(c -> c.conditionCount).sum(); - for (ClassInfo cls : classes.values()) { - sum += cls.assignmentCount; - } - - return sum; + return Math.sqrt(totalA*totalA + totalB*totalB + totalC*totalC); } } diff --git a/src/main/java/output/MetricsResult.java b/src/main/java/output/MetricsResult.java index c56c4b2..9d081a1 100644 --- a/src/main/java/output/MetricsResult.java +++ b/src/main/java/output/MetricsResult.java @@ -5,7 +5,7 @@ public class MetricsResult { public int classesAnalyzed; - public int abcAssignments; + public double abc; public int maxInheritanceDepth; public double avgInheritanceDepth; @@ -15,13 +15,13 @@ public class MetricsResult { public MetricsResult( int classesAnalyzed, - int abcAssignments, + double abc, InheritanceStats inheritanceStats, double avgOverriddenMethods, double avgFieldsPerClass ) { this.classesAnalyzed = classesAnalyzed; - this.abcAssignments = abcAssignments; + this.abc = abc; this.maxInheritanceDepth = inheritanceStats.maxDepth(); this.avgInheritanceDepth = inheritanceStats.avgDepth(); this.avgOverriddenMethods = avgOverriddenMethods; diff --git a/src/main/java/visitor/AbcMethodVisitor.java b/src/main/java/visitor/AbcMethodVisitor.java index ce07043..18a8424 100644 --- a/src/main/java/visitor/AbcMethodVisitor.java +++ b/src/main/java/visitor/AbcMethodVisitor.java @@ -1,5 +1,10 @@ package visitor; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -9,6 +14,14 @@ public class AbcMethodVisitor extends MethodVisitor { private final ClassInfo classInfo; + private static final Set CONDITIONAL_JUMPS = new HashSet<>(Arrays.asList( + Opcodes.IFEQ, Opcodes.IFNE, Opcodes.IFLT, Opcodes.IFGE, Opcodes.IFGT, Opcodes.IFLE, + Opcodes.IF_ICMPEQ, Opcodes.IF_ICMPNE, Opcodes.IF_ICMPLT, Opcodes.IF_ICMPGE, + Opcodes.IF_ICMPGT, Opcodes.IF_ICMPLE, Opcodes.IF_ACMPEQ, Opcodes.IF_ACMPNE, + Opcodes.IFNULL, Opcodes.IFNONNULL + )); + + public AbcMethodVisitor(MethodVisitor mv, ClassInfo classInfo) { super(Opcodes.ASM9, mv); this.classInfo = classInfo; @@ -21,4 +34,29 @@ public void visitVarInsn(int opcode, int var) { } super.visitVarInsn(opcode, var); } + + @Override + public void visitJumpInsn(int opcode, Label label) { + classInfo.branchCount++; + + if (CONDITIONAL_JUMPS.contains(opcode)) { + classInfo.conditionCount++; + } + + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + classInfo.branchCount++; + classInfo.conditionCount += labels.length + 1; + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + classInfo.branchCount++; + classInfo.conditionCount += labels.length + 1; + super.visitTableSwitchInsn(min, max, dflt, labels); + } }