From f9d96a459e32f90426415bf21870f6339a417456 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Sun, 2 Nov 2025 08:56:47 +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 57ee14bd2e1019696e5a50066951e9aef1dc60cf Mon Sep 17 00:00:00 2001 From: DenisStepanidenko Date: Thu, 27 Nov 2025 01:41:21 +0300 Subject: [PATCH 2/2] finish task --- build.gradle.kts | 10 + src/main/java/org/example/Example.java | 26 +- src/main/java/org/example/JarMetrics.java | 230 ++++++++++++++++++ src/main/java/org/example/graph/Graph.java | 76 ++++++ src/main/java/org/example/graph/Node.java | 88 +++++++ src/main/java/org/example/util/Metrics.java | 122 ++++++++++ .../java/org/example/visitor/ABCMetrics.java | 95 ++++++++ .../visitor/ClassCalculateMetrics.java | 59 +++++ 8 files changed, 695 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/example/JarMetrics.java create mode 100644 src/main/java/org/example/graph/Graph.java create mode 100644 src/main/java/org/example/graph/Node.java create mode 100644 src/main/java/org/example/util/Metrics.java create mode 100644 src/main/java/org/example/visitor/ABCMetrics.java create mode 100644 src/main/java/org/example/visitor/ClassCalculateMetrics.java diff --git a/build.gradle.kts b/build.gradle.kts index 2b92afd..d2bd7af 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -15,6 +15,8 @@ dependencies { implementation("org.ow2.asm:asm-analysis:9.5") implementation("org.ow2.asm:asm-util:9.5") + implementation ("org.json:json:20240303") + testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") @@ -22,4 +24,12 @@ dependencies { tasks.test { useJUnitPlatform() +} + +tasks.withType { + options.encoding = "UTF-8" +} + +tasks.withType { + options.encoding = "UTF-8" } \ 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..777ca55 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -3,6 +3,7 @@ import org.example.visitor.ClassPrinter; import org.objectweb.asm.ClassReader; +import java.io.File; import java.io.IOException; import java.util.Enumeration; import java.util.jar.JarEntry; @@ -13,17 +14,20 @@ 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(); +// 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); +// } +// } +// } - 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); - } - } - } + JarMetrics jarMetrics = new JarMetrics("src/main/resources/lab2-DenisStepanidenko.jar"); + jarMetrics.getMetrics(); } } diff --git a/src/main/java/org/example/JarMetrics.java b/src/main/java/org/example/JarMetrics.java new file mode 100644 index 0000000..9f2a937 --- /dev/null +++ b/src/main/java/org/example/JarMetrics.java @@ -0,0 +1,230 @@ +package org.example; + +import org.example.graph.Graph; +import org.example.graph.Node; +import org.example.visitor.ClassCalculateMetrics; +import org.objectweb.asm.ClassReader; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +import org.json.JSONObject; + +public class JarMetrics { + + private String pathToJarFile; + private Node root; + private Map classMap; + private Integer countOfClass = 0; + private int a; + private int b; + private int c; + private int countOfFields; + private int countOfMethods; + int maxDepth; + double averageDepth; + int overrideMethods; + double averageOverrideMethods; + double averageCountOfFields; + + private Graph graph = new Graph(); + + public JarMetrics(String pathToJarFile) { + this.pathToJarFile = pathToJarFile; + root = new Node("java/lang/Object", "", new HashSet<>(), List.of("hashCode()", "toString()", "equals(Ljava/lang/Object;)", "wait()", "notify()", "notifyAll()")); + classMap = new HashMap<>(); + classMap.put(root.getClassName(), root); + + } + + public void getMetrics() { + + try (JarFile sampleJar = new JarFile(pathToJarFile)) { + Enumeration enumeration = sampleJar.entries(); + + while (enumeration.hasMoreElements()) { + JarEntry entry = enumeration.nextElement(); + if (entry.getName().endsWith(".class")) { + + + ClassCalculateMetrics calculateMetrics = new ClassCalculateMetrics(); + ClassReader cr = new ClassReader(sampleJar.getInputStream(entry)); + cr.accept(calculateMetrics, 0); + + countOfClass++; + a += calculateMetrics.getMetrics().getA(); + b += calculateMetrics.getMetrics().getB(); + c += calculateMetrics.getMetrics().getC(); + countOfFields += calculateMetrics.getMetrics().getCountOfFields(); + countOfMethods += calculateMetrics.getMetrics().getCountOfMethods(); + + Node classNode = new Node(calculateMetrics.getMetrics().getClassName(), calculateMetrics.getMetrics().getSuperClassName(), calculateMetrics.getMetrics().getNameOfInterfaces(), calculateMetrics.getMetrics().getNameOfMethods()); + + + classMap.put(classNode.getClassName(), classNode); + } + } + + createGraph(); + + // Запускаем dfs для подсчёта глубины наследования + graph.dfs(root, 0); + + maxDepth = graph.getMaxDepth(); + averageDepth = graph.averageDepth(); + + overrideMethods = calculateOverrideMethods(); + averageOverrideMethods = (double) overrideMethods / countOfMethods; + averageCountOfFields = (double) countOfFields / countOfClass; + + + writeToJson(pathToJarFile, a, b, c, averageCountOfFields, + maxDepth, averageDepth, averageOverrideMethods, countOfMethods, overrideMethods); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static void writeToJson(String jarFileName, double aMetric, double bMetric, double cMetric, + double fieldsAverage, double maxDepth, double averageDepth, double averageOverridenMethods, + int methods, int overridenMethods) { + JSONObject metricJson = new JSONObject(); + metricJson.put("jarName", jarFileName); + metricJson.put("fieldsAverage", fieldsAverage); + metricJson.put("A", aMetric); + metricJson.put("B", bMetric); + metricJson.put("C", cMetric); + metricJson.put("ABC", Math.sqrt(aMetric * aMetric + bMetric * bMetric + cMetric * cMetric)); + metricJson.put("maxInheritanceDepth", maxDepth); + metricJson.put("averageInheritanceDepth", averageDepth); + metricJson.put("averageOverridenMethods", averageOverridenMethods); + metricJson.put("methods", methods); + metricJson.put("overridenMethods", overridenMethods); + + try { + File file = new File("metrics.json"); + file.createNewFile(); + FileWriter fileWriter = new FileWriter(file); + System.out.print(metricJson); + + fileWriter.write(metricJson.toString(4)); + fileWriter.flush(); + fileWriter.close(); + + } catch (IOException e) { + e.printStackTrace(); + } + + } + + private int calculateOverrideMethods() { + + // нужно посчитать переопределённые методы и у extends случаев и у implements + // superClass или интерфейсы + + findAllSuperClasses(); + findAllInterfaces(); + + return calculateOverrideMethodsInInterfaces() + calculateOverrideMethodsInSuperClasses(); + + } + + private int calculateOverrideMethodsInSuperClasses() { + + int result = 0; + for (Node node : classMap.values()) { + + for (String method : node.getNameOfMethods()) { + for (Node parent : node.getSuperClasses()) { + if (parent != null && parent.getNameOfMethods().contains(method)) + result++; + } + } + } + + return result; + } + + private int calculateOverrideMethodsInInterfaces() { + int result = 0; + + for (Node node : classMap.values()) { + + for (String method : node.getNameOfMethods()) { + for (String interfaceNode : node.getNameOfInterfaces()) { + Node parent = classMap.get(interfaceNode); + if (parent != null && parent.getNameOfMethods().contains(method)) + result++; + } + } + } + + return result; + } + + private void findAllInterfaces() { + + for (Node node : classMap.values()) { + findAllInterfaces(node, node); + } + + } + + private void findAllInterfaces(Node children, Node parent) { + + Set parentInterfaces = new HashSet<>(parent.getNameOfInterfaces()); + + for (String nameInterface : parentInterfaces) { + Node node = classMap.get(nameInterface); + if (node != null) { + children.getNameOfInterfaces().addAll(node.getNameOfInterfaces()); + findAllInterfaces(children, node); + } + } + + } + + /** + * Здесь мы заполним всю цепочку наследования + */ + private void findAllSuperClasses() { + + for (Node node : classMap.values()) { + node.getSuperClasses().add(classMap.get(node.getSuperClassName())); + findAllSuperClasses(node, node); + } + } + + private void findAllSuperClasses(Node children, Node parent) { + + Node nextParent = classMap.get(parent.getSuperClassName()); + if (nextParent != null) { + children.getSuperClasses().add(nextParent); + findAllSuperClasses(children, nextParent); + } + } + + private void createGraph() { + + for (Node from : classMap.values()) { + Node to = classMap.get(from.getSuperClassName()); + graph.addEdge(from, to); + } + + for (Node from : classMap.values()) { + + for (String interfacesName : from.getNameOfInterfaces()) { + Node to = classMap.get(interfacesName); + graph.addEdge(from, to); + } + } + + } + + +} diff --git a/src/main/java/org/example/graph/Graph.java b/src/main/java/org/example/graph/Graph.java new file mode 100644 index 0000000..9ad7012 --- /dev/null +++ b/src/main/java/org/example/graph/Graph.java @@ -0,0 +1,76 @@ +package org.example.graph; + +import java.util.*; + +public class Graph { + + private Map> adjacencyList = new HashMap<>(); + + public int maxDepth = -1; + public final List visited = new ArrayList<>(); + private Map depths = new HashMap<>(); + + public Map> getAdjacencyList() { + return adjacencyList; + } + + public void setAdjacencyList(Map> adjacencyList) { + this.adjacencyList = adjacencyList; + } + + public int getMaxDepth() { + return maxDepth; + } + + public void setMaxDepth(int maxDepth) { + this.maxDepth = maxDepth; + } + + public List getVisited() { + return visited; + } + + public Map getDepths() { + return depths; + } + + public void setDepths(Map depths) { + this.depths = depths; + } + + public void addNode(Node node) { + adjacencyList.putIfAbsent(node, new ArrayList<>()); + } + + + public void addEdge(Node node1, Node node2) { + addNode(node1); + addNode(node2); + + adjacencyList.get(node2).add(node1); + } + + + + public double averageDepth() { + return depths.values().stream().mapToInt(i -> i).average().orElse(0); + } + + public int dfs(Node root, int count) { + visited.add(root); + + for (Node node : adjacencyList.get(root)) { + if (!visited.contains(node)) { + int dfs = dfs(node, ++count); + if (maxDepth < dfs) { + maxDepth = dfs; + } + + depths.put(node.getClassName(), count); + count--; + } + } + + return count; + } +} diff --git a/src/main/java/org/example/graph/Node.java b/src/main/java/org/example/graph/Node.java new file mode 100644 index 0000000..62279e3 --- /dev/null +++ b/src/main/java/org/example/graph/Node.java @@ -0,0 +1,88 @@ +package org.example.graph; + +import java.util.*; + +public class Node { + + /** + * Имя самого класса + */ + private String className; + + /** + * Имя класса, от которого мы непосредственно наследовались + */ + private String superClassName; + + /** + * Имя интерфейсов, которые мы реализуем + */ + private Set nameOfInterfaces; + + /** + * Имена методов + */ + private List nameOfMethods; + + /** + * Список всех классов, от которых мы наследовались (именно вся цепочка транзитивных зависимостей) + */ + private Set superClasses = new HashSet<>(); + + public Set getSuperClasses() { + return superClasses; + } + + public Node(String className, String superClassName, Set nameOfInterfaces, List nameOfMethods) { + this.className = className; + this.superClassName = superClassName; + this.nameOfInterfaces = nameOfInterfaces; + this.nameOfMethods = nameOfMethods; + } + + public String getSuperClassName() { + return superClassName; + } + + public void setSuperClassName(String superClassName) { + this.superClassName = superClassName; + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public Set getNameOfInterfaces() { + return nameOfInterfaces; + } + + public void setNameOfInterfaces(Set nameOfInterfaces) { + this.nameOfInterfaces = nameOfInterfaces; + } + + public List getNameOfMethods() { + return nameOfMethods; + } + + public void setNameOfMethods(List nameOfMethods) { + this.nameOfMethods = nameOfMethods; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Node node = (Node) o; + return Objects.equals(className, node.className); + } + + @Override + public int hashCode() { + return Objects.hashCode(className); + } + +} diff --git a/src/main/java/org/example/util/Metrics.java b/src/main/java/org/example/util/Metrics.java new file mode 100644 index 0000000..c719d5f --- /dev/null +++ b/src/main/java/org/example/util/Metrics.java @@ -0,0 +1,122 @@ +package org.example.util; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class Metrics { + + /** + * Операции присваивания переменной (=, +=, -=, *=, ++, --, ...) + */ + private int a; + + /** + * Вызовы методов + */ + private int b; + + /** + * Логические условия и операторы сравнения + */ + private int c; + private int countOfFields; + private int countOfMethods; + private List nameOfMethods = new ArrayList(); + private String superClassName; + private String className; + private Set nameOfInterfaces = new HashSet<>(); + + public void increaseCountOfFields() { + countOfFields++; + } + + public void increaseCountOfMethods() { + countOfMethods++; + } + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + public int getB() { + return b; + } + + public void setB(int b) { + this.b = b; + } + + public int getC() { + return c; + } + + public void setC(int c) { + this.c = c; + } + + public int getCountOfFields() { + return countOfFields; + } + + public void setCountOfFields(int countOfFields) { + this.countOfFields = countOfFields; + } + + public int getCountOfMethods() { + return countOfMethods; + } + + public void setCountOfMethods(int countOfMethods) { + this.countOfMethods = countOfMethods; + } + + public List getNameOfMethods() { + return nameOfMethods; + } + + public void setNameOfMethods(List nameOfMethods) { + this.nameOfMethods = nameOfMethods; + } + + public String getSuperClassName() { + return superClassName; + } + + public void setSuperClassName(String superClassName) { + this.superClassName = superClassName; + } + + public String getClassName() { + return className; + } + + public void setClassName(String className) { + this.className = className; + } + + public Set getNameOfInterfaces() { + return nameOfInterfaces; + } + + public void setNameOfInterfaces(Set nameOfInterfaces) { + this.nameOfInterfaces = nameOfInterfaces; + } + + public void increaseA() { + a++; + } + + public void increaseB() { + b++; + } + + public void increaseC() { + c++; + } +} diff --git a/src/main/java/org/example/visitor/ABCMetrics.java b/src/main/java/org/example/visitor/ABCMetrics.java new file mode 100644 index 0000000..97a182b --- /dev/null +++ b/src/main/java/org/example/visitor/ABCMetrics.java @@ -0,0 +1,95 @@ +package org.example.visitor; + +import org.example.util.Metrics; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import static org.objectweb.asm.Opcodes.ASM8; +import static org.objectweb.asm.Opcodes.ASM9; + +public class ABCMetrics extends MethodVisitor { + + private Metrics metrics; + + + public ABCMetrics(Metrics metrics) { + super(ASM9); + this.metrics = metrics; + } + + /** + * Различные присвоения значений локальным переменным + */ + @Override + public void visitVarInsn(int opcode, int varIndex) { + + switch (opcode) { + case Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, Opcodes.ASTORE -> metrics.increaseA(); + } + + super.visitVarInsn(opcode, varIndex); + } + + /** + * Поиск методов (включая конструкторы) + */ + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + metrics.increaseB(); + + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + /** + * anewarray - создание массивов ссылочных типов + * multianewarray - многомерные массивы + */ + @Override + public void visitTypeInsn(int opcode, String type) { + switch (opcode) { + case Opcodes.ANEWARRAY, Opcodes.MULTIANEWARRAY -> metrics.increaseB(); + } + + super.visitTypeInsn(opcode, type); + } + + /** + * Создание массивов типо int[n] + */ + @Override + public void visitIntInsn(int opcode, int operand) { + if (opcode == Opcodes.NEWARRAY) { + metrics.increaseB(); + } + + super.visitIntInsn(opcode, operand); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + metrics.increaseC(); + + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + metrics.increaseC(); + + super.visitTryCatchBlock(start, end, handler, type); + } + + /** + * Обрабатываем switch + */ + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + for (int ignored : keys) + metrics.increaseC(); + + super.visitLookupSwitchInsn(dflt, keys, labels); + } + +} diff --git a/src/main/java/org/example/visitor/ClassCalculateMetrics.java b/src/main/java/org/example/visitor/ClassCalculateMetrics.java new file mode 100644 index 0000000..581ffa5 --- /dev/null +++ b/src/main/java/org/example/visitor/ClassCalculateMetrics.java @@ -0,0 +1,59 @@ +package org.example.visitor; + +import org.example.util.Metrics; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import static org.objectweb.asm.Opcodes.ASM8; +import static org.objectweb.asm.Opcodes.ASM9; + +public class ClassCalculateMetrics extends ClassVisitor { + + private Metrics metrics; + + public ClassCalculateMetrics() { + super(ASM9); + metrics = new Metrics(); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + + metrics.setClassName(name); + metrics.setSuperClassName(superName); + metrics.setNameOfInterfaces(new HashSet<>(Arrays.asList(interfaces))); + + } + + @Override + public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { + + metrics.increaseCountOfFields(); + return null; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + metrics.increaseCountOfMethods(); + + // Здесь мы хотим получить сигнатуру метода без возвращаемого значения (оно не входит в неё) + // Например bubbleSort([II) = bubbleSort(int[] arr, int n) + String signatureMethod = name + descriptor.substring(descriptor.indexOf('('), descriptor.lastIndexOf(')') + 1); + + if(!signatureMethod.equals("")) { + metrics.getNameOfMethods().add(signatureMethod); + } + + return new ABCMetrics(metrics); + + } + + public Metrics getMetrics() { + return metrics; + } +}