From e3c338af7e5938fcb45c45606ee7087f1bc3c1d8 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:18:02 +0000 Subject: [PATCH 1/7] 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 f383db861300d9b11f67d09c6d387d66d5d0d31f Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 8 Nov 2025 23:37:14 +0300 Subject: [PATCH 2/7] add field counter --- src/main/java/org/example/Analyzer.java | 45 +++++++++++++++ src/main/java/org/example/Example.java | 29 ---------- src/main/java/org/example/Statistic.java | 16 ++++++ .../java/org/example/example/BubbleSort.java | 29 ---------- .../java/org/example/util/AverageHolder.java | 18 ++++++ .../visitor/ClassStatisticVisitor.java | 55 +++++++++++++++++++ 6 files changed, 134 insertions(+), 58 deletions(-) create mode 100644 src/main/java/org/example/Analyzer.java delete mode 100644 src/main/java/org/example/Example.java create mode 100644 src/main/java/org/example/Statistic.java delete mode 100644 src/main/java/org/example/example/BubbleSort.java create mode 100644 src/main/java/org/example/util/AverageHolder.java create mode 100644 src/main/java/org/example/visitor/ClassStatisticVisitor.java diff --git a/src/main/java/org/example/Analyzer.java b/src/main/java/org/example/Analyzer.java new file mode 100644 index 0000000..0209ec0 --- /dev/null +++ b/src/main/java/org/example/Analyzer.java @@ -0,0 +1,45 @@ +package org.example; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.jar.JarFile; +import org.example.visitor.ClassStatisticVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; + +public class Analyzer { + + public static void main(String[] args) throws IOException { + var path = Path.of("src/main/resources/sample.jar"); + var analyzer = new Analyzer(); + + analyzer.analyzeJar(path); + } + + public void analyzeJar(Path path) throws IOException { + try (JarFile jar = new JarFile(path.toFile())) { + + Statistic statistic = new Statistic(); + + jar.stream() + .filter(entry -> entry.getName().endsWith(".class")) + .forEach(entry -> { + // Should be safe for Java classes, since they always in the class with same name + // unlike kotlin classes + String className = entry.getName().replace(".class", ""); + + try (InputStream is = jar.getInputStream(entry)) { + ClassReader cr = new ClassReader(is); + ClassVisitor visitor = new ClassStatisticVisitor(statistic); + cr.accept(visitor, 0); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + ); + + statistic.printStatistic(); + } + } +} diff --git a/src/main/java/org/example/Example.java b/src/main/java/org/example/Example.java deleted file mode 100644 index 52d0abe..0000000 --- a/src/main/java/org/example/Example.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.example; - -import org.example.visitor.ClassPrinter; -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 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(); - - 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); - } - } - } - } -} diff --git a/src/main/java/org/example/Statistic.java b/src/main/java/org/example/Statistic.java new file mode 100644 index 0000000..c48ddd5 --- /dev/null +++ b/src/main/java/org/example/Statistic.java @@ -0,0 +1,16 @@ +package org.example; + +import org.example.util.AverageHolder; + +public class Statistic { + + private final AverageHolder fieldAverageHolder = new AverageHolder(); + + public void updateFieldStatistic(Integer fieldCount) { + fieldAverageHolder.updateAverage(fieldCount); + } + + public void printStatistic() { + System.out.print("Average: " + fieldAverageHolder.getAverage()); + } +} diff --git a/src/main/java/org/example/example/BubbleSort.java b/src/main/java/org/example/example/BubbleSort.java deleted file mode 100644 index 8a21c95..0000000 --- a/src/main/java/org/example/example/BubbleSort.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.example.example; - -public class BubbleSort { - - static void bubbleSort(int[] arr, int n) - { - int i, j, temp; - boolean swapped; - for (i = 0; i < n - 1; i++) { - swapped = false; - for (j = 0; j < n - i - 1; j++) { - if (arr[j] > arr[j + 1]) { - - // Swap arr[j] and arr[j+1] - temp = arr[j]; - arr[j] = arr[j + 1]; - arr[j + 1] = temp; - swapped = true; - } - } - - // If no two elements were - // swapped by inner loop, then break - if (!swapped) - break; - } - } - -} diff --git a/src/main/java/org/example/util/AverageHolder.java b/src/main/java/org/example/util/AverageHolder.java new file mode 100644 index 0000000..6f516b4 --- /dev/null +++ b/src/main/java/org/example/util/AverageHolder.java @@ -0,0 +1,18 @@ +package org.example.util; + +public class AverageHolder { + private final Object lock = new Object(); + private Integer sum = 0; + private Integer count = 0; + + public Double getAverage() { + return count.equals(0) ? 0.0 : sum.doubleValue() / count; + } + + public void updateAverage(Integer value) { + synchronized (lock) { + sum += value; + count++; + } + } +} diff --git a/src/main/java/org/example/visitor/ClassStatisticVisitor.java b/src/main/java/org/example/visitor/ClassStatisticVisitor.java new file mode 100644 index 0000000..e4779f6 --- /dev/null +++ b/src/main/java/org/example/visitor/ClassStatisticVisitor.java @@ -0,0 +1,55 @@ +package org.example.visitor; + +import org.example.Statistic; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import static org.objectweb.asm.Opcodes.ASM8; + +public class ClassStatisticVisitor extends ClassVisitor { + + private final Statistic statistic; + + private Integer fieldCount = 0; + + public ClassStatisticVisitor(Statistic statistic) { + super(ASM8); + this.statistic = statistic; + } + + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + } + + public void visitSource(String source, String debug) { + } + + public void visitOuterClass(String owner, String name, String desc) { + } + + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + return super.visitAnnotation(desc, visible); + } + + public void visitAttribute(Attribute attr) { + } + + // todo also visit inner classes + public void visitInnerClass(String name, String outerName, String innerName, int access) { + } + + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + fieldCount++; + return super.visitField(access, name, desc, signature, value); + } + + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return super.visitMethod(access, name, desc, signature, exceptions); + } + + public void visitEnd() { + statistic.updateFieldStatistic(fieldCount); + } +} + From 08a59d858e8f2d50851a255b0d3f49f92c212041 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Thu, 4 Dec 2025 03:03:35 +0300 Subject: [PATCH 3/7] implement analyzer --- src/main/java/org/example/Analyzer.java | 48 ++++--- src/main/java/org/example/Statistic.java | 16 --- .../java/org/example/model/AbcMetric.java | 61 +++++++++ .../{util => model}/AverageHolder.java | 2 +- .../java/org/example/model/Statistic.java | 35 +++++ .../model/hierarchy/ClassHierarchyNode.java | 55 ++++++++ .../org/example/util/ClassRepository.java | 127 ++++++++++++++++++ .../example/util/provider/ClassProvider.java | 14 ++ .../provider/impl/CompositeClassProvider.java | 27 ++++ .../util/provider/impl/JarClassProvider.java | 28 ++++ .../provider/impl/SystemClassProvider.java | 13 ++ .../org/example/visitor/AbcMethodVisitor.java | 108 +++++++++++++++ .../visitor/ClassStatisticVisitor.java | 59 ++++---- 13 files changed, 534 insertions(+), 59 deletions(-) delete mode 100644 src/main/java/org/example/Statistic.java create mode 100644 src/main/java/org/example/model/AbcMetric.java rename src/main/java/org/example/{util => model}/AverageHolder.java (93%) create mode 100644 src/main/java/org/example/model/Statistic.java create mode 100644 src/main/java/org/example/model/hierarchy/ClassHierarchyNode.java create mode 100644 src/main/java/org/example/util/ClassRepository.java create mode 100644 src/main/java/org/example/util/provider/ClassProvider.java create mode 100644 src/main/java/org/example/util/provider/impl/CompositeClassProvider.java create mode 100644 src/main/java/org/example/util/provider/impl/JarClassProvider.java create mode 100644 src/main/java/org/example/util/provider/impl/SystemClassProvider.java create mode 100644 src/main/java/org/example/visitor/AbcMethodVisitor.java diff --git a/src/main/java/org/example/Analyzer.java b/src/main/java/org/example/Analyzer.java index 0209ec0..a142134 100644 --- a/src/main/java/org/example/Analyzer.java +++ b/src/main/java/org/example/Analyzer.java @@ -3,7 +3,14 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; +import java.util.List; import java.util.jar.JarFile; +import org.example.model.Statistic; +import org.example.util.ClassRepository; +import org.example.util.provider.ClassProvider; +import org.example.util.provider.impl.CompositeClassProvider; +import org.example.util.provider.impl.JarClassProvider; +import org.example.util.provider.impl.SystemClassProvider; import org.example.visitor.ClassStatisticVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; @@ -14,32 +21,37 @@ public static void main(String[] args) throws IOException { var path = Path.of("src/main/resources/sample.jar"); var analyzer = new Analyzer(); - analyzer.analyzeJar(path); + var statistic = analyzer.analyzeJar(path); + statistic.printStatistic(); } - public void analyzeJar(Path path) throws IOException { + public Statistic analyzeJar(Path path) throws IOException { + Statistic statistic = new Statistic(); try (JarFile jar = new JarFile(path.toFile())) { - Statistic statistic = new Statistic(); + ClassProvider jarProvider = new JarClassProvider(jar); + ClassProvider systemProvider = new SystemClassProvider(); + ClassProvider compositeProvider = new CompositeClassProvider(List.of(jarProvider, systemProvider)); + + ClassRepository repository = new ClassRepository(compositeProvider); + jar.stream() .filter(entry -> entry.getName().endsWith(".class")) .forEach(entry -> { - // Should be safe for Java classes, since they always in the class with same name - // unlike kotlin classes - String className = entry.getName().replace(".class", ""); - - try (InputStream is = jar.getInputStream(entry)) { - ClassReader cr = new ClassReader(is); - ClassVisitor visitor = new ClassStatisticVisitor(statistic); - cr.accept(visitor, 0); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - ); - - statistic.printStatistic(); + String internalName = entry.getName().replace(".class", ""); + + try (InputStream is = jar.getInputStream(entry)) { + ClassReader cr = new ClassReader(is); + + ClassVisitor visitor = new ClassStatisticVisitor(statistic, repository); + + cr.accept(visitor, 0); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); } + return statistic; } } diff --git a/src/main/java/org/example/Statistic.java b/src/main/java/org/example/Statistic.java deleted file mode 100644 index c48ddd5..0000000 --- a/src/main/java/org/example/Statistic.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.example; - -import org.example.util.AverageHolder; - -public class Statistic { - - private final AverageHolder fieldAverageHolder = new AverageHolder(); - - public void updateFieldStatistic(Integer fieldCount) { - fieldAverageHolder.updateAverage(fieldCount); - } - - public void printStatistic() { - System.out.print("Average: " + fieldAverageHolder.getAverage()); - } -} diff --git a/src/main/java/org/example/model/AbcMetric.java b/src/main/java/org/example/model/AbcMetric.java new file mode 100644 index 0000000..8a1a559 --- /dev/null +++ b/src/main/java/org/example/model/AbcMetric.java @@ -0,0 +1,61 @@ +package org.example.model; + +/* + ABC rules for Java + The following rules give the count of Assignments, Branches, Conditionals in the ABC metric for Java: + + 1. Add one to the assignment count when: + Occurrence of an assignment operator (exclude constant declarations and default parameter assignments) (**=, *=, /=, %=, +=, -=, <<=, >>=, &=, !=, ^=, >>>=**). + Occurrence of an increment or a decrement operator (prefix or postfix) (++, --). + 2. Add one to branch count when + Occurrence of a function call or a class method call. + Occurrence of a ‘new’ operator. + 3. Add one to condition count when: + Occurrence of a conditional operator (<, >, <=, >=, ==, !=). + Occurrence of the following keywords (‘else’, ‘case’, ‘default’, ‘?’, ‘try’, ‘catch’). + Occurrence of a unary conditional operator. + */ +public class AbcMetric { + + private long assignment; + private long branch; + private long condition; + + public AbcMetric(long assignment, long branch, long condition) { + this.assignment = assignment; + this.branch = branch; + this.condition = condition; + } + + public AbcMetric(AbcMetric other) { + this.assignment = other.assignment; + this.branch = other.branch; + this.condition = other.condition; + } + + public void add(AbcMetric other) { + this.assignment += other.assignment; + this.branch += other.branch; + this.condition += other.condition; + } + + public double getAbc() { + return Math.sqrt(assignment * assignment + branch * branch + condition * condition); + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + + AbcMetric abcMetric = (AbcMetric) o; + return assignment == abcMetric.assignment && branch == abcMetric.branch && condition == abcMetric.condition; + } + + @Override + public int hashCode() { + int result = Long.hashCode(assignment); + result = 31 * result + Long.hashCode(branch); + result = 31 * result + Long.hashCode(condition); + return result; + } +} diff --git a/src/main/java/org/example/util/AverageHolder.java b/src/main/java/org/example/model/AverageHolder.java similarity index 93% rename from src/main/java/org/example/util/AverageHolder.java rename to src/main/java/org/example/model/AverageHolder.java index 6f516b4..bd05ed2 100644 --- a/src/main/java/org/example/util/AverageHolder.java +++ b/src/main/java/org/example/model/AverageHolder.java @@ -1,4 +1,4 @@ -package org.example.util; +package org.example.model; public class AverageHolder { private final Object lock = new Object(); diff --git a/src/main/java/org/example/model/Statistic.java b/src/main/java/org/example/model/Statistic.java new file mode 100644 index 0000000..fed7fd2 --- /dev/null +++ b/src/main/java/org/example/model/Statistic.java @@ -0,0 +1,35 @@ +package org.example.model; + +public class Statistic { + + private final AverageHolder fieldAverageHolder = new AverageHolder(); + private final AverageHolder averageInheritanceDepthHolder = new AverageHolder(); + private final AverageHolder averageOverriddenCountHolder = new AverageHolder(); + private int maxInheritanceDepth = 0; + private final AbcMetric abcMetric = new AbcMetric(0, 0, 0); + + public void updateFieldStatistic(Integer fieldCount) { + fieldAverageHolder.updateAverage(fieldCount); + } + + public void writeDepth(int depth) { + averageInheritanceDepthHolder.updateAverage(depth); + maxInheritanceDepth = Math.max(maxInheritanceDepth, depth); + } + + public void writeOverriddenCount(int overriddenCount) { + averageOverriddenCountHolder.updateAverage(overriddenCount); + } + + public void printStatistic() { + System.out.println("Average field count: " + fieldAverageHolder.getAverage()); + System.out.println("ABC: " + abcMetric.getAbc()); + System.out.println("Inheritance max depth: " + maxInheritanceDepth); + System.out.println("Inheritance average depth: " + averageInheritanceDepthHolder.getAverage()); + System.out.println("Average override count: " + averageOverriddenCountHolder.getAverage()); + } + + public AbcMetric getAbcMetric() { + return abcMetric; + } +} diff --git a/src/main/java/org/example/model/hierarchy/ClassHierarchyNode.java b/src/main/java/org/example/model/hierarchy/ClassHierarchyNode.java new file mode 100644 index 0000000..a62dd77 --- /dev/null +++ b/src/main/java/org/example/model/hierarchy/ClassHierarchyNode.java @@ -0,0 +1,55 @@ +package org.example.model.hierarchy; + +import java.util.Collections; +import java.util.Set; + +public class ClassHierarchyNode { + + private final String name; + private final String superName; + private final Set methods; // name + descriptor + + private Integer cachedDepth = null; + + public ClassHierarchyNode(String name, String superName, Set methods) { + if (name == null || superName == null) { + throw new IllegalArgumentException("Name and superName must not be null"); + } + this.name = name; + this.superName = superName; + this.methods = methods != null ? methods : Collections.emptySet(); + } + + public String getName() { + return name; + } + + public String getSuperName() { + return superName; + } + + public boolean hasMethod(String signature) { + return methods.contains(signature); + } + + public void setCachedDepth(int depth) { + this.cachedDepth = depth; + } + + public Integer getCachedDepth() { + return cachedDepth; + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + + ClassHierarchyNode that = (ClassHierarchyNode) o; + return name.equals(that.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} \ No newline at end of file diff --git a/src/main/java/org/example/util/ClassRepository.java b/src/main/java/org/example/util/ClassRepository.java new file mode 100644 index 0000000..9d5cee6 --- /dev/null +++ b/src/main/java/org/example/util/ClassRepository.java @@ -0,0 +1,127 @@ +package org.example.util; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import org.example.model.hierarchy.ClassHierarchyNode; +import org.example.util.provider.ClassProvider; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +public class ClassRepository { + + private static final String OBJECT_CLASS_NAME = "java/lang/Object"; + + private final Map cache = new HashMap<>(); + private final ClassProvider classProvider; + + public ClassRepository(ClassProvider classProvider) { + this.classProvider = classProvider; + } + + public ClassHierarchyNode getNode(String className) { + if (className == null) { + throw new IllegalArgumentException("Class name must not be null"); + } + + if (cache.containsKey(className)) { + return cache.get(className); + } + + try { + ClassHierarchyNode node = loadNode(className); + cache.put(className, node); + return node; + } catch (IOException e) { + System.err.println("WARN: Unable to load class " + className); + cache.put(className, null); // remember failed attempt + return null; + } + } + + public int getDepth(String className) { + if (OBJECT_CLASS_NAME.equals(className) || className == null) { + return 0; + } + + ClassHierarchyNode node = getNode(className); + + if (node == null) { + return 0; + } + + if (node.getCachedDepth() != null) { + return node.getCachedDepth(); + } + + int depth = 1 + getDepth(node.getSuperName()); + + node.setCachedDepth(depth); + return depth; + } + + public boolean isOverridden(String className, String methodSignature) { + ClassHierarchyNode node = getNode(className); + + if (node == null) { + return false; + } + + return checkParentHierarchy(node.getSuperName(), methodSignature); + } + + private boolean checkParentHierarchy(String currentClassName, String methodSignature) { + if (currentClassName == null || OBJECT_CLASS_NAME.equals(currentClassName)) { + return false; + } + + ClassHierarchyNode node = getNode(currentClassName); + if (node == null) { + return false; + } + + if (node.hasMethod(methodSignature)) { + return true; + } + + return checkParentHierarchy(node.getSuperName(), methodSignature); + } + + private ClassHierarchyNode loadNode(String className) throws IOException { + try (InputStream in = classProvider.getClassInputStream(className)) { + if (in == null) return null; + + ClassReader cr = new ClassReader(in); + Set methods = new HashSet<>(); + final String[] superNameHolder = new String[1]; + + // only reading superclass and method signature + // flags needed for performance + cr.accept(new ClassVisitor(Opcodes.ASM9) { + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + superNameHolder[0] = superName; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + boolean isPrivateOrStatic = (access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC)) != 0; + boolean isConstructor = name.startsWith("<"); + + if (!isPrivateOrStatic && !isConstructor) { + methods.add(name + descriptor); + } + return null; // not entering method body + } + }, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + + return new ClassHierarchyNode(className, superNameHolder[0], methods); + } + } +} diff --git a/src/main/java/org/example/util/provider/ClassProvider.java b/src/main/java/org/example/util/provider/ClassProvider.java new file mode 100644 index 0000000..51bfc6d --- /dev/null +++ b/src/main/java/org/example/util/provider/ClassProvider.java @@ -0,0 +1,14 @@ +package org.example.util.provider; + +import java.io.IOException; +import java.io.InputStream; + +@FunctionalInterface +public interface ClassProvider { + + /** + * @param internalName class name in ASM format, for example "java/lang/String" + * @return InputStream or null + */ + InputStream getClassInputStream(String internalName) throws IOException; +} diff --git a/src/main/java/org/example/util/provider/impl/CompositeClassProvider.java b/src/main/java/org/example/util/provider/impl/CompositeClassProvider.java new file mode 100644 index 0000000..557de57 --- /dev/null +++ b/src/main/java/org/example/util/provider/impl/CompositeClassProvider.java @@ -0,0 +1,27 @@ +package org.example.util.provider.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import org.example.util.provider.ClassProvider; + +public class CompositeClassProvider implements ClassProvider { + + private final List providers; + + public CompositeClassProvider(List providers) { + this.providers = providers; + } + + @Override + public InputStream getClassInputStream(String internalName) throws IOException { + for (ClassProvider provider : providers) { + InputStream is = provider.getClassInputStream(internalName); + + if (is != null) { + return is; + } + } + return null; + } +} diff --git a/src/main/java/org/example/util/provider/impl/JarClassProvider.java b/src/main/java/org/example/util/provider/impl/JarClassProvider.java new file mode 100644 index 0000000..f81af7d --- /dev/null +++ b/src/main/java/org/example/util/provider/impl/JarClassProvider.java @@ -0,0 +1,28 @@ +package org.example.util.provider.impl; + +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; +import org.example.util.provider.ClassProvider; + +public class JarClassProvider implements ClassProvider { + + private final JarFile jarFile; + + public JarClassProvider(JarFile jarFile) { + this.jarFile = jarFile; + } + + @Override + public InputStream getClassInputStream(String internalName) throws IOException { + String entryName = internalName + ".class"; + ZipEntry entry = jarFile.getEntry(entryName); + + if (entry == null) { + return null; + } + + return jarFile.getInputStream(entry); + } +} diff --git a/src/main/java/org/example/util/provider/impl/SystemClassProvider.java b/src/main/java/org/example/util/provider/impl/SystemClassProvider.java new file mode 100644 index 0000000..068cd1d --- /dev/null +++ b/src/main/java/org/example/util/provider/impl/SystemClassProvider.java @@ -0,0 +1,13 @@ +package org.example.util.provider.impl; + +import java.io.InputStream; +import org.example.util.provider.ClassProvider; + +public class SystemClassProvider implements ClassProvider { + + @Override + public InputStream getClassInputStream(String internalName) { + String resourceName = internalName + ".class"; + return ClassLoader.getSystemResourceAsStream(resourceName); + } +} diff --git a/src/main/java/org/example/visitor/AbcMethodVisitor.java b/src/main/java/org/example/visitor/AbcMethodVisitor.java new file mode 100644 index 0000000..c5bf8db --- /dev/null +++ b/src/main/java/org/example/visitor/AbcMethodVisitor.java @@ -0,0 +1,108 @@ +package org.example.visitor; + +import org.example.model.AbcMetric; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import static org.objectweb.asm.Opcodes.ASM9; + +// init blocks are inlined in constructor +public class AbcMethodVisitor extends MethodVisitor { + + private final AbcMetric resultMetric; + private long assignment; + private long branch; + private long condition; + + public AbcMethodVisitor(MethodVisitor delegate, AbcMetric resultMetric) { + super(ASM9, delegate); + this.resultMetric = resultMetric; + } + + @Override + public void visitVarInsn(int opcode, int varIndex) { + if (opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE) { + assignment++; + } + + super.visitVarInsn(opcode, varIndex); + } + + @Override + public void visitFieldInsn(int opcode, String owner, String name, String descriptor) { + if (opcode == Opcodes.PUTSTATIC || opcode == Opcodes.PUTFIELD) { + assignment++; + } + + super.visitFieldInsn(opcode, owner, name, descriptor); + } + + @Override + public void visitIincInsn(int varIndex, int increment) { + assignment++; + super.visitIincInsn(varIndex, increment); + } + + @Override + public void visitInsn(int opcode) { + // array assignment + if (opcode >= Opcodes.IASTORE && opcode <= Opcodes.SASTORE) { + assignment++; + } + + super.visitInsn(opcode); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) { + branch++; + super.visitMethodInsn(opcode, owner, name, descriptor, isInterface); + } + + @Override + public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments) { + branch++; + super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments); + } + + @Override + public void visitJumpInsn(int opcode, Label label) { + if (opcode != Opcodes.GOTO && opcode != Opcodes.JSR) { + condition++; + } + super.visitJumpInsn(opcode, label); + } + + @Override + public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { + // dflt = default branch + // labels.length = number of cases + condition += labels.length; // cases + if (dflt != null) { + condition++; + } + super.visitTableSwitchInsn(min, max, dflt, labels); + } + + @Override + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + condition += labels.length; // cases + if (dflt != null) { + condition++; + } + super.visitLookupSwitchInsn(dflt, keys, labels); + } + + @Override + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + condition++; + super.visitTryCatchBlock(start, end, handler, type); + } + + @Override + public void visitEnd() { + resultMetric.add(new AbcMetric(assignment, branch, condition)); + super.visitEnd(); + } +} diff --git a/src/main/java/org/example/visitor/ClassStatisticVisitor.java b/src/main/java/org/example/visitor/ClassStatisticVisitor.java index e4779f6..84126c3 100644 --- a/src/main/java/org/example/visitor/ClassStatisticVisitor.java +++ b/src/main/java/org/example/visitor/ClassStatisticVisitor.java @@ -1,42 +1,35 @@ package org.example.visitor; -import org.example.Statistic; -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.Attribute; +import org.example.model.Statistic; +import org.example.util.ClassRepository; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; -import static org.objectweb.asm.Opcodes.ASM8; +import org.objectweb.asm.Opcodes; +import static org.objectweb.asm.Opcodes.ASM9; public class ClassStatisticVisitor extends ClassVisitor { private final Statistic statistic; + private final ClassRepository repository; - private Integer fieldCount = 0; + private String className; + private int fieldCount = 0; + private int overriddenCount = 0; - public ClassStatisticVisitor(Statistic statistic) { - super(ASM8); + public ClassStatisticVisitor(Statistic statistic, ClassRepository repository) { + super(ASM9); this.statistic = statistic; + this.repository = repository; } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { - } - - public void visitSource(String source, String debug) { - } - - public void visitOuterClass(String owner, String name, String desc) { - } - - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - return super.visitAnnotation(desc, visible); - } + this.className = name; - public void visitAttribute(Attribute attr) { - } + int depth = repository.getDepth(name); + statistic.writeDepth(depth); - // todo also visit inner classes - public void visitInnerClass(String name, String outerName, String innerName, int access) { + super.visit(version, access, name, signature, superName, interfaces); } public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { @@ -45,11 +38,29 @@ public FieldVisitor visitField(int access, String name, String desc, String sign } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - return super.visitMethod(access, name, desc, signature, exceptions); + MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); + + boolean isPrivate = (access & Opcodes.ACC_PRIVATE) != 0; + boolean isStatic = (access & Opcodes.ACC_STATIC) != 0; + + if (!isPrivate && !isStatic && !name.startsWith("<")) { + String methodSig = name + desc; + if (repository.isOverridden(className, methodSig)) { + overriddenCount++; + } + } + + boolean hasCode = (access & Opcodes.ACC_ABSTRACT) == 0 && (access & Opcodes.ACC_NATIVE) == 0; + + if (!hasCode) { + return mv; + } + + return new AbcMethodVisitor(mv, statistic.getAbcMetric()); } public void visitEnd() { statistic.updateFieldStatistic(fieldCount); + statistic.writeOverriddenCount(overriddenCount); } } - From 1278f5224daa72f61f64a82d05bd173bd738287c Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Thu, 4 Dec 2025 03:22:20 +0300 Subject: [PATCH 4/7] implement integration testing with multimodule architecture --- analyzer/build.gradle.kts | 19 +++++++++++ .../itmo/bytecodeanalyzer}/Analyzer.java | 20 ++++++------ .../itmo/bytecodeanalyzer/api}/Statistic.java | 5 ++- .../bytecodeanalyzer}/model/AbcMetric.java | 2 +- .../model/AverageHolder.java | 2 +- .../model/hierarchy/ClassHierarchyNode.java | 2 +- .../util/ByteCodePrinter.java | 2 +- .../util/CheckFrameAnalyzer.java | 2 +- .../util/ClassRepository.java | 6 ++-- .../util/provider/ClassProvider.java | 2 +- .../provider/impl/CompositeClassProvider.java | 4 +-- .../util/provider/impl/JarClassProvider.java | 4 +-- .../provider/impl/SystemClassProvider.java | 4 +-- .../visitor/AbcMethodVisitor.java | 4 +-- .../visitor/ClassPrinter.java | 2 +- .../visitor/ClassStatisticVisitor.java | 6 ++-- .../src}/main/resources/fescar.zip | Bin .../src}/main/resources/guava.zip | Bin .../src}/main/resources/sample.jar | Bin .../src}/main/resources/sample.kt | 0 build.gradle.kts | 25 ++++----------- example/build.gradle.kts | 28 ++++++++++++++++ .../example/ComplexClass.java | 23 ++++++++++++++ .../example/IntegrationTest.java | 30 ++++++++++++++++++ settings.gradle.kts | 4 ++- 25 files changed, 144 insertions(+), 52 deletions(-) create mode 100644 analyzer/build.gradle.kts rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/Analyzer.java (72%) rename {src/main/java/org/example/model => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api}/Statistic.java (88%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/model/AbcMetric.java (97%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/model/AverageHolder.java (88%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/model/hierarchy/ClassHierarchyNode.java (95%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/util/ByteCodePrinter.java (98%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/util/CheckFrameAnalyzer.java (99%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/util/ClassRepository.java (95%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/util/provider/ClassProvider.java (84%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/util/provider/impl/CompositeClassProvider.java (82%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/util/provider/impl/JarClassProvider.java (81%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/util/provider/impl/SystemClassProvider.java (68%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/visitor/AbcMethodVisitor.java (96%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/visitor/ClassPrinter.java (95%) rename {src/main/java/org/example => analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer}/visitor/ClassStatisticVisitor.java (92%) rename {src => analyzer/src}/main/resources/fescar.zip (100%) rename {src => analyzer/src}/main/resources/guava.zip (100%) rename {src => analyzer/src}/main/resources/sample.jar (100%) rename {src => analyzer/src}/main/resources/sample.kt (100%) create mode 100644 example/build.gradle.kts create mode 100644 example/src/main/java/com/quicklybly/itmo/bytecodeanalizer/example/ComplexClass.java create mode 100644 example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java diff --git a/analyzer/build.gradle.kts b/analyzer/build.gradle.kts new file mode 100644 index 0000000..abd06ba --- /dev/null +++ b/analyzer/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("java-library") +} + +dependencies { + implementation("org.ow2.asm:asm:9.5") + implementation("org.ow2.asm:asm-tree:9.5") + implementation("org.ow2.asm:asm-analysis:9.5") + implementation("org.ow2.asm:asm-util:9.5") + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + + +tasks.test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/src/main/java/org/example/Analyzer.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/Analyzer.java similarity index 72% rename from src/main/java/org/example/Analyzer.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/Analyzer.java index a142134..3f7fb13 100644 --- a/src/main/java/org/example/Analyzer.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/Analyzer.java @@ -1,24 +1,24 @@ -package org.example; - +package com.quicklybly.itmo.bytecodeanalyzer; + +import com.quicklybly.itmo.bytecodeanalyzer.api.Statistic; +import com.quicklybly.itmo.bytecodeanalyzer.util.ClassRepository; +import com.quicklybly.itmo.bytecodeanalyzer.util.provider.ClassProvider; +import com.quicklybly.itmo.bytecodeanalyzer.util.provider.impl.CompositeClassProvider; +import com.quicklybly.itmo.bytecodeanalyzer.util.provider.impl.JarClassProvider; +import com.quicklybly.itmo.bytecodeanalyzer.util.provider.impl.SystemClassProvider; +import com.quicklybly.itmo.bytecodeanalyzer.visitor.ClassStatisticVisitor; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; import java.util.List; import java.util.jar.JarFile; -import org.example.model.Statistic; -import org.example.util.ClassRepository; -import org.example.util.provider.ClassProvider; -import org.example.util.provider.impl.CompositeClassProvider; -import org.example.util.provider.impl.JarClassProvider; -import org.example.util.provider.impl.SystemClassProvider; -import org.example.visitor.ClassStatisticVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; public class Analyzer { public static void main(String[] args) throws IOException { - var path = Path.of("src/main/resources/sample.jar"); + var path = Path.of("analyzer/src/main/resources/sample.jar"); var analyzer = new Analyzer(); var statistic = analyzer.analyzeJar(path); diff --git a/src/main/java/org/example/model/Statistic.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/Statistic.java similarity index 88% rename from src/main/java/org/example/model/Statistic.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/Statistic.java index fed7fd2..c1159c4 100644 --- a/src/main/java/org/example/model/Statistic.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/Statistic.java @@ -1,4 +1,7 @@ -package org.example.model; +package com.quicklybly.itmo.bytecodeanalyzer.api; + +import com.quicklybly.itmo.bytecodeanalyzer.model.AbcMetric; +import com.quicklybly.itmo.bytecodeanalyzer.model.AverageHolder; public class Statistic { diff --git a/src/main/java/org/example/model/AbcMetric.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AbcMetric.java similarity index 97% rename from src/main/java/org/example/model/AbcMetric.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AbcMetric.java index 8a1a559..d8f1e7b 100644 --- a/src/main/java/org/example/model/AbcMetric.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AbcMetric.java @@ -1,4 +1,4 @@ -package org.example.model; +package com.quicklybly.itmo.bytecodeanalyzer.model; /* ABC rules for Java diff --git a/src/main/java/org/example/model/AverageHolder.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AverageHolder.java similarity index 88% rename from src/main/java/org/example/model/AverageHolder.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AverageHolder.java index bd05ed2..56fcd4e 100644 --- a/src/main/java/org/example/model/AverageHolder.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AverageHolder.java @@ -1,4 +1,4 @@ -package org.example.model; +package com.quicklybly.itmo.bytecodeanalyzer.model; public class AverageHolder { private final Object lock = new Object(); diff --git a/src/main/java/org/example/model/hierarchy/ClassHierarchyNode.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/hierarchy/ClassHierarchyNode.java similarity index 95% rename from src/main/java/org/example/model/hierarchy/ClassHierarchyNode.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/hierarchy/ClassHierarchyNode.java index a62dd77..b15f865 100644 --- a/src/main/java/org/example/model/hierarchy/ClassHierarchyNode.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/hierarchy/ClassHierarchyNode.java @@ -1,4 +1,4 @@ -package org.example.model.hierarchy; +package com.quicklybly.itmo.bytecodeanalyzer.model.hierarchy; import java.util.Collections; import java.util.Set; diff --git a/src/main/java/org/example/util/ByteCodePrinter.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/ByteCodePrinter.java similarity index 98% rename from src/main/java/org/example/util/ByteCodePrinter.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/ByteCodePrinter.java index 3d1ca38..d74b95c 100644 --- a/src/main/java/org/example/util/ByteCodePrinter.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/ByteCodePrinter.java @@ -1,4 +1,4 @@ -package org.example.util; +package com.quicklybly.itmo.bytecodeanalyzer.util; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; diff --git a/src/main/java/org/example/util/CheckFrameAnalyzer.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/CheckFrameAnalyzer.java similarity index 99% rename from src/main/java/org/example/util/CheckFrameAnalyzer.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/CheckFrameAnalyzer.java index 5231ca7..c312d9e 100644 --- a/src/main/java/org/example/util/CheckFrameAnalyzer.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/CheckFrameAnalyzer.java @@ -1,4 +1,4 @@ -package org.example.util; +package com.quicklybly.itmo.bytecodeanalyzer.util; // 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/util/ClassRepository.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/ClassRepository.java similarity index 95% rename from src/main/java/org/example/util/ClassRepository.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/ClassRepository.java index 9d5cee6..f3ececd 100644 --- a/src/main/java/org/example/util/ClassRepository.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/ClassRepository.java @@ -1,4 +1,4 @@ -package org.example.util; +package com.quicklybly.itmo.bytecodeanalyzer.util; import java.io.IOException; import java.io.InputStream; @@ -6,8 +6,8 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.example.model.hierarchy.ClassHierarchyNode; -import org.example.util.provider.ClassProvider; +import com.quicklybly.itmo.bytecodeanalyzer.model.hierarchy.ClassHierarchyNode; +import com.quicklybly.itmo.bytecodeanalyzer.util.provider.ClassProvider; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; diff --git a/src/main/java/org/example/util/provider/ClassProvider.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/ClassProvider.java similarity index 84% rename from src/main/java/org/example/util/provider/ClassProvider.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/ClassProvider.java index 51bfc6d..65d3f87 100644 --- a/src/main/java/org/example/util/provider/ClassProvider.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/ClassProvider.java @@ -1,4 +1,4 @@ -package org.example.util.provider; +package com.quicklybly.itmo.bytecodeanalyzer.util.provider; import java.io.IOException; import java.io.InputStream; diff --git a/src/main/java/org/example/util/provider/impl/CompositeClassProvider.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/impl/CompositeClassProvider.java similarity index 82% rename from src/main/java/org/example/util/provider/impl/CompositeClassProvider.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/impl/CompositeClassProvider.java index 557de57..3a7de0d 100644 --- a/src/main/java/org/example/util/provider/impl/CompositeClassProvider.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/impl/CompositeClassProvider.java @@ -1,9 +1,9 @@ -package org.example.util.provider.impl; +package com.quicklybly.itmo.bytecodeanalyzer.util.provider.impl; import java.io.IOException; import java.io.InputStream; import java.util.List; -import org.example.util.provider.ClassProvider; +import com.quicklybly.itmo.bytecodeanalyzer.util.provider.ClassProvider; public class CompositeClassProvider implements ClassProvider { diff --git a/src/main/java/org/example/util/provider/impl/JarClassProvider.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/impl/JarClassProvider.java similarity index 81% rename from src/main/java/org/example/util/provider/impl/JarClassProvider.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/impl/JarClassProvider.java index f81af7d..8d7cd03 100644 --- a/src/main/java/org/example/util/provider/impl/JarClassProvider.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/impl/JarClassProvider.java @@ -1,10 +1,10 @@ -package org.example.util.provider.impl; +package com.quicklybly.itmo.bytecodeanalyzer.util.provider.impl; import java.io.IOException; import java.io.InputStream; import java.util.jar.JarFile; import java.util.zip.ZipEntry; -import org.example.util.provider.ClassProvider; +import com.quicklybly.itmo.bytecodeanalyzer.util.provider.ClassProvider; public class JarClassProvider implements ClassProvider { diff --git a/src/main/java/org/example/util/provider/impl/SystemClassProvider.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/impl/SystemClassProvider.java similarity index 68% rename from src/main/java/org/example/util/provider/impl/SystemClassProvider.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/impl/SystemClassProvider.java index 068cd1d..032515b 100644 --- a/src/main/java/org/example/util/provider/impl/SystemClassProvider.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/util/provider/impl/SystemClassProvider.java @@ -1,7 +1,7 @@ -package org.example.util.provider.impl; +package com.quicklybly.itmo.bytecodeanalyzer.util.provider.impl; import java.io.InputStream; -import org.example.util.provider.ClassProvider; +import com.quicklybly.itmo.bytecodeanalyzer.util.provider.ClassProvider; public class SystemClassProvider implements ClassProvider { diff --git a/src/main/java/org/example/visitor/AbcMethodVisitor.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/AbcMethodVisitor.java similarity index 96% rename from src/main/java/org/example/visitor/AbcMethodVisitor.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/AbcMethodVisitor.java index c5bf8db..fc23b21 100644 --- a/src/main/java/org/example/visitor/AbcMethodVisitor.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/AbcMethodVisitor.java @@ -1,6 +1,6 @@ -package org.example.visitor; +package com.quicklybly.itmo.bytecodeanalyzer.visitor; -import org.example.model.AbcMetric; +import com.quicklybly.itmo.bytecodeanalyzer.model.AbcMetric; import org.objectweb.asm.Handle; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; diff --git a/src/main/java/org/example/visitor/ClassPrinter.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassPrinter.java similarity index 95% rename from src/main/java/org/example/visitor/ClassPrinter.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassPrinter.java index ba1ab9f..f224ec8 100644 --- a/src/main/java/org/example/visitor/ClassPrinter.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassPrinter.java @@ -1,4 +1,4 @@ -package org.example.visitor; +package com.quicklybly.itmo.bytecodeanalyzer.visitor; import org.objectweb.asm.*; diff --git a/src/main/java/org/example/visitor/ClassStatisticVisitor.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassStatisticVisitor.java similarity index 92% rename from src/main/java/org/example/visitor/ClassStatisticVisitor.java rename to analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassStatisticVisitor.java index 84126c3..aea575c 100644 --- a/src/main/java/org/example/visitor/ClassStatisticVisitor.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassStatisticVisitor.java @@ -1,7 +1,7 @@ -package org.example.visitor; +package com.quicklybly.itmo.bytecodeanalyzer.visitor; -import org.example.model.Statistic; -import org.example.util.ClassRepository; +import com.quicklybly.itmo.bytecodeanalyzer.api.Statistic; +import com.quicklybly.itmo.bytecodeanalyzer.util.ClassRepository; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; diff --git a/src/main/resources/fescar.zip b/analyzer/src/main/resources/fescar.zip similarity index 100% rename from src/main/resources/fescar.zip rename to analyzer/src/main/resources/fescar.zip diff --git a/src/main/resources/guava.zip b/analyzer/src/main/resources/guava.zip similarity index 100% rename from src/main/resources/guava.zip rename to analyzer/src/main/resources/guava.zip diff --git a/src/main/resources/sample.jar b/analyzer/src/main/resources/sample.jar similarity index 100% rename from src/main/resources/sample.jar rename to analyzer/src/main/resources/sample.jar diff --git a/src/main/resources/sample.kt b/analyzer/src/main/resources/sample.kt similarity index 100% rename from src/main/resources/sample.kt rename to analyzer/src/main/resources/sample.kt diff --git a/build.gradle.kts b/build.gradle.kts index 2b92afd..5bc764f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,24 +2,11 @@ plugins { id("java") } -group = "org.example" -version = "1.0-SNAPSHOT" +allprojects { + group = "org.example" + version = "1.0-SNAPSHOT" -repositories { - mavenCentral() + repositories { + mavenCentral() + } } - -dependencies { - implementation("org.ow2.asm:asm:9.5") - implementation("org.ow2.asm:asm-tree:9.5") - implementation("org.ow2.asm:asm-analysis:9.5") - implementation("org.ow2.asm:asm-util:9.5") - - testImplementation(platform("org.junit:junit-bom:5.10.0")) - testImplementation("org.junit.jupiter:junit-jupiter") - testRuntimeOnly("org.junit.platform:junit-platform-launcher") -} - -tasks.test { - useJUnitPlatform() -} \ No newline at end of file diff --git a/example/build.gradle.kts b/example/build.gradle.kts new file mode 100644 index 0000000..117f9ba --- /dev/null +++ b/example/build.gradle.kts @@ -0,0 +1,28 @@ +import org.gradle.jvm.tasks.Jar + +plugins { + id("java") +} + +dependencies { + implementation(project(":analyzer")) + + testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +val jarTask = tasks.named("jar") { + manifest { + attributes("Main-Class" to "org.example.target.Main") + } + archiveFileName.set("example-target.jar") +} + +tasks.test { + useJUnitPlatform() + + dependsOn(jarTask) + + systemProperty("test.jar.path", jarTask.get().archiveFile.get().asFile.absolutePath) +} diff --git a/example/src/main/java/com/quicklybly/itmo/bytecodeanalizer/example/ComplexClass.java b/example/src/main/java/com/quicklybly/itmo/bytecodeanalizer/example/ComplexClass.java new file mode 100644 index 0000000..1244aaf --- /dev/null +++ b/example/src/main/java/com/quicklybly/itmo/bytecodeanalizer/example/ComplexClass.java @@ -0,0 +1,23 @@ +package com.quicklybly.itmo.bytecodeanalizer.example; + +import java.util.ArrayList; + +public class ComplexClass extends ArrayList { + + private final int x = 0; + private final int y = 0; + + public void heavyMethod() { + if (size() > 10) { + System.out.println("List is big"); + } else { + System.out.println("List is small"); + } + new Thread(() -> System.out.println("Async")).start(); + } + + @Override + public int size() { + return super.size(); + } +} diff --git a/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java b/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java new file mode 100644 index 0000000..16ece4e --- /dev/null +++ b/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java @@ -0,0 +1,30 @@ +package com.quicklybly.itmo.bytecodeanalizer.example; + +import com.quicklybly.itmo.bytecodeanalyzer.Analyzer; +import com.quicklybly.itmo.bytecodeanalyzer.api.Statistic; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class IntegrationTest { + + @Test + public void analyzeComplexClass() throws IOException { + String jarPathStr = System.getProperty("test.jar.path"); + + Assertions.assertNotNull(jarPathStr, "System property 'test.jar.path' is not set"); + + Path jarPath = Path.of(jarPathStr); + File jarFile = jarPath.toFile(); + + System.out.println("Testing artifact: " + jarFile.getAbsolutePath()); + Assertions.assertTrue(jarFile.exists(), "JAR file does not exist at path: " + jarPath); + + Analyzer analyzer = new Analyzer(); + + Statistic stats = analyzer.analyzeJar(jarPath); + stats.printStatistic(); // todo + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index a7f83d7..1665c95 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1 +1,3 @@ -rootProject.name = "bytecode-template" \ No newline at end of file +rootProject.name = "bytecode-template" + +include("analyzer", "example") From c6a59dcbc5c6cefcb0252e0d7c338637a8adcaaf Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Thu, 4 Dec 2025 03:24:44 +0300 Subject: [PATCH 5/7] remove unused class --- .../visitor/ClassPrinter.java | 46 ------------------- 1 file changed, 46 deletions(-) delete mode 100644 analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassPrinter.java diff --git a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassPrinter.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassPrinter.java deleted file mode 100644 index f224ec8..0000000 --- a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/visitor/ClassPrinter.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.quicklybly.itmo.bytecodeanalyzer.visitor; - -import org.objectweb.asm.*; - -import static org.objectweb.asm.Opcodes.ASM8; - -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 + " {"); - } - - public void visitSource(String source, String debug) { - } - - public void visitOuterClass(String owner, String name, String desc) { - } - - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - return null; - } - - public void visitAttribute(Attribute attr) { - } - - public void visitInnerClass(String name, String outerName, String innerName, int access) { - } - - public 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("}"); - } -} - From ae4c113fa9d2d5ac2a919104741c35a93d3dd8b9 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Thu, 4 Dec 2025 15:50:10 +0300 Subject: [PATCH 6/7] add json adapter --- analyzer/build.gradle.kts | 3 +- .../itmo/bytecodeanalyzer/Analyzer.java | 2 +- .../itmo/bytecodeanalyzer/api/Statistic.java | 31 +++++++++++++++---- .../bytecodeanalyzer/api/StatisticMapper.java | 25 +++++++++++++++ .../bytecodeanalyzer/model/AbcMetric.java | 7 +++++ .../bytecodeanalyzer/model/AverageHolder.java | 7 +++++ example/build.gradle.kts | 1 + .../example/IntegrationTest.java | 8 ++++- 8 files changed, 74 insertions(+), 10 deletions(-) create mode 100644 analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/StatisticMapper.java diff --git a/analyzer/build.gradle.kts b/analyzer/build.gradle.kts index abd06ba..6302e1f 100644 --- a/analyzer/build.gradle.kts +++ b/analyzer/build.gradle.kts @@ -13,7 +13,6 @@ dependencies { testRuntimeOnly("org.junit.platform:junit-platform-launcher") } - tasks.test { useJUnitPlatform() -} \ No newline at end of file +} diff --git a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/Analyzer.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/Analyzer.java index 3f7fb13..00372b1 100644 --- a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/Analyzer.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/Analyzer.java @@ -22,7 +22,7 @@ public static void main(String[] args) throws IOException { var analyzer = new Analyzer(); var statistic = analyzer.analyzeJar(path); - statistic.printStatistic(); + System.out.println(statistic); } public Statistic analyzeJar(Path path) throws IOException { diff --git a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/Statistic.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/Statistic.java index c1159c4..bdeb291 100644 --- a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/Statistic.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/Statistic.java @@ -24,15 +24,34 @@ public void writeOverriddenCount(int overriddenCount) { averageOverriddenCountHolder.updateAverage(overriddenCount); } - public void printStatistic() { - System.out.println("Average field count: " + fieldAverageHolder.getAverage()); - System.out.println("ABC: " + abcMetric.getAbc()); - System.out.println("Inheritance max depth: " + maxInheritanceDepth); - System.out.println("Inheritance average depth: " + averageInheritanceDepthHolder.getAverage()); - System.out.println("Average override count: " + averageOverriddenCountHolder.getAverage()); + @Override + public String toString() { + return "Statistic{" + + "fieldAverageHolder=" + fieldAverageHolder + + ", averageInheritanceDepthHolder=" + averageInheritanceDepthHolder + + ", averageOverriddenCountHolder=" + averageOverriddenCountHolder + + ", maxInheritanceDepth=" + maxInheritanceDepth + + ", abcMetric=" + abcMetric + + '}'; } public AbcMetric getAbcMetric() { return abcMetric; } + + public double getAverageFieldCount() { + return fieldAverageHolder.getAverage(); + } + + public double getAverageInheritanceDepth() { + return averageInheritanceDepthHolder.getAverage(); + } + + public double getAverageOverriddenCount() { + return averageOverriddenCountHolder.getAverage(); + } + + public int getMaxInheritanceDepth() { + return maxInheritanceDepth; + } } diff --git a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/StatisticMapper.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/StatisticMapper.java new file mode 100644 index 0000000..8bc9c9d --- /dev/null +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/api/StatisticMapper.java @@ -0,0 +1,25 @@ +package com.quicklybly.itmo.bytecodeanalyzer.api; + +public class StatisticMapper { + + private StatisticMapper() { + throw new IllegalStateException("Utility class"); + } + + public static String toJson(Statistic statistic) { + return String.format( + "{" + + "\"fieldAverage\":%s," + + "\"averageInheritanceDepth\":%s," + + "\"averageOverriddenCountHolder\":%s," + + "\"maxInheritanceDepth\":%d," + + "\"abc\":%s" + + "}", + statistic.getAverageFieldCount(), + statistic.getAverageInheritanceDepth(), + statistic.getAverageOverriddenCount(), + statistic.getMaxInheritanceDepth(), + statistic.getAbcMetric().getAbc() + ); + } +} diff --git a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AbcMetric.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AbcMetric.java index d8f1e7b..98a301a 100644 --- a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AbcMetric.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AbcMetric.java @@ -58,4 +58,11 @@ public int hashCode() { result = 31 * result + Long.hashCode(condition); return result; } + + @Override + public String toString() { + return "AbcMetric{" + + "abc=" + getAbc() + + '}'; + } } diff --git a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AverageHolder.java b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AverageHolder.java index 56fcd4e..3bc1ea1 100644 --- a/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AverageHolder.java +++ b/analyzer/src/main/java/com/quicklybly/itmo/bytecodeanalyzer/model/AverageHolder.java @@ -15,4 +15,11 @@ public void updateAverage(Integer value) { count++; } } + + @Override + public String toString() { + return "AverageHolder{" + + "average=" + getAverage() + + '}'; + } } diff --git a/example/build.gradle.kts b/example/build.gradle.kts index 117f9ba..b42849d 100644 --- a/example/build.gradle.kts +++ b/example/build.gradle.kts @@ -8,6 +8,7 @@ dependencies { implementation(project(":analyzer")) testImplementation(platform("org.junit:junit-bom:5.10.0")) + testImplementation("org.assertj:assertj-core:3.24.2") testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java b/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java index 16ece4e..6bdf244 100644 --- a/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java +++ b/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java @@ -7,6 +7,7 @@ import java.nio.file.Path; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; public class IntegrationTest { @@ -25,6 +26,11 @@ public void analyzeComplexClass() throws IOException { Analyzer analyzer = new Analyzer(); Statistic stats = analyzer.analyzeJar(jarPath); - stats.printStatistic(); // todo + + assertThat(stats.getMaxInheritanceDepth()).isEqualTo(4); + assertThat(stats.getAverageInheritanceDepth()).isEqualTo(4.0); + assertThat(stats.getAverageFieldCount()).isEqualTo(2.0); + assertThat(stats.getAverageOverriddenCount()).isEqualTo(1.0); + assertThat(stats.getAbcMetric().getAbc()).isBetween(9.0, 10.0); } } From 2a2e70e70a2509b8fceed462fa2fa1b4c61f0ca4 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Thu, 4 Dec 2025 15:54:56 +0300 Subject: [PATCH 7/7] update test a bit --- .../itmo/bytecodeanalizer/example/SimpleClass.java | 5 +++++ .../itmo/bytecodeanalizer/example/IntegrationTest.java | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 example/src/main/java/com/quicklybly/itmo/bytecodeanalizer/example/SimpleClass.java diff --git a/example/src/main/java/com/quicklybly/itmo/bytecodeanalizer/example/SimpleClass.java b/example/src/main/java/com/quicklybly/itmo/bytecodeanalizer/example/SimpleClass.java new file mode 100644 index 0000000..c8137a4 --- /dev/null +++ b/example/src/main/java/com/quicklybly/itmo/bytecodeanalizer/example/SimpleClass.java @@ -0,0 +1,5 @@ +package com.quicklybly.itmo.bytecodeanalizer.example; + +public class SimpleClass { + private final String s = "123"; +} diff --git a/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java b/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java index 6bdf244..804d80e 100644 --- a/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java +++ b/example/src/test/java/com/quicklybly/itmo/bytecodeanalizer/example/IntegrationTest.java @@ -28,9 +28,9 @@ public void analyzeComplexClass() throws IOException { Statistic stats = analyzer.analyzeJar(jarPath); assertThat(stats.getMaxInheritanceDepth()).isEqualTo(4); - assertThat(stats.getAverageInheritanceDepth()).isEqualTo(4.0); - assertThat(stats.getAverageFieldCount()).isEqualTo(2.0); - assertThat(stats.getAverageOverriddenCount()).isEqualTo(1.0); - assertThat(stats.getAbcMetric().getAbc()).isBetween(9.0, 10.0); + assertThat(stats.getAverageInheritanceDepth()).isEqualTo(2.5); + assertThat(stats.getAverageFieldCount()).isEqualTo(1.5); + assertThat(stats.getAverageOverriddenCount()).isEqualTo(0.5); + assertThat(stats.getAbcMetric().getAbc()).isBetween(10.0, 11.0); } }