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
13 changes: 12 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,22 @@ dependencies {
implementation("org.ow2.asm:asm-analysis:9.5")
implementation("org.ow2.asm:asm-util:9.5")

implementation("com.fasterxml.jackson.core:jackson-databind:2.15.3")

implementation("org.slf4j:slf4j-api:2.0.9")
implementation("ch.qos.logback:logback-classic:1.4.11")

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()
}
}

java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(21))
}
}
Empty file modified gradlew
100644 → 100755
Empty file.
90 changes: 90 additions & 0 deletions src/main/java/org/example/JarAnalyzerApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.example;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;

import org.example.model.ClassInfo;
import org.example.model.JarAnalysisResult;
import org.example.service.JarProcessor;
import org.example.service.MetricsCalculator;
import org.example.service.ReportGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JarAnalyzerApp {
private static final Logger log = LoggerFactory.getLogger(JarAnalyzerApp.class);

private final JarProcessor jarProcessor;
private final MetricsCalculator metricsCalculator;
private final ReportGenerator reportGenerator;

public JarAnalyzerApp() {
this.jarProcessor = new JarProcessor();
this.metricsCalculator = new MetricsCalculator();
this.reportGenerator = new ReportGenerator();
}

public void analyze(String jarPath, String outputPath) {
try {
Path jarFilePath = Path.of(jarPath);
validateInputFile(jarFilePath);

System.out.println("Processing JAR file: " + jarFilePath.getFileName());
List<ClassInfo> classes = jarProcessor.process(jarFilePath);

if (classes.isEmpty()) {
log.warn("No classes found in the JAR file");
}

System.out.println("Calculating metrics...");
String jarFileName = jarFilePath.getFileName().toString();
JarAnalysisResult result = metricsCalculator.calculate(jarFileName, classes);

if (outputPath == null) {
reportGenerator.printToConsole(result);
} else {
writeToFile(result, outputPath);
}
} catch (Exception e) {
log.error("Application run failed", e);
System.exit(1);
}
}

private void writeToFile(JarAnalysisResult result, String outputPath) throws IOException {
Path outputFilePath = Path.of(outputPath);
reportGenerator.writeToJson(result, outputFilePath);
System.out.println("JSON report written to: " + outputFilePath.toAbsolutePath());
}

private void validateInputFile(Path jarPath) {
if (!Files.exists(jarPath)) {
throw new IllegalArgumentException("Input file does not exist: " + jarPath);
}

if (!Files.isRegularFile(jarPath)) {
throw new IllegalArgumentException("Input path is not a file: " + jarPath);
}

if (!jarPath.toString().toLowerCase().endsWith(".jar")) {
throw new IllegalArgumentException("Input file must be a JAR file: " + jarPath);
}
}

public static void main(String[] args) {
if (args.length < 1 || args.length > 2) {
System.out.println("Usage:");
System.out.println("java -jar analyzer.jar <input.jar> - output to console");
System.out.println("java -jar analyzer.jar <input.jar> <output.json> - output to file");
System.exit(1);
}

String inputJar = args[0];
String outputJson = args.length == 2 ? args[1] : null;

JarAnalyzerApp app = new JarAnalyzerApp();
app.analyze(inputJar, outputJson);
}
}
66 changes: 66 additions & 0 deletions src/main/java/org/example/model/ABCMetrics.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.example.model;

public final class ABCMetrics {
private int assignments;
private int branches;
private int conditions;

public ABCMetrics() {
this(0, 0, 0);
}

public ABCMetrics(int assignments, int branches, int conditions) {
this.assignments = assignments;
this.branches = branches;
this.conditions = conditions;
}

public void incrementAssignments() {
assignments++;
}

public void incrementBranches() {
branches++;
}

public void incrementConditions() {
conditions++;
}

public void incrementConditions(int delta) {
conditions += delta;
}

public void add(ABCMetrics other) {
this.assignments += other.assignments;
this.branches += other.branches;
this.conditions += other.conditions;
}

public int getAssignments() {
return assignments;
}

public int getBranches() {
return branches;
}

public int getConditions() {
return conditions;
}

public double calculateMagnitude() {
return Math.sqrt(
(double) assignments * assignments +
(double) branches * branches +
(double) conditions * conditions
);
}

@Override
public String toString() {
return String.format("ABC(A=%d, B=%d, C=%d, magnitude=%.2f)",
assignments, branches, conditions, calculateMagnitude());
}
}

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

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

public final class ClassInfo {
private final String name;
private final String superName;
private final List<String> interfaces;
private final Set<MethodInfo> methods;
private final int fieldCount;
private final ABCMetrics abcMetrics;
private final boolean isInterface;

private ClassInfo(Builder builder) {
this.name = Objects.requireNonNull(builder.name, "name cannot be null");
this.superName = builder.superName;
this.interfaces = List.copyOf(builder.interfaces);
this.methods = Set.copyOf(builder.methods);
this.fieldCount = builder.fieldCount;
this.abcMetrics = builder.abcMetrics;
this.isInterface = builder.isInterface;
}

public String getName() {
return name;
}

public String getSuperName() {
return superName;
}

public List<String> getInterfaces() {
return interfaces;
}

public Set<MethodInfo> getMethods() {
return methods;
}

public int getFieldCount() {
return fieldCount;
}

public ABCMetrics getAbcMetrics() {
return abcMetrics;
}

public boolean isInterface() {
return isInterface;
}

@Override
public String toString() {
return String.format("ClassInfo{name='%s', super='%s', interfaces=%s, methods=%d, fields=%d}",
name, superName, interfaces, methods.size(), fieldCount);
}

public static Builder builder() {
return new Builder();
}

public static final class Builder {
private String name;
private String superName;
private final List<String> interfaces = new ArrayList<>();
private final Set<MethodInfo> methods = new HashSet<>();
private int fieldCount = 0;
private ABCMetrics abcMetrics = new ABCMetrics();
private boolean isInterface = false;

private Builder() {
}

public Builder name(String name) {
this.name = name;
return this;
}

public Builder superName(String superName) {
this.superName = superName;
return this;
}

public Builder addInterface(String interfaceName) {
if (interfaceName != null) {
this.interfaces.add(interfaceName);
}
return this;
}

public Builder addInterfaces(String[] interfaceNames) {
if (interfaceNames != null) {
for (String name : interfaceNames) {
addInterface(name);
}
}
return this;
}

public Builder addMethod(MethodInfo method) {
this.methods.add(method);
return this;
}

public Builder incrementFieldCount() {
this.fieldCount++;
return this;
}

public Builder abcMetrics(ABCMetrics abcMetrics) {
this.abcMetrics = abcMetrics;
return this;
}

public Builder isInterface(boolean isInterface) {
this.isInterface = isInterface;
return this;
}

public ClassInfo build() {
return new ClassInfo(this);
}
}
}

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

public record JarAnalysisResult(
String jarFileName,
int totalClasses,
int totalInterfaces,
InheritanceMetrics inheritance,
ABCSummary abc,
double averageOverriddenMethods,
double averageFieldsPerClass
) {
public record InheritanceMetrics(
int maxDepth,
double averageDepth
) {
}

public record ABCSummary(
int totalAssignments,
int totalBranches,
int totalConditions,
double magnitude
) {
public static ABCSummary from(ABCMetrics metrics) {
return new ABCSummary(
metrics.getAssignments(),
metrics.getBranches(),
metrics.getConditions(),
metrics.calculateMagnitude()
);
}
}
}

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

import java.util.Objects;

public record MethodInfo(String name, String descriptor) {
public MethodInfo {
Objects.requireNonNull(name, "name cannot be null");
Objects.requireNonNull(descriptor, "descriptor cannot be null");
}

public boolean isConstructor() {
return "<init>".equals(name);
}

public boolean isStaticInitializer() {
return "<clinit>".equals(name);
}

@Override
public String toString() {
return name + descriptor;
}
}

Loading