From 9e58708b00f5d5a0cd14d64213ef93c2cbaf5b80 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:01 +0000 Subject: [PATCH 01/11] 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 92056bcd2027df5ee24710b1a264396ace90636b Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Fri, 28 Nov 2025 15:58:02 +0300 Subject: [PATCH 02/11] =?UTF-8?q?=D0=BA=D0=B0=D1=80=D0=BA=D0=B0=D1=81=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=B2=D0=B8=D0=B7=D0=B8=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/example/Example.java | 2 - .../org/example/abc_visitor/AbcVisitor.java | 20 ++++++ .../java/org/example/structs/ClassInfo.java | 11 +++ .../java/org/example/structs/MethodInfo.java | 7 ++ .../org/example/visitor/ClassPrinter.java | 72 +++++++++++++++---- 5 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/example/abc_visitor/AbcVisitor.java create mode 100644 src/main/java/org/example/structs/ClassInfo.java create mode 100644 src/main/java/org/example/structs/MethodInfo.java diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index 52d0abe..78df736 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -11,8 +11,6 @@ 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(); diff --git a/src/main/java/org/example/abc_visitor/AbcVisitor.java b/src/main/java/org/example/abc_visitor/AbcVisitor.java new file mode 100644 index 0000000..e092f9d --- /dev/null +++ b/src/main/java/org/example/abc_visitor/AbcVisitor.java @@ -0,0 +1,20 @@ +package org.example.abc_visitor; + +import org.example.structs.MethodInfo; +import org.objectweb.asm.MethodVisitor; + + +public class AbcVisitor extends MethodVisitor { + + private final MethodInfo methodInfo; + + public AbcVisitor(int api, MethodInfo methodInfo) { + super(api); + this.methodInfo = methodInfo; + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } +} diff --git a/src/main/java/org/example/structs/ClassInfo.java b/src/main/java/org/example/structs/ClassInfo.java new file mode 100644 index 0000000..0ae894b --- /dev/null +++ b/src/main/java/org/example/structs/ClassInfo.java @@ -0,0 +1,11 @@ +package org.example.structs; + +import java.util.List; +import java.util.ArrayList; + +public class ClassInfo { + public String name; + public String superName; + public List fields = new ArrayList<>(); + public List methods = new ArrayList<>(); +} diff --git a/src/main/java/org/example/structs/MethodInfo.java b/src/main/java/org/example/structs/MethodInfo.java new file mode 100644 index 0000000..0ae08da --- /dev/null +++ b/src/main/java/org/example/structs/MethodInfo.java @@ -0,0 +1,7 @@ +package org.example.structs; + +public class MethodInfo { + public String name; + boolean isOverride = false; + int assignments = 0; +} \ No newline at end of file diff --git a/src/main/java/org/example/visitor/ClassPrinter.java b/src/main/java/org/example/visitor/ClassPrinter.java index ba1ab9f..ed716ce 100644 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ b/src/main/java/org/example/visitor/ClassPrinter.java @@ -1,46 +1,90 @@ package org.example.visitor; +import org.example.structs.MethodInfo; import org.objectweb.asm.*; +import java.util.List; import static org.objectweb.asm.Opcodes.ASM8; +import org.example.structs.ClassInfo; public class ClassPrinter extends ClassVisitor { + + private ClassInfo currentClass; + private List allClasses; + private ClassInfo classInfo; + 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 visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces + ) { + classInfo = new ClassInfo(); + classInfo.name = name; + classInfo.superName = superName; } - public void visitSource(String source, String debug) { + public void visitSource( + String source, + String debug + ) { } - public void visitOuterClass(String owner, String name, String desc) { + public void visitOuterClass( + String owner, + String name, + String desc + ) { } - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + 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 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 FieldVisitor visitField( + int access, + String name, + String desc, + String signature, + Object value + ) { + currentClass.fields.add(name); + return super.visitField(access, name, desc, signature, value); } - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - System.out.println(" " + name + desc); + public MethodVisitor visitMethod( + int access, + String name, + String desc, + String signature, + String[] exceptions + ) { + MethodInfo methodInfo = new MethodInfo(); + methodInfo.name = name; + currentClass.methods.add(methodInfo); return null; } - public void visitEnd() { - System.out.println("}"); - } + public void visitEnd() {allClasses.add(currentClass);} } From 2028ff6e4d647fd58cd7fd0f6b5dcf0c18e8a010 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Fri, 28 Nov 2025 16:21:45 +0300 Subject: [PATCH 03/11] iter 2 --- src/main/java/org/example/Example.java | 19 +++++++++++++++---- .../org/example/abc_visitor/AbcVisitor.java | 13 +++++++++++-- .../java/org/example/structs/MethodInfo.java | 2 +- .../org/example/visitor/ClassPrinter.java | 7 +++++-- 4 files changed, 32 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index 78df736..c583c27 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -1,27 +1,38 @@ package org.example; +import org.example.structs.ClassInfo; import org.example.visitor.ClassPrinter; import org.objectweb.asm.ClassReader; import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; import java.util.Enumeration; +import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class Example { public static void main(String[] args) throws IOException { + List allClasses = new ArrayList<>(); + 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); + ClassPrinter cp = new ClassPrinter(allClasses); + InputStream inputStream = sampleJar.getInputStream(entry); + ClassReader reader = new ClassReader(inputStream); + reader.accept(cp, 0); } } } + getStats(allClasses); + } + + private static void getStats(List allClasses) { + System.out.println("Classes count: " + allClasses.size()); } } diff --git a/src/main/java/org/example/abc_visitor/AbcVisitor.java b/src/main/java/org/example/abc_visitor/AbcVisitor.java index e092f9d..bb7a938 100644 --- a/src/main/java/org/example/abc_visitor/AbcVisitor.java +++ b/src/main/java/org/example/abc_visitor/AbcVisitor.java @@ -2,6 +2,7 @@ import org.example.structs.MethodInfo; import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; public class AbcVisitor extends MethodVisitor { @@ -14,7 +15,15 @@ public AbcVisitor(int api, MethodInfo methodInfo) { } @Override - public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { - super.visitMethodInsn(opcode, owner, name, desc, itf); + public void visitMethodInsn( + int opcode, + String owner, + String name, + String desc, + boolean itf + ) { + if ((opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE) || opcode == Opcodes.IINC) { + methodInfo.assignments++; + } } } diff --git a/src/main/java/org/example/structs/MethodInfo.java b/src/main/java/org/example/structs/MethodInfo.java index 0ae08da..28c8a5a 100644 --- a/src/main/java/org/example/structs/MethodInfo.java +++ b/src/main/java/org/example/structs/MethodInfo.java @@ -3,5 +3,5 @@ public class MethodInfo { public String name; boolean isOverride = false; - int assignments = 0; + public int assignments = 0; } \ No newline at end of file diff --git a/src/main/java/org/example/visitor/ClassPrinter.java b/src/main/java/org/example/visitor/ClassPrinter.java index ed716ce..fae38e9 100644 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ b/src/main/java/org/example/visitor/ClassPrinter.java @@ -1,7 +1,9 @@ package org.example.visitor; +import org.example.abc_visitor.AbcVisitor; import org.example.structs.MethodInfo; import org.objectweb.asm.*; + import java.util.List; import static org.objectweb.asm.Opcodes.ASM8; @@ -13,8 +15,9 @@ public class ClassPrinter extends ClassVisitor { private List allClasses; private ClassInfo classInfo; - public ClassPrinter() { + public ClassPrinter(List allClasses) { super(ASM8); + this.allClasses = allClasses; } public void visit( @@ -82,7 +85,7 @@ public MethodVisitor visitMethod( MethodInfo methodInfo = new MethodInfo(); methodInfo.name = name; currentClass.methods.add(methodInfo); - return null; + return new AbcVisitor(Opcodes.ASM9, methodInfo); } public void visitEnd() {allClasses.add(currentClass);} From 5614cfbf3ec421614d932d3d23ed0651144258b6 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Fri, 28 Nov 2025 16:24:05 +0300 Subject: [PATCH 04/11] =?UTF-8?q?=D1=83=D0=B1=D1=80=D0=B0=D0=BB=20=D0=B4?= =?UTF-8?q?=D1=83=D0=B1=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/example/visitor/ClassPrinter.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/example/visitor/ClassPrinter.java b/src/main/java/org/example/visitor/ClassPrinter.java index fae38e9..5fecfb2 100644 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ b/src/main/java/org/example/visitor/ClassPrinter.java @@ -13,7 +13,6 @@ public class ClassPrinter extends ClassVisitor { private ClassInfo currentClass; private List allClasses; - private ClassInfo classInfo; public ClassPrinter(List allClasses) { super(ASM8); @@ -28,9 +27,9 @@ public void visit( String superName, String[] interfaces ) { - classInfo = new ClassInfo(); - classInfo.name = name; - classInfo.superName = superName; + currentClass = new ClassInfo(); + currentClass.name = name; + currentClass.superName = superName; } public void visitSource( From 07d3ae3a319611b540de8274234d6dcd23298caf Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Fri, 28 Nov 2025 16:42:48 +0300 Subject: [PATCH 05/11] wip stat calc --- src/main/java/org/example/Example.java | 36 +++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index c583c27..49cbb9d 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -1,14 +1,13 @@ package org.example; import org.example.structs.ClassInfo; +import org.example.structs.MethodInfo; import org.example.visitor.ClassPrinter; import org.objectweb.asm.ClassReader; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; +import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -33,6 +32,35 @@ public static void main(String[] args) throws IOException { } private static void getStats(List allClasses) { - System.out.println("Classes count: " + allClasses.size()); + int totalFields = 0; + int totalMethods = 0; + int totalAssignments = 0; + int totalOverrideMethods = 0; + int maxDepth = 0; + int sumDepth = 0; + + Map superMap = new HashMap<>(); + for (ClassInfo classInfo : allClasses) { + superMap.put(classInfo.name, classInfo.superName); + } + + for (ClassInfo classInfo : allClasses) { + totalFields += classInfo.fields.size(); + totalMethods += classInfo.methods.size(); + for (MethodInfo methodInfo : classInfo.methods) { + totalAssignments += methodInfo.assignments; + } + int depth = 0; + String s = classInfo.superName; + while (s != null && !s.equals("java/lang/Object")) { + depth++; + s = superMap.get(s); + } + if (depth >= maxDepth) { + maxDepth = depth; + sumDepth += depth; + } + } + } } From 2732734f7d00f472ca3147646fc8d1e811b98ec2 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Fri, 28 Nov 2025 16:54:52 +0300 Subject: [PATCH 06/11] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/example/Example.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index 49cbb9d..f5394af 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -33,7 +33,6 @@ public static void main(String[] args) throws IOException { private static void getStats(List allClasses) { int totalFields = 0; - int totalMethods = 0; int totalAssignments = 0; int totalOverrideMethods = 0; int maxDepth = 0; @@ -46,9 +45,9 @@ private static void getStats(List allClasses) { for (ClassInfo classInfo : allClasses) { totalFields += classInfo.fields.size(); - totalMethods += classInfo.methods.size(); for (MethodInfo methodInfo : classInfo.methods) { totalAssignments += methodInfo.assignments; + String parent = classInfo.superName; } int depth = 0; String s = classInfo.superName; @@ -62,5 +61,14 @@ private static void getStats(List allClasses) { } } + int avgDepth = !allClasses.isEmpty() ? (sumDepth / allClasses.size()) : 0; + int avgFields = !allClasses.isEmpty() ? (totalFields / allClasses.size()) : 0; + int avgOverride = !allClasses.isEmpty() ? (totalOverrideMethods / allClasses.size()) : 0; + + System.out.println("Средняя глубина наследования: " + avgDepth); + System.out.println("Максимальная глубина наследования: " + maxDepth); + System.out.println("Среднее количество полей в классе: " + avgFields); + System.out.println("Среднее количество переопределнных методов: " + avgOverride); + System.out.println("Метрика ABC (сумма assignments): " + totalAssignments); } } From 68e67e79a409dfc9580d8e76b1761fea90a3e0b0 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 29 Nov 2025 14:03:24 +0300 Subject: [PATCH 07/11] =?UTF-8?q?=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C,=20=D0=BF=D0=BE=D0=BC=D0=B5=D0=BD=D1=8F=D0=BB=20sa?= =?UTF-8?q?mple=20=D0=BD=D0=B0=20=D0=B1=D0=BE=D0=BB=D0=B5=D0=B5=20=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D1=80=D0=B5=D0=B7=D0=B5=D0=BD=D1=82=D0=B0=D1=82?= =?UTF-8?q?=D0=B8=D0=B2=D0=BD=D1=8B=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 2 +- src/main/java/org/example/Example.java | 5 +++-- .../org/example/abc_visitor/AbcVisitor.java | 13 +++++------ .../org/example/visitor/ClassPrinter.java | 21 ++++++++++++++++-- src/main/resources/sample.jar | Bin 6635 -> 40260 bytes 5 files changed, 28 insertions(+), 13 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2b92afd..74a3476 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,7 +10,7 @@ repositories { } dependencies { - implementation("org.ow2.asm:asm:9.5") + implementation("org.ow2.asm:asm:9.6") implementation("org.ow2.asm:asm-tree:9.5") implementation("org.ow2.asm:asm-analysis:9.5") implementation("org.ow2.asm:asm-util:9.5") diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index f5394af..f7e5f96 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -20,6 +20,7 @@ public static void main(String[] args) throws IOException { Enumeration enumeration = sampleJar.entries(); while (enumeration.hasMoreElements()) { JarEntry entry = enumeration.nextElement(); + System.out.println("Processing: " + entry.getName()); if (entry.getName().endsWith(".class")) { ClassPrinter cp = new ClassPrinter(allClasses); InputStream inputStream = sampleJar.getInputStream(entry); @@ -55,9 +56,9 @@ private static void getStats(List allClasses) { depth++; s = superMap.get(s); } - if (depth >= maxDepth) { + sumDepth += depth; + if (depth > maxDepth) { maxDepth = depth; - sumDepth += depth; } } diff --git a/src/main/java/org/example/abc_visitor/AbcVisitor.java b/src/main/java/org/example/abc_visitor/AbcVisitor.java index bb7a938..d77e4f4 100644 --- a/src/main/java/org/example/abc_visitor/AbcVisitor.java +++ b/src/main/java/org/example/abc_visitor/AbcVisitor.java @@ -15,15 +15,12 @@ public AbcVisitor(int api, MethodInfo methodInfo) { } @Override - public void visitMethodInsn( - int opcode, - String owner, - String name, - String desc, - boolean itf - ) { - if ((opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE) || opcode == Opcodes.IINC) { + public void visitVarInsn(int opcode, int var) { + if ( + (opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE) || opcode == Opcodes.IINC) + { methodInfo.assignments++; } + super.visitVarInsn(opcode, var); } } diff --git a/src/main/java/org/example/visitor/ClassPrinter.java b/src/main/java/org/example/visitor/ClassPrinter.java index 5fecfb2..c874e38 100644 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ b/src/main/java/org/example/visitor/ClassPrinter.java @@ -19,6 +19,7 @@ public ClassPrinter(List allClasses) { this.allClasses = allClasses; } + @Override public void visit( int version, int access, @@ -30,39 +31,50 @@ public void visit( currentClass = new ClassInfo(); currentClass.name = name; currentClass.superName = superName; + super.visit(version, access, name, signature, superName, interfaces); } + @Override public void visitSource( String source, String debug ) { + super.visitSource(source, debug); } + @Override public void visitOuterClass( String owner, String name, String desc ) { + super.visitOuterClass(owner, name, desc); } + @Override public AnnotationVisitor visitAnnotation( String desc, boolean visible ) { - return null; + return super.visitAnnotation(desc, visible); } + @Override public void visitAttribute(Attribute attr) { + super.visitAttribute(attr); } + @Override public void visitInnerClass( String name, String outerName, String innerName, int access ) { + super.visitInnerClass(name, outerName, innerName, access); } + @Override public FieldVisitor visitField( int access, String name, @@ -74,6 +86,7 @@ public FieldVisitor visitField( return super.visitField(access, name, desc, signature, value); } + @Override public MethodVisitor visitMethod( int access, String name, @@ -87,6 +100,10 @@ public MethodVisitor visitMethod( return new AbcVisitor(Opcodes.ASM9, methodInfo); } - public void visitEnd() {allClasses.add(currentClass);} + @Override + public void visitEnd() { + allClasses.add(currentClass); + super.visitEnd(); + } } diff --git a/src/main/resources/sample.jar b/src/main/resources/sample.jar index 64eba96ebde407c872848083e2385bb0a9b5ca47..d2dd69f984a0051a0d019097cf403070cf8f5c90 100644 GIT binary patch literal 40260 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 From a313222efce2d38da0582d0980319171967a410f Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 29 Nov 2025 14:31:09 +0300 Subject: [PATCH 08/11] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D1=8C=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B7=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D0=BE=D0=B2=20=D0=B2?= =?UTF-8?q?=20json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 1 + src/main/java/org/example/Example.java | 20 ++++++++++++++++++- .../java/org/example/structs/MethodInfo.java | 2 +- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 74a3476..6404df8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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.16.2") testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index f7e5f96..9c5e826 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -1,10 +1,13 @@ package org.example; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectWriter; import org.example.structs.ClassInfo; import org.example.structs.MethodInfo; import org.example.visitor.ClassPrinter; import org.objectweb.asm.ClassReader; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.*; @@ -32,7 +35,11 @@ public static void main(String[] args) throws IOException { getStats(allClasses); } - private static void getStats(List allClasses) { + private static void getStats( + List allClasses + ) + throws IOException + { int totalFields = 0; int totalAssignments = 0; int totalOverrideMethods = 0; @@ -71,5 +78,16 @@ private static void getStats(List allClasses) { System.out.println("Среднее количество полей в классе: " + avgFields); System.out.println("Среднее количество переопределнных методов: " + avgOverride); System.out.println("Метрика ABC (сумма assignments): " + totalAssignments); + + Map metrics = new HashMap<>(); + metrics.put("averageInheritanceDepth", avgDepth); + metrics.put("maxInheritanceDepth", maxDepth); + metrics.put("averageFieldsPerClass", avgFields); + metrics.put("averageOverriddenMethods", avgOverride); + metrics.put("ABC_metric_totalAssignments", totalAssignments); + + ObjectMapper mapper = new ObjectMapper(); + ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter(); + writer.writeValue(new File("results.json"), metrics); } } diff --git a/src/main/java/org/example/structs/MethodInfo.java b/src/main/java/org/example/structs/MethodInfo.java index 28c8a5a..02ec2b3 100644 --- a/src/main/java/org/example/structs/MethodInfo.java +++ b/src/main/java/org/example/structs/MethodInfo.java @@ -2,6 +2,6 @@ public class MethodInfo { public String name; - boolean isOverride = false; + public boolean isOverride = false; public int assignments = 0; } \ No newline at end of file From 568e8984e16cb284aa59ff6befdf90aaf5a53fc5 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 13:56:53 +0300 Subject: [PATCH 09/11] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20?= =?UTF-8?q?=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B2=20=D0=B4=D0=B0=D1=82=D0=B0=D0=BA=D0=BB=D0=B0=D1=81=D1=81?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/example/structs/ClassInfo.java | 1 + .../java/org/example/structs/MethodInfo.java | 1 + .../util/InheritanceDepthCalculator.java | 4 ++ .../org/example/util/OverrideDetector.java | 65 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 src/main/java/org/example/util/InheritanceDepthCalculator.java create mode 100644 src/main/java/org/example/util/OverrideDetector.java diff --git a/src/main/java/org/example/structs/ClassInfo.java b/src/main/java/org/example/structs/ClassInfo.java index 0ae894b..80b26f0 100644 --- a/src/main/java/org/example/structs/ClassInfo.java +++ b/src/main/java/org/example/structs/ClassInfo.java @@ -8,4 +8,5 @@ public class ClassInfo { public String superName; public List fields = new ArrayList<>(); public List methods = new ArrayList<>(); + public List interfaces = new ArrayList<>(); } diff --git a/src/main/java/org/example/structs/MethodInfo.java b/src/main/java/org/example/structs/MethodInfo.java index 02ec2b3..9a144b7 100644 --- a/src/main/java/org/example/structs/MethodInfo.java +++ b/src/main/java/org/example/structs/MethodInfo.java @@ -4,4 +4,5 @@ public class MethodInfo { public String name; public boolean isOverride = false; public int assignments = 0; + public String desc; } \ No newline at end of file diff --git a/src/main/java/org/example/util/InheritanceDepthCalculator.java b/src/main/java/org/example/util/InheritanceDepthCalculator.java new file mode 100644 index 0000000..41c67b0 --- /dev/null +++ b/src/main/java/org/example/util/InheritanceDepthCalculator.java @@ -0,0 +1,4 @@ +package org.example.util; + +public class InheritanceDepthCalculator { +} diff --git a/src/main/java/org/example/util/OverrideDetector.java b/src/main/java/org/example/util/OverrideDetector.java new file mode 100644 index 0000000..aec430f --- /dev/null +++ b/src/main/java/org/example/util/OverrideDetector.java @@ -0,0 +1,65 @@ +package org.example.util; + +import org.example.structs.ClassInfo; +import org.example.structs.MethodInfo; + +import java.util.List; + +public class OverrideGetter { + + public int get( + List allClasses, + ClassInfo classInfo, + int totalAssignments, + int totalOverrideMethods + ) { + for (MethodInfo m : classInfo.methods) { + totalAssignments += m.assignments; + boolean isOverride = false; + String parent = classInfo.superName; + while (parent != null && !parent.equals("java/lang/Object")) { + String finalParent = parent; + ClassInfo parentClass = allClasses.stream() + .filter(c -> c.name.equals(finalParent)) + .findFirst().orElse(null); + if (parentClass != null) { + if (parentClass.methods.stream() + .anyMatch(pm -> pm.name.equals(m.name) && pm.desc.equals(m.desc))) { + isOverride = true; + break; + } + parent = parentClass.superName; + } else break; + } + + for (String iface : classInfo.interfaces) { + ClassInfo ifaceClass = allClasses.stream().filter(c -> c.name.equals(iface)).findFirst().orElse(null); + if (ifaceClass != null && ifaceClass.methods.stream() + .anyMatch(pm -> pm.name.equals(m.name) && pm.desc.equals(m.desc))) { + isOverride = true; + break; + } + } + + if (isOverride) totalOverrideMethods++; + } + return totalOverrideMethods; + } + + private boolean overrideInterfaceCheck( + List allClasses, + ClassInfo classInfo + ) { + boolean isOverride = false; + for (String iface : classInfo.interfaces) { + ClassInfo ifaceClass = allClasses.stream().filter( + c -> c.name.equals(iface) + ).findFirst().orElse(null); + if (ifaceClass != null && ifaceClass.methods.stream().anyMatch(pm -> pm.name.equals(m.name) && pm.desc.equals(m.desc))) { + isOverride = true; + return isOverride; + } + } + return null; + } +} From 8ebf12ece8acac3acf64506107a617bdf7bac8ea Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sun, 30 Nov 2025 13:57:29 +0300 Subject: [PATCH 10/11] =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=B1=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D1=81=D1=87=D0=B5=D1=82=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D0=B0=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/example/Example.java | 30 +++---- .../util/InheritanceDepthCalculator.java | 45 ++++++++++ .../org/example/util/OverrideDetector.java | 89 +++++++++---------- .../org/example/visitor/ClassPrinter.java | 5 ++ 4 files changed, 104 insertions(+), 65 deletions(-) diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index 9c5e826..cbd17f0 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.ObjectWriter; import org.example.structs.ClassInfo; import org.example.structs.MethodInfo; +import org.example.util.InheritanceDepthCalculator; +import org.example.util.OverrideDetector; import org.example.visitor.ClassPrinter; import org.objectweb.asm.ClassReader; @@ -23,7 +25,6 @@ public static void main(String[] args) throws IOException { Enumeration enumeration = sampleJar.entries(); while (enumeration.hasMoreElements()) { JarEntry entry = enumeration.nextElement(); - System.out.println("Processing: " + entry.getName()); if (entry.getName().endsWith(".class")) { ClassPrinter cp = new ClassPrinter(allClasses); InputStream inputStream = sampleJar.getInputStream(entry); @@ -43,8 +44,9 @@ private static void getStats( int totalFields = 0; int totalAssignments = 0; int totalOverrideMethods = 0; - int maxDepth = 0; - int sumDepth = 0; + + OverrideDetector detector = new OverrideDetector(allClasses); + InheritanceDepthCalculator calc = new InheritanceDepthCalculator(allClasses); Map superMap = new HashMap<>(); for (ClassInfo classInfo : allClasses) { @@ -53,23 +55,17 @@ private static void getStats( for (ClassInfo classInfo : allClasses) { totalFields += classInfo.fields.size(); - for (MethodInfo methodInfo : classInfo.methods) { - totalAssignments += methodInfo.assignments; - String parent = classInfo.superName; - } - int depth = 0; - String s = classInfo.superName; - while (s != null && !s.equals("java/lang/Object")) { - depth++; - s = superMap.get(s); - } - sumDepth += depth; - if (depth > maxDepth) { - maxDepth = depth; + for (MethodInfo m : classInfo.methods) { + totalAssignments += m.assignments; + if (detector.isOverride(classInfo, m)) { + totalOverrideMethods++; + } } } - int avgDepth = !allClasses.isEmpty() ? (sumDepth / allClasses.size()) : 0; + int maxDepth = calc.getMaxDepth(allClasses); + int avgDepth = calc.getAverageDepth(allClasses); + int avgFields = !allClasses.isEmpty() ? (totalFields / allClasses.size()) : 0; int avgOverride = !allClasses.isEmpty() ? (totalOverrideMethods / allClasses.size()) : 0; diff --git a/src/main/java/org/example/util/InheritanceDepthCalculator.java b/src/main/java/org/example/util/InheritanceDepthCalculator.java index 41c67b0..40ba400 100644 --- a/src/main/java/org/example/util/InheritanceDepthCalculator.java +++ b/src/main/java/org/example/util/InheritanceDepthCalculator.java @@ -1,4 +1,49 @@ package org.example.util; +import org.example.structs.ClassInfo; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class InheritanceDepthCalculator { + + private final Map superMap = new HashMap<>(); + + public InheritanceDepthCalculator(List allClasses) { + for (ClassInfo classInfo : allClasses) { + superMap.put(classInfo.name, classInfo.superName); + } + } + + public int getDepth(ClassInfo cls) { + int depth = 0; + String parent = cls.superName; + + while (parent != null && !parent.equals("java/lang/Object")) { + depth++; + parent = superMap.get(parent); + } + + return depth; + } + + public int getMaxDepth(List classes) { + int max = 0; + for (ClassInfo cls : classes) { + int d = getDepth(cls); + if (d > max) max = d; + } + return max; + } + + public int getAverageDepth(List classes) { + if (classes.isEmpty()) return 0; + + int sum = 0; + for (ClassInfo cls : classes) { + sum += getDepth(cls); + } + return sum / classes.size(); + } } diff --git a/src/main/java/org/example/util/OverrideDetector.java b/src/main/java/org/example/util/OverrideDetector.java index aec430f..bcd2f32 100644 --- a/src/main/java/org/example/util/OverrideDetector.java +++ b/src/main/java/org/example/util/OverrideDetector.java @@ -5,61 +5,54 @@ import java.util.List; -public class OverrideGetter { +public class OverrideDetector { - public int get( - List allClasses, - ClassInfo classInfo, - int totalAssignments, - int totalOverrideMethods - ) { - for (MethodInfo m : classInfo.methods) { - totalAssignments += m.assignments; - boolean isOverride = false; - String parent = classInfo.superName; - while (parent != null && !parent.equals("java/lang/Object")) { - String finalParent = parent; - ClassInfo parentClass = allClasses.stream() - .filter(c -> c.name.equals(finalParent)) - .findFirst().orElse(null); - if (parentClass != null) { - if (parentClass.methods.stream() - .anyMatch(pm -> pm.name.equals(m.name) && pm.desc.equals(m.desc))) { - isOverride = true; - break; - } - parent = parentClass.superName; - } else break; - } + private final List classes; - for (String iface : classInfo.interfaces) { - ClassInfo ifaceClass = allClasses.stream().filter(c -> c.name.equals(iface)).findFirst().orElse(null); - if (ifaceClass != null && ifaceClass.methods.stream() - .anyMatch(pm -> pm.name.equals(m.name) && pm.desc.equals(m.desc))) { - isOverride = true; - break; - } - } + public OverrideDetector(List classes) { + this.classes = classes; + } + + public boolean isOverride(ClassInfo cls, MethodInfo method) { + if (checkSuperclass(cls.superName, method)) return true; - if (isOverride) totalOverrideMethods++; + for (String iface : cls.interfaces) { + if (checkInterface(iface, method)) return true; } - return totalOverrideMethods; + + return false; } - private boolean overrideInterfaceCheck( - List allClasses, - ClassInfo classInfo - ) { - boolean isOverride = false; - for (String iface : classInfo.interfaces) { - ClassInfo ifaceClass = allClasses.stream().filter( - c -> c.name.equals(iface) - ).findFirst().orElse(null); - if (ifaceClass != null && ifaceClass.methods.stream().anyMatch(pm -> pm.name.equals(m.name) && pm.desc.equals(m.desc))) { - isOverride = true; - return isOverride; + private boolean checkSuperclass(String parent, MethodInfo method) { + while (parent != null && !parent.equals("java/lang/Object")) { + ClassInfo parentClass = findClass(parent); + if (parentClass == null) return false; + + if (containsMethod(parentClass, method)) { + return true; } + + parent = parentClass.superName; } - return null; + return false; + } + + private boolean checkInterface(String iface, MethodInfo method) { + ClassInfo ifaceClass = findClass(iface); + if (ifaceClass == null) return false; + + return containsMethod(ifaceClass, method); + } + + private boolean containsMethod(ClassInfo cls, MethodInfo m) { + return cls.methods.stream() + .anyMatch(pm -> pm.name.equals(m.name) && pm.desc.equals(m.desc)); + } + + private ClassInfo findClass(String internalName) { + return classes.stream() + .filter(c -> c.name.equals(internalName)) + .findFirst() + .orElse(null); } } diff --git a/src/main/java/org/example/visitor/ClassPrinter.java b/src/main/java/org/example/visitor/ClassPrinter.java index c874e38..1f4e523 100644 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ b/src/main/java/org/example/visitor/ClassPrinter.java @@ -4,6 +4,7 @@ import org.example.structs.MethodInfo; import org.objectweb.asm.*; +import java.util.Arrays; import java.util.List; import static org.objectweb.asm.Opcodes.ASM8; @@ -31,6 +32,9 @@ public void visit( currentClass = new ClassInfo(); currentClass.name = name; currentClass.superName = superName; + if (interfaces != null) { + currentClass.interfaces.addAll(Arrays.asList(interfaces)); + } super.visit(version, access, name, signature, superName, interfaces); } @@ -97,6 +101,7 @@ public MethodVisitor visitMethod( MethodInfo methodInfo = new MethodInfo(); methodInfo.name = name; currentClass.methods.add(methodInfo); + methodInfo.desc = desc; return new AbcVisitor(Opcodes.ASM9, methodInfo); } From f927625127f9f10ad0dc9e9e4db3172c37cae22f Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Sat, 13 Dec 2025 21:24:01 +0300 Subject: [PATCH 11/11] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20abc-=D0=BC=D0=B5=D1=82=D1=80=D0=B8?= =?UTF-8?q?=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/org/example/Example.java | 24 ++++- .../org/example/abc_visitor/AbcVisitor.java | 94 ++++++++++++++++++- .../java/org/example/structs/MethodInfo.java | 2 + 3 files changed, 113 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index cbd17f0..c5fd28b 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -42,7 +42,9 @@ private static void getStats( throws IOException { int totalFields = 0; - int totalAssignments = 0; + int totalA = 0; + int totalB = 0; + int totalC = 0; int totalOverrideMethods = 0; OverrideDetector detector = new OverrideDetector(allClasses); @@ -56,13 +58,22 @@ private static void getStats( for (ClassInfo classInfo : allClasses) { totalFields += classInfo.fields.size(); for (MethodInfo m : classInfo.methods) { - totalAssignments += m.assignments; + totalA += m.assignments; + totalB += m.branches; + totalC += m.conditions; + if (detector.isOverride(classInfo, m)) { totalOverrideMethods++; } } } + double abcMetric = Math.sqrt( + totalA * totalA + + totalB * totalB + + totalC * totalC + ); + int maxDepth = calc.getMaxDepth(allClasses); int avgDepth = calc.getAverageDepth(allClasses); @@ -73,14 +84,19 @@ private static void getStats( System.out.println("Максимальная глубина наследования: " + maxDepth); System.out.println("Среднее количество полей в классе: " + avgFields); System.out.println("Среднее количество переопределнных методов: " + avgOverride); - System.out.println("Метрика ABC (сумма assignments): " + totalAssignments); + System.out.println("Метрика ABC: " + abcMetric); Map metrics = new HashMap<>(); metrics.put("averageInheritanceDepth", avgDepth); metrics.put("maxInheritanceDepth", maxDepth); metrics.put("averageFieldsPerClass", avgFields); metrics.put("averageOverriddenMethods", avgOverride); - metrics.put("ABC_metric_totalAssignments", totalAssignments); + metrics.put("ABC_metric", Map.of( + "A", totalA, + "B", totalB, + "C", totalC, + "value", abcMetric + )); ObjectMapper mapper = new ObjectMapper(); ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter(); diff --git a/src/main/java/org/example/abc_visitor/AbcVisitor.java b/src/main/java/org/example/abc_visitor/AbcVisitor.java index d77e4f4..f39cd95 100644 --- a/src/main/java/org/example/abc_visitor/AbcVisitor.java +++ b/src/main/java/org/example/abc_visitor/AbcVisitor.java @@ -1,6 +1,8 @@ package org.example.abc_visitor; import org.example.structs.MethodInfo; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -14,13 +16,99 @@ public AbcVisitor(int api, MethodInfo methodInfo) { this.methodInfo = methodInfo; } + /// ///// a-метрика @Override public void visitVarInsn(int opcode, int var) { - if ( - (opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE) || opcode == Opcodes.IINC) - { + if (opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE) { methodInfo.assignments++; } super.visitVarInsn(opcode, var); } + + @Override + public void visitIincInsn(int var, int increment) { + methodInfo.assignments++; + super.visitIincInsn(var, increment); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + if (opcode == Opcodes.PUTFIELD || opcode == Opcodes.PUTSTATIC) { + methodInfo.assignments++; + } + super.visitFieldInsn(opcode, owner, name, descriptor); + } + + @Override + public void visitInsn(int opcode) { + switch (opcode) { + case Opcodes.IASTORE: + case Opcodes.LASTORE: + case Opcodes.FASTORE: + case Opcodes.DASTORE: + case Opcodes.AASTORE: + case Opcodes.BASTORE: + case Opcodes.CASTORE: + case Opcodes.SASTORE: + methodInfo.assignments++; + break; + } + super.visitInsn(opcode); + } + + /// //////// b-метрика + @Override + public void visitMethodInsn( + int opcode, + String owner, + String name, + String descriptor, + boolean isInterface + ) { + methodInfo.branches++; + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + @Override + public void visitInvokeDynamicInsn( + String name, + String descriptor, + Handle bootstrapMethodHandle, + Object... bootstrapMethodArguments + ) { + methodInfo.branches++; + super.visitInvokeDynamicInsn( + name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments + ); + } + + /// ///////// c-метрика + @Override + public void visitJumpInsn(int opcode, Label label) { + if (opcode != Opcodes.GOTO) { + methodInfo.conditions++; + } + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitTableSwitchInsn( + int min, + int max, + Label dflt, + Label... labels + ) { + methodInfo.conditions++; + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn( + Label dflt, + int[] keys, + Label[] labels + ) { + methodInfo.conditions++; + super.visitLookupSwitchInsn(dflt, keys, labels); + } } diff --git a/src/main/java/org/example/structs/MethodInfo.java b/src/main/java/org/example/structs/MethodInfo.java index 9a144b7..05efc17 100644 --- a/src/main/java/org/example/structs/MethodInfo.java +++ b/src/main/java/org/example/structs/MethodInfo.java @@ -5,4 +5,6 @@ public class MethodInfo { public boolean isOverride = false; public int assignments = 0; public String desc; + public int conditions; + public int branches; } \ No newline at end of file