From 77fc57e7c8f0ac0798af9d6f13a78f9922a64472 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Mon, 10 Nov 2025 17:29:15 +0000 Subject: [PATCH 1/4] 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 44c4ec0fd91165e654bc7091f96c6297a7e9d948 Mon Sep 17 00:00:00 2001 From: Daniil Vinichenko Date: Thu, 4 Dec 2025 17:47:39 +0300 Subject: [PATCH 2/4] init commit --- build.gradle.kts | 1 + config.json | 5 + metrics.json | 7 + src/main/java/org/example/ClassMetrics.java | 50 +++++++ src/main/java/org/example/Example.java | 2 +- src/main/java/org/example/JarAnalyzer.java | 31 ++++ .../java/org/example/MetricsAnalyzer.java | 104 ++++++++++++++ .../java/org/example/consoler/Config.java | 23 +++ .../java/org/example/consoler/Resulter.java | 40 ++++++ .../{util => misc}/ByteCodePrinter.java | 2 +- .../{util => misc}/CheckFrameAnalyzer.java | 2 +- .../example/misc/ClassHierarchyBuilder.java | 56 ++++++++ .../java/org/example/misc/ConfigReader.java | 35 +++++ .../java/org/example/misc/JsonMarshaler.java | 21 +++ .../visitor\321\213/ABCMethodVisitor.java" | 117 +++++++++++++++ .../visitor\321\213/ABCMetricVisitor.java" | 81 +++++++++++ .../visitor\321\213/ClassPrinter.java" | 21 ++- .../visitor\321\213/DepthVisitor.java" | 53 +++++++ .../FieldsCounterVisitor.java" | 77 ++++++++++ .../visitor\321\213/MultiVisitor.java" | 136 ++++++++++++++++++ .../OverriddenMethodsVisitor.java" | 116 +++++++++++++++ 21 files changed, 976 insertions(+), 4 deletions(-) create mode 100644 config.json create mode 100644 metrics.json create mode 100644 src/main/java/org/example/ClassMetrics.java create mode 100644 src/main/java/org/example/JarAnalyzer.java create mode 100644 src/main/java/org/example/MetricsAnalyzer.java create mode 100644 src/main/java/org/example/consoler/Config.java create mode 100644 src/main/java/org/example/consoler/Resulter.java rename src/main/java/org/example/{util => misc}/ByteCodePrinter.java (99%) rename src/main/java/org/example/{util => misc}/CheckFrameAnalyzer.java (99%) create mode 100644 src/main/java/org/example/misc/ClassHierarchyBuilder.java create mode 100644 src/main/java/org/example/misc/ConfigReader.java create mode 100644 src/main/java/org/example/misc/JsonMarshaler.java create mode 100644 "src/main/java/org/example/visitor\321\213/ABCMethodVisitor.java" create mode 100644 "src/main/java/org/example/visitor\321\213/ABCMetricVisitor.java" rename src/main/java/org/example/visitor/ClassPrinter.java => "src/main/java/org/example/visitor\321\213/ClassPrinter.java" (63%) create mode 100644 "src/main/java/org/example/visitor\321\213/DepthVisitor.java" create mode 100644 "src/main/java/org/example/visitor\321\213/FieldsCounterVisitor.java" create mode 100644 "src/main/java/org/example/visitor\321\213/MultiVisitor.java" create mode 100644 "src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" diff --git a/build.gradle.kts b/build.gradle.kts index 2b92afd..2ca30db 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.google.code.gson:gson:2.10.1") testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/config.json b/config.json new file mode 100644 index 0000000..0226c4b --- /dev/null +++ b/config.json @@ -0,0 +1,5 @@ +{ + "inputJar": "src/main/resources/sample.jar", + "outputJson": "metrics.json" +} + diff --git a/metrics.json b/metrics.json new file mode 100644 index 0000000..e1f863f --- /dev/null +++ b/metrics.json @@ -0,0 +1,7 @@ +{ + "Max Depth": 1, + "Average Depth": 0.63, + "Average ABC Metric": 1.15, + "Average Overridden Methods": 0.00, + "Average Fields per Class": 0.38 +} diff --git a/src/main/java/org/example/ClassMetrics.java b/src/main/java/org/example/ClassMetrics.java new file mode 100644 index 0000000..53db538 --- /dev/null +++ b/src/main/java/org/example/ClassMetrics.java @@ -0,0 +1,50 @@ +package org.example; + +public class ClassMetrics { + private final String className; + private int depth; + private double abcMetric; + private int overriddenMethodsCount; + private int fieldsCount; + + public ClassMetrics(String className) { + this.className = className; + this.depth = 0; + this.abcMetric = 0.0; + this.overriddenMethodsCount = 0; + this.fieldsCount = 0; + } + + public int getDepth() { + return depth; + } + + public void setDepth(int depth) { + this.depth = depth; + } + + public double getAbcMetric() { + return abcMetric; + } + + public void setAbcMetric(double abcMetric) { + this.abcMetric = abcMetric; + } + + public int getOverriddenMethodsCount() { + return overriddenMethodsCount; + } + + public void setOverriddenMethodsCount(int overriddenMethodsCount) { + this.overriddenMethodsCount = overriddenMethodsCount; + } + + public int getFieldsCount() { + return fieldsCount; + } + + public void setFieldsCount(int fieldsCount) { + this.fieldsCount = fieldsCount; + } +} + diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java index 52d0abe..61601c9 100644 --- a/src/main/java/org/example/Example.java +++ b/src/main/java/org/example/Example.java @@ -1,6 +1,6 @@ package org.example; -import org.example.visitor.ClassPrinter; +import org.example.visitorы.ClassPrinter; import org.objectweb.asm.ClassReader; import java.io.IOException; diff --git a/src/main/java/org/example/JarAnalyzer.java b/src/main/java/org/example/JarAnalyzer.java new file mode 100644 index 0000000..7b18d1a --- /dev/null +++ b/src/main/java/org/example/JarAnalyzer.java @@ -0,0 +1,31 @@ +package org.example; + +import org.objectweb.asm.ClassReader; + +import java.io.IOException; +import java.util.Enumeration; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class JarAnalyzer { + private final String jarPath; + + public JarAnalyzer(String jarPath) { + this.jarPath = jarPath; + } + + public void processClasses(org.objectweb.asm.ClassVisitor visitor) throws IOException { + try (JarFile jarFile = new JarFile(jarPath)) { + Enumeration entries = jarFile.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + if (entry.getName().endsWith(".class")) { + ClassReader classReader = new ClassReader(jarFile.getInputStream(entry)); + classReader.accept(visitor, ClassReader.EXPAND_FRAMES); + } + } + } + } +} + diff --git a/src/main/java/org/example/MetricsAnalyzer.java b/src/main/java/org/example/MetricsAnalyzer.java new file mode 100644 index 0000000..41edcca --- /dev/null +++ b/src/main/java/org/example/MetricsAnalyzer.java @@ -0,0 +1,104 @@ +package org.example; + +import org.example.consoler.Config; +import org.example.consoler.Resulter; +import org.example.misc.ClassHierarchyBuilder; +import org.example.misc.ConfigReader; +import org.example.misc.JsonMarshaler; +import org.example.visitorы.*; + +import java.io.IOException; +import java.util.List; + +public class MetricsAnalyzer { + + public static void main(String[] args) { + try { + Config config = ConfigReader.readConfig(); + String inputJar = config.getInputJar(); + String outputJson = config.getOutputJson(); + Resulter metrics = analyzeJar(inputJar); + print(metrics); + JsonMarshaler.write(metrics, outputJson); + } catch (Exception e) { + System.err.println("Error: " + e.getMessage()); + e.printStackTrace(); + } + } + + private static Resulter analyzeJar(String jarPath) throws IOException { + JarAnalyzer jarAnalyzer = new JarAnalyzer(jarPath); + + ClassHierarchyBuilder hierarchyBuilder = new ClassHierarchyBuilder(); + jarAnalyzer.processClasses(hierarchyBuilder); + + List classMetricsList; + + MultiVisitor multiVisitor = getMultiVisitor(hierarchyBuilder); + + jarAnalyzer.processClasses(multiVisitor); + classMetricsList = multiVisitor.getClassMetricsList(); + + return calculateAggregatedMetrics(classMetricsList); + } + + private static MultiVisitor getMultiVisitor(ClassHierarchyBuilder hierarchyBuilder) { + DepthVisitor inheritanceVisitor = new DepthVisitor(hierarchyBuilder); + ABCMetricVisitor abcVisitor = new ABCMetricVisitor(); + OverriddenMethodsVisitor overriddenVisitor = new OverriddenMethodsVisitor(hierarchyBuilder); + FieldsCounterVisitor fieldsVisitor = new FieldsCounterVisitor(); + + return new MultiVisitor( + inheritanceVisitor, abcVisitor, overriddenVisitor, fieldsVisitor + ); + } + + private static Resulter calculateAggregatedMetrics( + List classMetricsList) { + + if (classMetricsList.isEmpty()) { + return new Resulter(0, 0.0, 0.0, 0.0, 0.0); + } + + int maxInheritanceDepth = 0; + double totalInheritanceDepth = 0; + double totalABCMetric = 0; + double totalOverriddenMethods = 0; + double totalFields = 0; + int classCount = 0; + + for (ClassMetrics cm : classMetricsList) { + if (cm.getDepth() > maxInheritanceDepth) { + maxInheritanceDepth = cm.getDepth(); + } + totalInheritanceDepth += cm.getDepth(); + totalABCMetric += cm.getAbcMetric(); + totalOverriddenMethods += cm.getOverriddenMethodsCount(); + totalFields += cm.getFieldsCount(); + classCount++; + } + + double avgInheritanceDepth = totalInheritanceDepth / classCount; + double avgABCMetric = totalABCMetric / classCount; + double avgOverriddenMethods = totalOverriddenMethods / classCount; + double avgFieldsPerClass = totalFields / classCount; + + return new Resulter( + maxInheritanceDepth, + avgInheritanceDepth, + avgABCMetric, + avgOverriddenMethods, + avgFieldsPerClass + ); + } + + private static void print(Resulter metrics) { + System.out.println("====Metrics====\n"); + System.out.println("Maximum Depth: " + metrics.getMaxDepth()); + System.out.println("Average Depth: " + String.format("%.2f", metrics.getAvgDepth())); + System.out.println("Average ABC Metric: " + String.format("%.2f", metrics.getAvgABCMetric())); + System.out.println("Average Overridden Methods: " + String.format("%.2f", metrics.getAvgOverriddenMethods())); + System.out.println("Average Fields per Class: " + String.format("%.2f", metrics.getAvgFieldsPerClass())); + } +} + diff --git a/src/main/java/org/example/consoler/Config.java b/src/main/java/org/example/consoler/Config.java new file mode 100644 index 0000000..2d069a1 --- /dev/null +++ b/src/main/java/org/example/consoler/Config.java @@ -0,0 +1,23 @@ +package org.example.consoler; + +public class Config { + private String inputJar; + private String outputJson; + + public Config() { + } + + public Config(String inputJar, String outputJson) { + this.inputJar = inputJar; + this.outputJson = outputJson; + } + + public String getInputJar() { + return inputJar; + } + + public String getOutputJson() { + return outputJson; + } +} + diff --git a/src/main/java/org/example/consoler/Resulter.java b/src/main/java/org/example/consoler/Resulter.java new file mode 100644 index 0000000..b0461b7 --- /dev/null +++ b/src/main/java/org/example/consoler/Resulter.java @@ -0,0 +1,40 @@ +package org.example.consoler; + +public class Resulter { + private final int maxDepth; + private final double avgDepth; + private final double avgABCMetric; + private final double avgOverriddenMethods; + private final double avgFieldsPerClass; + + public Resulter(int maxDepth, double avgDepth, + double avgABCMetric, double avgOverriddenMethods, + double avgFieldsPerClass) { + this.maxDepth = maxDepth; + this.avgDepth = avgDepth; + this.avgABCMetric = avgABCMetric; + this.avgOverriddenMethods = avgOverriddenMethods; + this.avgFieldsPerClass = avgFieldsPerClass; + } + + public int getMaxDepth() { + return maxDepth; + } + + public double getAvgDepth() { + return avgDepth; + } + + public double getAvgABCMetric() { + return avgABCMetric; + } + + public double getAvgOverriddenMethods() { + return avgOverriddenMethods; + } + + public double getAvgFieldsPerClass() { + return avgFieldsPerClass; + } +} + diff --git a/src/main/java/org/example/util/ByteCodePrinter.java b/src/main/java/org/example/misc/ByteCodePrinter.java similarity index 99% rename from src/main/java/org/example/util/ByteCodePrinter.java rename to src/main/java/org/example/misc/ByteCodePrinter.java index 3d1ca38..24ae433 100644 --- a/src/main/java/org/example/util/ByteCodePrinter.java +++ b/src/main/java/org/example/misc/ByteCodePrinter.java @@ -1,4 +1,4 @@ -package org.example.util; +package org.example.misc; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; diff --git a/src/main/java/org/example/util/CheckFrameAnalyzer.java b/src/main/java/org/example/misc/CheckFrameAnalyzer.java similarity index 99% rename from src/main/java/org/example/util/CheckFrameAnalyzer.java rename to src/main/java/org/example/misc/CheckFrameAnalyzer.java index 5231ca7..900bd8d 100644 --- a/src/main/java/org/example/util/CheckFrameAnalyzer.java +++ b/src/main/java/org/example/misc/CheckFrameAnalyzer.java @@ -1,4 +1,4 @@ -package org.example.util; +package org.example.misc; // ASM: a very small and fast Java bytecode manipulation framework // Copyright (c) 2000-2011 INRIA, France Telecom diff --git a/src/main/java/org/example/misc/ClassHierarchyBuilder.java b/src/main/java/org/example/misc/ClassHierarchyBuilder.java new file mode 100644 index 0000000..e080dfa --- /dev/null +++ b/src/main/java/org/example/misc/ClassHierarchyBuilder.java @@ -0,0 +1,56 @@ +package org.example.misc; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class ClassHierarchyBuilder extends ClassVisitor { + private final Map classToParent = new HashMap<>(); + private final Map depthCache = new HashMap<>(); + + public ClassHierarchyBuilder() { + super(Opcodes.ASM9); + } + + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + // Игнорируем интерфейсы и массивы + if ((access & Opcodes.ACC_INTERFACE) == 0 && superName != null) { + classToParent.put(name, superName); + } + } + + public Map getClassToParent() { + return classToParent; + } + + public int getInheritanceDepth(String className) { + if (depthCache.containsKey(className)) { + return depthCache.get(className); + } + + int depth = calculateDepth(className, new HashSet<>()); + depthCache.put(className, depth); + return depth; + } + + private int calculateDepth(String className, Set visited) { + if (visited.contains(className)) { + return 0; + } + visited.add(className); + + String parent = classToParent.get(className); + + if (parent == null || parent.equals("java/lang/Object")) { + return 0; + } + return 1 + calculateDepth(parent, visited); + } +} + diff --git a/src/main/java/org/example/misc/ConfigReader.java b/src/main/java/org/example/misc/ConfigReader.java new file mode 100644 index 0000000..c3f128f --- /dev/null +++ b/src/main/java/org/example/misc/ConfigReader.java @@ -0,0 +1,35 @@ +package org.example.misc; + +import com.google.gson.Gson; +import org.example.consoler.Config; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class ConfigReader { + private static final String DEFAULT_CONFIG_PATH = "config.json"; + private static final Gson gson = new Gson(); + + public static Config readConfig() throws IOException { + return readConfig(DEFAULT_CONFIG_PATH); + } + + public static Config readConfig(String configPath) throws IOException { + Path path = Paths.get(configPath); + if (!Files.exists(path)) { + throw new IOException("Config file not found: " + configPath); + } + + String content = Files.readString(path); + Config config = gson.fromJson(content, Config.class); + + if (config.getInputJar() == null || config.getOutputJson() == null) { + throw new IOException("Config must contain 'inputJar' and 'outputJson' fields"); + } + + return config; + } +} + diff --git a/src/main/java/org/example/misc/JsonMarshaler.java b/src/main/java/org/example/misc/JsonMarshaler.java new file mode 100644 index 0000000..132b8b1 --- /dev/null +++ b/src/main/java/org/example/misc/JsonMarshaler.java @@ -0,0 +1,21 @@ +package org.example.misc; + +import org.example.consoler.Resulter; + +import java.io.FileWriter; +import java.io.IOException; + +public class JsonMarshaler { + public static void write(Resulter metrics, String outputPath) throws IOException { + try (FileWriter writer = new FileWriter(outputPath)) { + writer.write("{\n"); + writer.write(" \"Max Depth\": " + metrics.getMaxDepth() + ",\n"); + writer.write(" \"Average Depth\": " + String.format("%.2f", metrics.getAvgDepth()) + ",\n"); + writer.write(" \"Average ABC Metric\": " + String.format("%.2f", metrics.getAvgABCMetric()) + ",\n"); + writer.write(" \"Average Overridden Methods\": " + String.format("%.2f", metrics.getAvgOverriddenMethods()) + ",\n"); + writer.write(" \"Average Fields per Class\": " + String.format("%.2f", metrics.getAvgFieldsPerClass()) + "\n"); + writer.write("}\n"); + } + } +} + diff --git "a/src/main/java/org/example/visitor\321\213/ABCMethodVisitor.java" "b/src/main/java/org/example/visitor\321\213/ABCMethodVisitor.java" new file mode 100644 index 0000000..b31c4de --- /dev/null +++ "b/src/main/java/org/example/visitor\321\213/ABCMethodVisitor.java" @@ -0,0 +1,117 @@ +package org.example.visitorы; + +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.List; +import java.util.Map; + +/** + * {@link MethodVisitor}, который подсчитывает компоненты ABC-метрики + * (assignments, branches, conditions) для одного метода. + */ +class ABCMethodVisitor extends MethodVisitor { + private final String className; + private final Map> classABCMetrics; + private int assignments = 0; + private int branches = 0; + private int conditions = 0; + + /** + * Создает визитор для одного метода. + * + * @param api версия ASM API + * @param className имя класса-владельца метода + * @param classABCMetrics накопитель ABC-метрик по классам + */ + public ABCMethodVisitor(int api, String className, Map> classABCMetrics) { + super(api); + this.className = className; + this.classABCMetrics = classABCMetrics; + } + + /** + * Обрабатывает инструкции работы с локальными переменными и учитывает сохранения (store). + */ + @Override + public void visitVarInsn(int opcode, int var) { + if (opcode == Opcodes.ISTORE || opcode == Opcodes.LSTORE || + opcode == Opcodes.FSTORE || opcode == Opcodes.DSTORE || + opcode == Opcodes.ASTORE) { + assignments++; + } + } + + /** + * Обрабатывает инструкцию инкремента локальной переменной как присваивание. + */ + @Override + public void visitIincInsn(int var, int increment) { + assignments++; + } + + /** + * Обрабатывает условные и безусловные переходы, разделяя их на ветвления и условия. + */ + @Override + public void visitJumpInsn(int opcode, Label label) { + if (opcode == Opcodes.GOTO || opcode == Opcodes.JSR) { + branches++; + } + else if (opcode == Opcodes.IFEQ || opcode == Opcodes.IFNE || + opcode == Opcodes.IFLT || opcode == Opcodes.IFGE || + opcode == Opcodes.IFGT || opcode == Opcodes.IFLE || + opcode == Opcodes.IF_ICMPEQ || opcode == Opcodes.IF_ICMPNE || + opcode == Opcodes.IF_ICMPLT || opcode == Opcodes.IF_ICMPGE || + opcode == Opcodes.IF_ICMPGT || opcode == Opcodes.IF_ICMPLE || + opcode == Opcodes.IF_ACMPEQ || opcode == Opcodes.IF_ACMPNE || + opcode == Opcodes.IFNULL || opcode == Opcodes.IFNONNULL) { + conditions++; + } + } + + /** + * Обрабатывает табличный switch как одну ветку и одно условие. + */ + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + branches++; + conditions++; + } + + /** + * Обрабатывает switch с произвольными ключами как одну ветку и одно условие. + */ + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + branches++; + conditions++; + } + + /** + * Обрабатывает простые инструкции, в частности {@code RET} как ветвление. + */ + @Override + public void visitInsn(int opcode) { + if (opcode == Opcodes.RET) { + branches++; + } + } + + /** + * В конце метода вычисляет итоговую ABC-метрику и сохраняет её для соответствующего класса. + */ + @Override + public void visitEnd() { + // sqrt(A^2 + B^2 + C^2) + double abc = Math.sqrt(assignments * assignments + branches * branches + conditions * conditions); + + List abcList = classABCMetrics.get(className); + if (abcList != null) { + abcList.add(abc); + } + } +} + + diff --git "a/src/main/java/org/example/visitor\321\213/ABCMetricVisitor.java" "b/src/main/java/org/example/visitor\321\213/ABCMetricVisitor.java" new file mode 100644 index 0000000..4805d3d --- /dev/null +++ "b/src/main/java/org/example/visitor\321\213/ABCMetricVisitor.java" @@ -0,0 +1,81 @@ +package org.example.visitorы; + +import org.example.ClassMetrics; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.HashMap; +import java.util.Map; + +/** + * {@link ClassVisitor}, который вычисляет ABC-метрику для методов классов + * (Assignments, Branches, Conditions) и агрегирует её по классам. + */ +public class ABCMetricVisitor extends ClassVisitor { + private final Map metricsMap; + private String currentClassName; + private final Map> classABCMetrics = new HashMap<>(); + + public ABCMetricVisitor() { + super(Opcodes.ASM9); + this.metricsMap = new HashMap<>(); + } + + /** + * Вызывается для каждого посещаемого класса. + * Пропускает интерфейсы и инициализирует накопители метрик. + */ + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + // Игнорируем интерфейсы + if ((access & Opcodes.ACC_INTERFACE) != 0) { + return; + } + + currentClassName = name; + metricsMap.computeIfAbsent(name, ClassMetrics::new); + classABCMetrics.put(name, new java.util.ArrayList<>()); + } + + /** + * Вызывается для каждого метода класса. + * Возвращает {@link MethodVisitor}, который считает ABC-метрику по инструкциям метода. + */ + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, + String signature, String[] exceptions) { + // Игнорируем автогенерируемые методы и конструкторы классов + if ((access & Opcodes.ACC_SYNTHETIC) != 0 || + name.equals("") || name.equals("")) { + return null; + } + + return new ABCMethodVisitor(api, currentClassName, classABCMetrics); + } + + /** + * Завершает обработку текущего класса и вычисляет среднее значение ABC-метрики по его методам. + */ + @Override + public void visitEnd() { + ClassMetrics metrics = metricsMap.get(currentClassName); + if (metrics != null) { + java.util.List abcList = classABCMetrics.get(currentClassName); + if (abcList != null && !abcList.isEmpty()) { + double sum = 0.0; + for (Double abc : abcList) { + sum += abc; + } + metrics.setAbcMetric(sum / abcList.size()); + } else { + metrics.setAbcMetric(0.0); + } + } + } + + public Map getMetricsMap() { + return metricsMap; + } +} diff --git a/src/main/java/org/example/visitor/ClassPrinter.java "b/src/main/java/org/example/visitor\321\213/ClassPrinter.java" similarity index 63% rename from src/main/java/org/example/visitor/ClassPrinter.java rename to "src/main/java/org/example/visitor\321\213/ClassPrinter.java" index ba1ab9f..3557abc 100644 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ "b/src/main/java/org/example/visitor\321\213/ClassPrinter.java" @@ -1,14 +1,24 @@ -package org.example.visitor; +package org.example.visitorы; import org.objectweb.asm.*; import static org.objectweb.asm.Opcodes.ASM8; +/** + * Простой {@link ClassVisitor}, который печатает структуру класса: + * имя, поля и методы. Используется для отладки и демонстрации работы ASM. + */ public class ClassPrinter extends ClassVisitor { + /** + * Создает визитор печати структуры класса. + */ public ClassPrinter() { super(ASM8); } + /** + * Печатает заголовок класса и его родителя. + */ public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { System.out.println("\n" + name + " extends " + superName + " {"); } @@ -29,16 +39,25 @@ public void visitAttribute(Attribute attr) { public void visitInnerClass(String name, String outerName, String innerName, int access) { } + /** + * Печатает объявление поля. + */ public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { System.out.println(" " + desc + " " + name); return null; } + /** + * Печатает сигнатуру метода. + */ public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { System.out.println(" " + name + desc); return null; } + /** + * Закрывает вывод класса фигурной скобкой. + */ public void visitEnd() { System.out.println("}"); } diff --git "a/src/main/java/org/example/visitor\321\213/DepthVisitor.java" "b/src/main/java/org/example/visitor\321\213/DepthVisitor.java" new file mode 100644 index 0000000..a76e416 --- /dev/null +++ "b/src/main/java/org/example/visitor\321\213/DepthVisitor.java" @@ -0,0 +1,53 @@ +package org.example.visitorы; + +import org.example.ClassMetrics; +import org.example.misc.ClassHierarchyBuilder; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.Map; + +/** + * {@link ClassVisitor}, который рассчитывает глубину наследования + * для каждого класса на основе построенной иерархии. + */ +public class DepthVisitor extends ClassVisitor { + private final ClassHierarchyBuilder hierarchyBuilder; + private final Map metricsMap; + + /** + * Создает визитор вычисления глубины наследования. + * + * @param hierarchyBuilder построитель иерархии классов + */ + public DepthVisitor(ClassHierarchyBuilder hierarchyBuilder) { + super(Opcodes.ASM9); + this.hierarchyBuilder = hierarchyBuilder; + this.metricsMap = new java.util.HashMap<>(); + } + + /** + * Для каждого класса запрашивает глубину наследования у + * {@link ClassHierarchyBuilder} и сохраняет её в {@link ClassMetrics}. + */ + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + // Игнорируем интерфейсы + if ((access & Opcodes.ACC_INTERFACE) != 0) { + return; + } + + ClassMetrics metrics = metricsMap.computeIfAbsent(name, ClassMetrics::new); + int depth = hierarchyBuilder.getInheritanceDepth(name); + metrics.setDepth(depth); + } + + /** + * Возвращает карту метрик по классам. + */ + public Map getMetricsMap() { + return metricsMap; + } +} + diff --git "a/src/main/java/org/example/visitor\321\213/FieldsCounterVisitor.java" "b/src/main/java/org/example/visitor\321\213/FieldsCounterVisitor.java" new file mode 100644 index 0000000..dae70db --- /dev/null +++ "b/src/main/java/org/example/visitor\321\213/FieldsCounterVisitor.java" @@ -0,0 +1,77 @@ +package org.example.visitorы; + +import org.example.ClassMetrics; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.HashMap; +import java.util.Map; + +/** + * {@link ClassVisitor}, который считает количество полей в каждом классе, + * игнорируя синтетические поля. + */ +public class FieldsCounterVisitor extends ClassVisitor { + private final Map metricsMap; + private String currentClassName; + private int fieldCount = 0; + + /** + * Создает визитор подсчета полей. + */ + public FieldsCounterVisitor() { + super(Opcodes.ASM9); + this.metricsMap = new HashMap<>(); + } + + /** + * Инициализирует счетчики для очередного класса. + * Интерфейсы не учитываются. + */ + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + // Игнорируем интерфейсы + if ((access & Opcodes.ACC_INTERFACE) != 0) { + return; + } + + currentClassName = name; + fieldCount = 0; + metricsMap.computeIfAbsent(name, ClassMetrics::new); + } + + /** + * Обрабатывает объявление поля и увеличивает счетчик + * для несинтетических полей. + */ + @Override + public FieldVisitor visitField(int access, String name, String descriptor, + String signature, Object value) { + // Игнорируем автогенерируемые поля (например, для внутренних классов) + if ((access & Opcodes.ACC_SYNTHETIC) == 0) { + fieldCount++; + } + return null; + } + + /** + * Сохраняет итоговое количество полей в {@link ClassMetrics} для класса. + */ + @Override + public void visitEnd() { + ClassMetrics metrics = metricsMap.get(currentClassName); + if (metrics != null) { + metrics.setFieldsCount(fieldCount); + } + } + + /** + * Возвращает карту метрик по классам. + */ + public Map getMetricsMap() { + return metricsMap; + } +} + diff --git "a/src/main/java/org/example/visitor\321\213/MultiVisitor.java" "b/src/main/java/org/example/visitor\321\213/MultiVisitor.java" new file mode 100644 index 0000000..6a08e6d --- /dev/null +++ "b/src/main/java/org/example/visitor\321\213/MultiVisitor.java" @@ -0,0 +1,136 @@ +package org.example.visitorы; + +import org.example.ClassMetrics; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Композитный {@link ClassVisitor}, который делегирует события нескольким + * специализированным визиторам и затем объединяет их метрики по классам. + */ +public class MultiVisitor extends ClassVisitor { + private final DepthVisitor depthVisitor; + private final ABCMetricVisitor abcVisitor; + private final OverriddenMethodsVisitor overriddenVisitor; + private final FieldsCounterVisitor fieldsVisitor; + + /** + * Создает комбинированный визитор поверх переданных визиторов метрик. + */ + public MultiVisitor( + DepthVisitor depthVisitor, + ABCMetricVisitor abcVisitor, + OverriddenMethodsVisitor overriddenVisitor, + FieldsCounterVisitor fieldsVisitor) { + super(Opcodes.ASM9); + this.depthVisitor = depthVisitor; + this.abcVisitor = abcVisitor; + this.overriddenVisitor = overriddenVisitor; + this.fieldsVisitor = fieldsVisitor; + } + + /** + * Передает информацию о классе всем внутренним визиторам, + * исключая интерфейсы. + */ + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + // Игнорируем интерфейсы + if ((access & Opcodes.ACC_INTERFACE) != 0) { + return; + } + + depthVisitor.visit(version, access, name, signature, superName, interfaces); + abcVisitor.visit(version, access, name, signature, superName, interfaces); + overriddenVisitor.visit(version, access, name, signature, superName, interfaces); + fieldsVisitor.visit(version, access, name, signature, superName, interfaces); + } + + /** + * Делегирует обработку полей визитору подсчета полей. + */ + @Override + public org.objectweb.asm.FieldVisitor visitField(int access, String name, String descriptor, + String signature, Object value) { + fieldsVisitor.visitField(access, name, descriptor, signature, value); + return null; + } + + /** + * Делегирует обработку методов визиторам ABC-метрики и + * подсчета переопределенных методов. + */ + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, + String signature, String[] exceptions) { + MethodVisitor abcMethodVisitor = abcVisitor.visitMethod(access, name, descriptor, signature, exceptions); + overriddenVisitor.visitMethod(access, name, descriptor, signature, exceptions); + return abcMethodVisitor; + } + + /** + * Завершает работу всех внутренних визиторов. + */ + @Override + public void visitEnd() { + depthVisitor.visitEnd(); + abcVisitor.visitEnd(); + overriddenVisitor.visitEnd(); + fieldsVisitor.visitEnd(); + } + + /** + * Объединяет результаты всех визиторов в единый список метрик по классам. + */ + public List getClassMetricsList() { + Map depthMap = depthVisitor.getMetricsMap(); + Map abcMap = abcVisitor.getMetricsMap(); + Map overriddenMap = overriddenVisitor.getMetricsMap(); + Map fieldsMap = fieldsVisitor.getMetricsMap(); + + List result = new ArrayList<>(); + + Set allClasses = new HashSet<>(); + allClasses.addAll(depthMap.keySet()); + allClasses.addAll(abcMap.keySet()); + allClasses.addAll(overriddenMap.keySet()); + allClasses.addAll(fieldsMap.keySet()); + + for (String className : allClasses) { + ClassMetrics metrics = new ClassMetrics(className); + + ClassMetrics depthMetrics = depthMap.get(className); + if (depthMetrics != null) { + metrics.setDepth(depthMetrics.getDepth()); + } + + ClassMetrics abcMetrics = abcMap.get(className); + if (abcMetrics != null) { + metrics.setAbcMetric(abcMetrics.getAbcMetric()); + } + + ClassMetrics overriddenMetrics = overriddenMap.get(className); + if (overriddenMetrics != null) { + metrics.setOverriddenMethodsCount(overriddenMetrics.getOverriddenMethodsCount()); + } + + ClassMetrics fieldsMetrics = fieldsMap.get(className); + if (fieldsMetrics != null) { + metrics.setFieldsCount(fieldsMetrics.getFieldsCount()); + } + + result.add(metrics); + } + + return result; + } +} + diff --git "a/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" "b/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" new file mode 100644 index 0000000..91bc7db --- /dev/null +++ "b/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" @@ -0,0 +1,116 @@ +package org.example.visitorы; + +import org.example.ClassMetrics; +import org.example.misc.ClassHierarchyBuilder; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * {@link ClassVisitor}, который определяет количество методов, + * переопределяющих методы родительских классов. + */ +public class OverriddenMethodsVisitor extends ClassVisitor { + private final ClassHierarchyBuilder hierarchyBuilder; + private final Map metricsMap; + private final Map> classMethods = new HashMap<>(); + private String currentClassName; + private java.util.Set currentMethods = new java.util.HashSet<>(); + + /** + * Создает визитор подсчета переопределенных методов. + * + * @param hierarchyBuilder построитель иерархии классов для поиска родителей + */ + public OverriddenMethodsVisitor(ClassHierarchyBuilder hierarchyBuilder) { + super(Opcodes.ASM9); + this.hierarchyBuilder = hierarchyBuilder; + this.metricsMap = new HashMap<>(); + } + + /** + * Подготавливает коллекции для обработки нового класса. + * Интерфейсы игнорируются. + */ + @Override + public void visit(int version, int access, String name, String signature, + String superName, String[] interfaces) { + // Игнорируем интерфейсы + if ((access & Opcodes.ACC_INTERFACE) != 0) { + return; + } + + currentClassName = name; + currentMethods = new java.util.HashSet<>(); + metricsMap.computeIfAbsent(name, ClassMetrics::new); + } + + /** + * Фиксирует сигнатуры пользовательских методов (не синтетических, + * не конструкторов, не статических) для дальнейшего сравнения с родителями. + */ + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, + String signature, String[] exceptions) { + // Игнорируем автогенерируемые методы, конструкторы и статические методы + if ((access & Opcodes.ACC_SYNTHETIC) != 0 || + (access & Opcodes.ACC_STATIC) != 0 || + name.equals("") || name.equals("")) { + return null; + } + + String methodSignature = name + descriptor; + currentMethods.add(methodSignature); + + return null; + } + + /** + * После обхода всех методов класса вычисляет количество переопределенных + * методов и сохраняет результат в {@link ClassMetrics}. + */ + @Override + public void visitEnd() { + classMethods.put(currentClassName, currentMethods); + int overriddenCount = countOverriddenMethods(currentClassName, currentMethods); + + ClassMetrics metrics = metricsMap.get(currentClassName); + if (metrics != null) { + metrics.setOverriddenMethodsCount(overriddenCount); + } + } + + /** + * Считает количество методов из {@code methods}, которые уже существуют + * в родительском классе. + */ + private int countOverriddenMethods(String className, Set methods) { + int count = 0; + Map classToParent = hierarchyBuilder.getClassToParent(); + String parent = classToParent.get(className); + + if (parent == null || parent.equals("java/lang/Object")) { + return 0; + } + + Set parentMethods = classMethods.get(parent); + if (parentMethods != null) { + for (String method : methods) { + if (parentMethods.contains(method)) { + count++; + } + } + } + + return count; + } + + public Map getMetricsMap() { + return metricsMap; + } +} + From 982c3bce68c05ca9884bdb7c2c2e2495410b1783 Mon Sep 17 00:00:00 2001 From: Daniil Vinichenko Date: Mon, 8 Dec 2025 20:59:43 +0300 Subject: [PATCH 3/4] some changes --- src/main/java/org/example/MetricsAnalyzer.java | 6 ++---- .../java/org/example/visitor\321\213/ABCMetricVisitor.java" | 5 ----- .../java/org/example/visitor\321\213/DepthVisitor.java" | 5 ----- .../org/example/visitor\321\213/FieldsCounterVisitor.java" | 5 ----- .../example/visitor\321\213/OverriddenMethodsVisitor.java" | 5 ----- 5 files changed, 2 insertions(+), 24 deletions(-) diff --git a/src/main/java/org/example/MetricsAnalyzer.java b/src/main/java/org/example/MetricsAnalyzer.java index 41edcca..1642e98 100644 --- a/src/main/java/org/example/MetricsAnalyzer.java +++ b/src/main/java/org/example/MetricsAnalyzer.java @@ -15,11 +15,9 @@ public class MetricsAnalyzer { public static void main(String[] args) { try { Config config = ConfigReader.readConfig(); - String inputJar = config.getInputJar(); - String outputJson = config.getOutputJson(); - Resulter metrics = analyzeJar(inputJar); + Resulter metrics = analyzeJar(config.getInputJar()); print(metrics); - JsonMarshaler.write(metrics, outputJson); + JsonMarshaler.write(metrics, config.getOutputJson()); } catch (Exception e) { System.err.println("Error: " + e.getMessage()); e.printStackTrace(); diff --git "a/src/main/java/org/example/visitor\321\213/ABCMetricVisitor.java" "b/src/main/java/org/example/visitor\321\213/ABCMetricVisitor.java" index 4805d3d..06b7d31 100644 --- "a/src/main/java/org/example/visitor\321\213/ABCMetricVisitor.java" +++ "b/src/main/java/org/example/visitor\321\213/ABCMetricVisitor.java" @@ -29,11 +29,6 @@ public ABCMetricVisitor() { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - // Игнорируем интерфейсы - if ((access & Opcodes.ACC_INTERFACE) != 0) { - return; - } - currentClassName = name; metricsMap.computeIfAbsent(name, ClassMetrics::new); classABCMetrics.put(name, new java.util.ArrayList<>()); diff --git "a/src/main/java/org/example/visitor\321\213/DepthVisitor.java" "b/src/main/java/org/example/visitor\321\213/DepthVisitor.java" index a76e416..bc94aed 100644 --- "a/src/main/java/org/example/visitor\321\213/DepthVisitor.java" +++ "b/src/main/java/org/example/visitor\321\213/DepthVisitor.java" @@ -33,11 +33,6 @@ public DepthVisitor(ClassHierarchyBuilder hierarchyBuilder) { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - // Игнорируем интерфейсы - if ((access & Opcodes.ACC_INTERFACE) != 0) { - return; - } - ClassMetrics metrics = metricsMap.computeIfAbsent(name, ClassMetrics::new); int depth = hierarchyBuilder.getInheritanceDepth(name); metrics.setDepth(depth); diff --git "a/src/main/java/org/example/visitor\321\213/FieldsCounterVisitor.java" "b/src/main/java/org/example/visitor\321\213/FieldsCounterVisitor.java" index dae70db..58de5fb 100644 --- "a/src/main/java/org/example/visitor\321\213/FieldsCounterVisitor.java" +++ "b/src/main/java/org/example/visitor\321\213/FieldsCounterVisitor.java" @@ -32,11 +32,6 @@ public FieldsCounterVisitor() { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - // Игнорируем интерфейсы - if ((access & Opcodes.ACC_INTERFACE) != 0) { - return; - } - currentClassName = name; fieldCount = 0; metricsMap.computeIfAbsent(name, ClassMetrics::new); diff --git "a/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" "b/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" index 91bc7db..24b28b2 100644 --- "a/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" +++ "b/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" @@ -39,11 +39,6 @@ public OverriddenMethodsVisitor(ClassHierarchyBuilder hierarchyBuilder) { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - // Игнорируем интерфейсы - if ((access & Opcodes.ACC_INTERFACE) != 0) { - return; - } - currentClassName = name; currentMethods = new java.util.HashSet<>(); metricsMap.computeIfAbsent(name, ClassMetrics::new); From 872e8f271b7e62d3c72c663e41de5b56182ec1ba Mon Sep 17 00:00:00 2001 From: Daniil Vinichenko Date: Fri, 12 Dec 2025 11:03:54 +0300 Subject: [PATCH 4/4] no more multiple visits --- src/main/java/org/example/JarAnalyzer.java | 3 +- .../java/org/example/MetricsAnalyzer.java | 22 +- .../visitor\321\213/DepthVisitor.java" | 20 +- .../visitor\321\213/MultiVisitor.java" | 219 +++++++++++------- .../OverriddenMethodsVisitor.java" | 12 +- 5 files changed, 149 insertions(+), 127 deletions(-) diff --git a/src/main/java/org/example/JarAnalyzer.java b/src/main/java/org/example/JarAnalyzer.java index 7b18d1a..654e791 100644 --- a/src/main/java/org/example/JarAnalyzer.java +++ b/src/main/java/org/example/JarAnalyzer.java @@ -1,6 +1,7 @@ package org.example; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; import java.io.IOException; import java.util.Enumeration; @@ -14,7 +15,7 @@ public JarAnalyzer(String jarPath) { this.jarPath = jarPath; } - public void processClasses(org.objectweb.asm.ClassVisitor visitor) throws IOException { + public void processClasses(ClassVisitor visitor) throws IOException { try (JarFile jarFile = new JarFile(jarPath)) { Enumeration entries = jarFile.entries(); diff --git a/src/main/java/org/example/MetricsAnalyzer.java b/src/main/java/org/example/MetricsAnalyzer.java index 1642e98..6886e1c 100644 --- a/src/main/java/org/example/MetricsAnalyzer.java +++ b/src/main/java/org/example/MetricsAnalyzer.java @@ -2,7 +2,6 @@ import org.example.consoler.Config; import org.example.consoler.Resulter; -import org.example.misc.ClassHierarchyBuilder; import org.example.misc.ConfigReader; import org.example.misc.JsonMarshaler; import org.example.visitorы.*; @@ -26,31 +25,14 @@ public static void main(String[] args) { private static Resulter analyzeJar(String jarPath) throws IOException { JarAnalyzer jarAnalyzer = new JarAnalyzer(jarPath); - - ClassHierarchyBuilder hierarchyBuilder = new ClassHierarchyBuilder(); - jarAnalyzer.processClasses(hierarchyBuilder); - - List classMetricsList; - - MultiVisitor multiVisitor = getMultiVisitor(hierarchyBuilder); + MultiVisitor multiVisitor = new MultiVisitor(); jarAnalyzer.processClasses(multiVisitor); - classMetricsList = multiVisitor.getClassMetricsList(); + List classMetricsList = multiVisitor.getClassMetricsList(); return calculateAggregatedMetrics(classMetricsList); } - private static MultiVisitor getMultiVisitor(ClassHierarchyBuilder hierarchyBuilder) { - DepthVisitor inheritanceVisitor = new DepthVisitor(hierarchyBuilder); - ABCMetricVisitor abcVisitor = new ABCMetricVisitor(); - OverriddenMethodsVisitor overriddenVisitor = new OverriddenMethodsVisitor(hierarchyBuilder); - FieldsCounterVisitor fieldsVisitor = new FieldsCounterVisitor(); - - return new MultiVisitor( - inheritanceVisitor, abcVisitor, overriddenVisitor, fieldsVisitor - ); - } - private static Resulter calculateAggregatedMetrics( List classMetricsList) { diff --git "a/src/main/java/org/example/visitor\321\213/DepthVisitor.java" "b/src/main/java/org/example/visitor\321\213/DepthVisitor.java" index bc94aed..14cebe1 100644 --- "a/src/main/java/org/example/visitor\321\213/DepthVisitor.java" +++ "b/src/main/java/org/example/visitor\321\213/DepthVisitor.java" @@ -1,41 +1,33 @@ package org.example.visitorы; import org.example.ClassMetrics; -import org.example.misc.ClassHierarchyBuilder; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.Opcodes; import java.util.Map; /** - * {@link ClassVisitor}, который рассчитывает глубину наследования - * для каждого класса на основе построенной иерархии. + * {@link ClassVisitor}, который готовит метрики для расчета глубины наследования. + * Сама глубина вычисляется позже в {@link MultiVisitor#finalizeDepths()}. */ public class DepthVisitor extends ClassVisitor { - private final ClassHierarchyBuilder hierarchyBuilder; private final Map metricsMap; /** - * Создает визитор вычисления глубины наследования. - * - * @param hierarchyBuilder построитель иерархии классов + * Создает визитор для подготовки метрик глубины наследования. */ - public DepthVisitor(ClassHierarchyBuilder hierarchyBuilder) { + public DepthVisitor() { super(Opcodes.ASM9); - this.hierarchyBuilder = hierarchyBuilder; this.metricsMap = new java.util.HashMap<>(); } /** - * Для каждого класса запрашивает глубину наследования у - * {@link ClassHierarchyBuilder} и сохраняет её в {@link ClassMetrics}. + * Создает запись метрик для класса. Глубина наследования будет вычислена позже. */ @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - ClassMetrics metrics = metricsMap.computeIfAbsent(name, ClassMetrics::new); - int depth = hierarchyBuilder.getInheritanceDepth(name); - metrics.setDepth(depth); + metricsMap.computeIfAbsent(name, ClassMetrics::new); } /** diff --git "a/src/main/java/org/example/visitor\321\213/MultiVisitor.java" "b/src/main/java/org/example/visitor\321\213/MultiVisitor.java" index 6a08e6d..f2a72c0 100644 --- "a/src/main/java/org/example/visitor\321\213/MultiVisitor.java" +++ "b/src/main/java/org/example/visitor\321\213/MultiVisitor.java" @@ -2,44 +2,36 @@ import org.example.ClassMetrics; import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** - * Композитный {@link ClassVisitor}, который делегирует события нескольким - * специализированным визиторам и затем объединяет их метрики по классам. + * Композитный {@link ClassVisitor}, который собирает все метрики за один проход: + * глубину наследования, ABC-метрику, переопределенные методы и количество полей. */ public class MultiVisitor extends ClassVisitor { - private final DepthVisitor depthVisitor; - private final ABCMetricVisitor abcVisitor; - private final OverriddenMethodsVisitor overriddenVisitor; - private final FieldsCounterVisitor fieldsVisitor; + private final Map classToParent = new HashMap<>(); + private final Map depthCache = new HashMap<>(); + + private final Map metricsMap = new HashMap<>(); + private String currentClassName; + private final Map> classABCMetrics = new HashMap<>(); + private final Map> classMethods = new HashMap<>(); + private Set currentMethods; + private int currentFieldCount; - /** - * Создает комбинированный визитор поверх переданных визиторов метрик. - */ - public MultiVisitor( - DepthVisitor depthVisitor, - ABCMetricVisitor abcVisitor, - OverriddenMethodsVisitor overriddenVisitor, - FieldsCounterVisitor fieldsVisitor) { + public MultiVisitor() { super(Opcodes.ASM9); - this.depthVisitor = depthVisitor; - this.abcVisitor = abcVisitor; - this.overriddenVisitor = overriddenVisitor; - this.fieldsVisitor = fieldsVisitor; } - /** - * Передает информацию о классе всем внутренним визиторам, - * исключая интерфейсы. - */ @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { @@ -48,89 +40,144 @@ public void visit(int version, int access, String name, String signature, return; } - depthVisitor.visit(version, access, name, signature, superName, interfaces); - abcVisitor.visit(version, access, name, signature, superName, interfaces); - overriddenVisitor.visit(version, access, name, signature, superName, interfaces); - fieldsVisitor.visit(version, access, name, signature, superName, interfaces); + currentClassName = name; + + // Строим иерархию классов + if (superName != null) { + classToParent.put(name, superName); + } + + metricsMap.computeIfAbsent(name, ClassMetrics::new); + classABCMetrics.put(name, new ArrayList<>()); + currentMethods = new HashSet<>(); + currentFieldCount = 0; } - /** - * Делегирует обработку полей визитору подсчета полей. - */ @Override - public org.objectweb.asm.FieldVisitor visitField(int access, String name, String descriptor, - String signature, Object value) { - fieldsVisitor.visitField(access, name, descriptor, signature, value); + public FieldVisitor visitField(int access, String name, String descriptor, + String signature, Object value) { + // Считаем только несинтетические поля + if ((access & Opcodes.ACC_SYNTHETIC) == 0) { + currentFieldCount++; + } return null; } - /** - * Делегирует обработку методов визиторам ABC-метрики и - * подсчета переопределенных методов. - */ @Override public MethodVisitor visitMethod(int access, String name, String descriptor, - String signature, String[] exceptions) { - MethodVisitor abcMethodVisitor = abcVisitor.visitMethod(access, name, descriptor, signature, exceptions); - overriddenVisitor.visitMethod(access, name, descriptor, signature, exceptions); - return abcMethodVisitor; + String signature, String[] exceptions) { + // Игнорируем синтетические методы и конструкторы + boolean isABCMethod = (access & Opcodes.ACC_SYNTHETIC) == 0 && + !name.equals("") && !name.equals(""); + + // Игнорируем синтетические, статические методы и конструкторы + boolean isOverriddenMethod = (access & Opcodes.ACC_SYNTHETIC) == 0 && + (access & Opcodes.ACC_STATIC) == 0 && + !name.equals("") && !name.equals(""); + + if (isOverriddenMethod) { + String methodSignature = name + descriptor; + currentMethods.add(methodSignature); + } + + if (isABCMethod) { + return new ABCMethodVisitor(api, currentClassName, classABCMetrics); + } + + return null; } - /** - * Завершает работу всех внутренних визиторов. - */ @Override public void visitEnd() { - depthVisitor.visitEnd(); - abcVisitor.visitEnd(); - overriddenVisitor.visitEnd(); - fieldsVisitor.visitEnd(); + ClassMetrics metrics = metricsMap.get(currentClassName); + if (metrics != null) { + metrics.setFieldsCount(currentFieldCount); + } + + classMethods.put(currentClassName, currentMethods); + + List abcList = classABCMetrics.get(currentClassName); + if (metrics != null && abcList != null && !abcList.isEmpty()) { + double sum = 0.0; + for (Double abc : abcList) { + sum += abc; + } + metrics.setAbcMetric(sum / abcList.size()); + } else if (metrics != null) { + metrics.setAbcMetric(0.0); + } + + if (metrics != null) { + int overriddenCount = countOverriddenMethods(currentClassName, currentMethods); + metrics.setOverriddenMethodsCount(overriddenCount); + } } /** - * Объединяет результаты всех визиторов в единый список метрик по классам. + * Считает количество методов из {@code methods}, которые уже существуют + * в родительском классе. */ - public List getClassMetricsList() { - Map depthMap = depthVisitor.getMetricsMap(); - Map abcMap = abcVisitor.getMetricsMap(); - Map overriddenMap = overriddenVisitor.getMetricsMap(); - Map fieldsMap = fieldsVisitor.getMetricsMap(); - - List result = new ArrayList<>(); + private int countOverriddenMethods(String className, Set methods) { + int count = 0; + String parent = classToParent.get(className); - Set allClasses = new HashSet<>(); - allClasses.addAll(depthMap.keySet()); - allClasses.addAll(abcMap.keySet()); - allClasses.addAll(overriddenMap.keySet()); - allClasses.addAll(fieldsMap.keySet()); - - for (String className : allClasses) { - ClassMetrics metrics = new ClassMetrics(className); - - ClassMetrics depthMetrics = depthMap.get(className); - if (depthMetrics != null) { - metrics.setDepth(depthMetrics.getDepth()); - } - - ClassMetrics abcMetrics = abcMap.get(className); - if (abcMetrics != null) { - metrics.setAbcMetric(abcMetrics.getAbcMetric()); - } - - ClassMetrics overriddenMetrics = overriddenMap.get(className); - if (overriddenMetrics != null) { - metrics.setOverriddenMethodsCount(overriddenMetrics.getOverriddenMethodsCount()); - } - - ClassMetrics fieldsMetrics = fieldsMap.get(className); - if (fieldsMetrics != null) { - metrics.setFieldsCount(fieldsMetrics.getFieldsCount()); + if (parent == null || parent.equals("java/lang/Object")) { + return 0; + } + + Set parentMethods = classMethods.get(parent); + if (parentMethods != null) { + for (String method : methods) { + if (parentMethods.contains(method)) { + count++; + } } - - result.add(metrics); } - return result; + return count; } -} + /** + * Вычисляет глубины наследования для всех классов на основе построенной иерархии. + */ + private void finalizeDepths() { + for (String className : metricsMap.keySet()) { + ClassMetrics metrics = metricsMap.get(className); + int depth = getInheritanceDepth(className); + metrics.setDepth(depth); + } + } + + private int getInheritanceDepth(String className) { + if (depthCache.containsKey(className)) { + return depthCache.get(className); + } + + int depth = calculateDepth(className, new HashSet<>()); + depthCache.put(className, depth); + return depth; + } + + private int calculateDepth(String className, Set visited) { + if (visited.contains(className)) { + return 0; + } + visited.add(className); + + String parent = classToParent.get(className); + + if (parent == null || parent.equals("java/lang/Object")) { + return 0; + } + return 1 + calculateDepth(parent, visited); + } + + /** + * Объединяет результаты всех метрик в единый список по классам. + * Перед объединением вычисляет глубины наследования. + */ + public List getClassMetricsList() { + finalizeDepths(); + return new ArrayList<>(metricsMap.values()); + } +} diff --git "a/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" "b/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" index 24b28b2..bc1d96a 100644 --- "a/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" +++ "b/src/main/java/org/example/visitor\321\213/OverriddenMethodsVisitor.java" @@ -1,7 +1,6 @@ package org.example.visitorы; import org.example.ClassMetrics; -import org.example.misc.ClassHierarchyBuilder; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -9,13 +8,14 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; /** * {@link ClassVisitor}, который определяет количество методов, * переопределяющих методы родительских классов. */ public class OverriddenMethodsVisitor extends ClassVisitor { - private final ClassHierarchyBuilder hierarchyBuilder; + private final Supplier> classToParentSupplier; private final Map metricsMap; private final Map> classMethods = new HashMap<>(); private String currentClassName; @@ -24,11 +24,11 @@ public class OverriddenMethodsVisitor extends ClassVisitor { /** * Создает визитор подсчета переопределенных методов. * - * @param hierarchyBuilder построитель иерархии классов для поиска родителей + * @param classToParentSupplier поставщик карты иерархии классов (класс -> родитель) */ - public OverriddenMethodsVisitor(ClassHierarchyBuilder hierarchyBuilder) { + public OverriddenMethodsVisitor(Supplier> classToParentSupplier) { super(Opcodes.ASM9); - this.hierarchyBuilder = hierarchyBuilder; + this.classToParentSupplier = classToParentSupplier; this.metricsMap = new HashMap<>(); } @@ -85,7 +85,7 @@ public void visitEnd() { */ private int countOverriddenMethods(String className, Set methods) { int count = 0; - Map classToParent = hierarchyBuilder.getClassToParent(); + Map classToParent = classToParentSupplier.get(); String parent = classToParent.get(className); if (parent == null || parent.equals("java/lang/Object")) {