Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 файла
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
5 changes: 5 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"inputJar": "src/main/resources/sample.jar",
"outputJson": "metrics.json"
}

7 changes: 7 additions & 0 deletions metrics.json
Original file line number Diff line number Diff line change
@@ -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
}
50 changes: 50 additions & 0 deletions src/main/java/org/example/ClassMetrics.java
Original file line number Diff line number Diff line change
@@ -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;
}
}

2 changes: 1 addition & 1 deletion src/main/java/org/example/Example.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/org/example/JarAnalyzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.example;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;

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(ClassVisitor visitor) throws IOException {
try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> 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);
}
}
}
}
}

84 changes: 84 additions & 0 deletions src/main/java/org/example/MetricsAnalyzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.example;

import org.example.consoler.Config;
import org.example.consoler.Resulter;
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();
Resulter metrics = analyzeJar(config.getInputJar());
print(metrics);
JsonMarshaler.write(metrics, config.getOutputJson());
} catch (Exception e) {
System.err.println("Error: " + e.getMessage());
e.printStackTrace();
}
}

private static Resulter analyzeJar(String jarPath) throws IOException {
JarAnalyzer jarAnalyzer = new JarAnalyzer(jarPath);
MultiVisitor multiVisitor = new MultiVisitor();

jarAnalyzer.processClasses(multiVisitor);
List<ClassMetrics> classMetricsList = multiVisitor.getClassMetricsList();

return calculateAggregatedMetrics(classMetricsList);
}

private static Resulter calculateAggregatedMetrics(
List<ClassMetrics> 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()));
}
}

23 changes: 23 additions & 0 deletions src/main/java/org/example/consoler/Config.java
Original file line number Diff line number Diff line change
@@ -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;
}
}

40 changes: 40 additions & 0 deletions src/main/java/org/example/consoler/Resulter.java
Original file line number Diff line number Diff line change
@@ -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;
}
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.example.util;
package org.example.misc;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.tree.ClassNode;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
56 changes: 56 additions & 0 deletions src/main/java/org/example/misc/ClassHierarchyBuilder.java
Original file line number Diff line number Diff line change
@@ -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<String, String> classToParent = new HashMap<>();
private final Map<String, Integer> 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<String, String> 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<String> 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);
}
}

35 changes: 35 additions & 0 deletions src/main/java/org/example/misc/ConfigReader.java
Original file line number Diff line number Diff line change
@@ -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;
}
}

21 changes: 21 additions & 0 deletions src/main/java/org/example/misc/JsonMarshaler.java
Original file line number Diff line number Diff line change
@@ -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");
}
}
}

Loading