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 файла diff --git a/build.gradle.kts b/build.gradle.kts index 2b92afd..6404df8 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,10 +10,11 @@ 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") + 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 52d0abe..c5fd28b 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -1,29 +1,105 @@ 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.util.InheritanceDepthCalculator; +import org.example.util.OverrideDetector; import org.example.visitor.ClassPrinter; import org.objectweb.asm.ClassReader; +import java.io.File; import java.io.IOException; -import java.util.Enumeration; +import java.io.InputStream; +import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; public class Example { public static void main(String[] args) throws IOException { -// var printer = new ByteCodePrinter(); -// printer.printBubbleSortBytecode(); + 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 + ) + throws IOException + { + int totalFields = 0; + int totalA = 0; + int totalB = 0; + int totalC = 0; + int totalOverrideMethods = 0; + + OverrideDetector detector = new OverrideDetector(allClasses); + InheritanceDepthCalculator calc = new InheritanceDepthCalculator(allClasses); + + Map superMap = new HashMap<>(); + for (ClassInfo classInfo : allClasses) { + superMap.put(classInfo.name, classInfo.superName); + } + + for (ClassInfo classInfo : allClasses) { + totalFields += classInfo.fields.size(); + for (MethodInfo m : classInfo.methods) { + 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); + + 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: " + 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", Map.of( + "A", totalA, + "B", totalB, + "C", totalC, + "value", abcMetric + )); + + ObjectMapper mapper = new ObjectMapper(); + ObjectWriter writer = mapper.writerWithDefaultPrettyPrinter(); + writer.writeValue(new File("results.json"), metrics); } } 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..f39cd95 --- /dev/null +++ b/src/main/java/org/example/abc_visitor/AbcVisitor.java @@ -0,0 +1,114 @@ +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; + + +public class AbcVisitor extends MethodVisitor { + + private final MethodInfo methodInfo; + + public AbcVisitor(int api, MethodInfo methodInfo) { + super(api); + this.methodInfo = methodInfo; + } + + /// ///// a-метрика + @Override + public void visitVarInsn(int opcode, int var) { + 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/ClassInfo.java b/src/main/java/org/example/structs/ClassInfo.java new file mode 100644 index 0000000..80b26f0 --- /dev/null +++ b/src/main/java/org/example/structs/ClassInfo.java @@ -0,0 +1,12 @@ +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<>(); + 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 new file mode 100644 index 0000000..05efc17 --- /dev/null +++ b/src/main/java/org/example/structs/MethodInfo.java @@ -0,0 +1,10 @@ +package org.example.structs; + +public class MethodInfo { + public String name; + public boolean isOverride = false; + public int assignments = 0; + public String desc; + public int conditions; + public int branches; +} \ 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..40ba400 --- /dev/null +++ b/src/main/java/org/example/util/InheritanceDepthCalculator.java @@ -0,0 +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 new file mode 100644 index 0000000..bcd2f32 --- /dev/null +++ b/src/main/java/org/example/util/OverrideDetector.java @@ -0,0 +1,58 @@ +package org.example.util; + +import org.example.structs.ClassInfo; +import org.example.structs.MethodInfo; + +import java.util.List; + +public class OverrideDetector { + + private final List classes; + + public OverrideDetector(List classes) { + this.classes = classes; + } + + public boolean isOverride(ClassInfo cls, MethodInfo method) { + if (checkSuperclass(cls.superName, method)) return true; + + for (String iface : cls.interfaces) { + if (checkInterface(iface, method)) return true; + } + + return false; + } + + 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 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 ba1ab9f..1f4e523 100644 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ b/src/main/java/org/example/visitor/ClassPrinter.java @@ -1,46 +1,114 @@ package org.example.visitor; +import org.example.abc_visitor.AbcVisitor; +import org.example.structs.MethodInfo; import org.objectweb.asm.*; +import java.util.Arrays; +import java.util.List; + import static org.objectweb.asm.Opcodes.ASM8; +import org.example.structs.ClassInfo; public class ClassPrinter extends ClassVisitor { - public ClassPrinter() { + + private ClassInfo currentClass; + private List allClasses; + + public ClassPrinter(List allClasses) { super(ASM8); + this.allClasses = allClasses; } - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - System.out.println("\n" + name + " extends " + superName + " {"); + @Override + public void visit( + int version, + int access, + String name, + String signature, + String superName, + String[] interfaces + ) { + 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); } - public void visitSource(String source, String debug) { + @Override + public void visitSource( + String source, + String debug + ) { + super.visitSource(source, debug); } - public void visitOuterClass(String owner, String name, String desc) { + @Override + public void visitOuterClass( + String owner, + String name, + String desc + ) { + super.visitOuterClass(owner, name, desc); } - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - return null; + @Override + public AnnotationVisitor visitAnnotation( + String desc, + boolean visible + ) { + return super.visitAnnotation(desc, visible); } + @Override public void visitAttribute(Attribute attr) { + super.visitAttribute(attr); } - public void visitInnerClass(String name, String outerName, String innerName, int access) { + @Override + public void visitInnerClass( + String name, + String outerName, + String innerName, + int access + ) { + super.visitInnerClass(name, outerName, innerName, access); } - public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { - System.out.println(" " + desc + " " + name); - return null; + @Override + 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); - return null; + @Override + public MethodVisitor visitMethod( + int access, + String name, + String desc, + String signature, + String[] exceptions + ) { + MethodInfo methodInfo = new MethodInfo(); + methodInfo.name = name; + currentClass.methods.add(methodInfo); + methodInfo.desc = desc; + return new AbcVisitor(Opcodes.ASM9, methodInfo); } + @Override public void visitEnd() { - System.out.println("}"); + allClasses.add(currentClass); + super.visitEnd(); } } diff --git a/src/main/resources/sample.jar b/src/main/resources/sample.jar index 64eba96..d2dd69f 100644 Binary files a/src/main/resources/sample.jar and b/src/main/resources/sample.jar differ