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
2 changes: 2 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ dependencies {
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")

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

tasks.test {
Expand Down
8 changes: 8 additions & 0 deletions output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"classesAnalyzed" : 330,
"abc" : 2577.6365143285816,
"maxInheritanceDepth" : 5,
"avgInheritanceDepth" : 1.4393939393939394,
"avgOverriddenMethods" : 2.2790697674418605,
"avgFieldsPerClass" : 2.609090909090909
}
20 changes: 20 additions & 0 deletions src/main/java/analyzer/ClassInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package analyzer;

import java.util.HashSet;
import java.util.Set;

public class ClassInfo {
public final String name;
public final String superName;
public final Set<MethodSignature> methods = new HashSet<>();

public int fieldsCount = 0;
public int assignmentCount = 0;
public int branchCount = 0;
public int conditionCount = 0;

public ClassInfo(String name, String superName) {
this.name = name;
this.superName = superName;
}
}
106 changes: 106 additions & 0 deletions src/main/java/analyzer/JarAnalyzer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package analyzer;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.objectweb.asm.ClassReader;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;

import metrics.AbcCalculator;
import metrics.FieldMetricsCalculator;
import metrics.InheritanceDepthCalculator;
import metrics.InheritanceStats;
import metrics.OverrideMethodsCalculator;
import output.MetricsResult;
import visitor.MetricsClassVisitor;

public class JarAnalyzer {

public static void main(String[] args) throws IOException {

if (args.length < 2) {
System.err.println("Expected 2 arguments: <input.jar> <output.json>");
System.exit(1);
}

String jarPath = args[0];
String jsonOutputPath = args[1];

Map<String, ClassInfo> classes = new HashMap<>();

try (JarFile jarFile = new JarFile(jarPath)) {
Enumeration<JarEntry> entries = jarFile.entries();

while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();

if (!entry.getName().endsWith(".class")) {
continue;
}

try (InputStream is = jarFile.getInputStream(entry)) {
ClassReader reader = new ClassReader(is);
MetricsClassVisitor visitor = new MetricsClassVisitor();
reader.accept(visitor, ClassReader.SKIP_DEBUG);

ClassInfo info = visitor.getClassInfo();
classes.put(info.name, info);
}
}
}


InheritanceStats depthStats =
InheritanceDepthCalculator.calculate(classes);

double abc =
AbcCalculator.totalAssignments(classes);

double avgOverriddenMethods =
OverrideMethodsCalculator.averageOverriddenMethods(classes);

double avgFields =
FieldMetricsCalculator.averageFields(classes);

MetricsResult result = new MetricsResult(
classes.size(),
abc,
depthStats,
avgOverriddenMethods,
avgFields
);

printToConsole(result);

writeJson(result, jsonOutputPath);
}

private static void printToConsole(MetricsResult r) {
System.out.println("====== Metrics ======");
System.out.println("Classes analyzed: " + r.classesAnalyzed);
System.out.println("ABC (assignments): " + r.abc);
System.out.println("Max inheritance depth: " + r.maxInheritanceDepth);
System.out.println("Avg inheritance depth: " + r.avgInheritanceDepth);
System.out.println("Avg overridden methods: " + r.avgOverriddenMethods);
System.out.println("Avg fields per class: " + r.avgFieldsPerClass);
}

private static void writeJson(
MetricsResult result,
String outputPath
) throws IOException {

ObjectMapper mapper = new ObjectMapper();
mapper.enable(SerializationFeature.INDENT_OUTPUT);

mapper.writeValue(new File(outputPath), result);
}
}
18 changes: 18 additions & 0 deletions src/main/java/analyzer/MethodSignature.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package analyzer;

public record MethodSignature(String name, String descriptor) {

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof MethodSignature)) {
return false;
}
MethodSignature that = (MethodSignature) o;
return name.equals(that.name)
&& descriptor.equals(that.descriptor);
}

}
16 changes: 16 additions & 0 deletions src/main/java/metrics/AbcCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package metrics;

import java.util.Map;

import analyzer.ClassInfo;

public class AbcCalculator {

public static double totalAssignments(Map<String, ClassInfo> classes) {
int totalA = classes.values().stream().mapToInt(c -> c.assignmentCount).sum();
int totalB = classes.values().stream().mapToInt(c -> c.branchCount).sum();
int totalC = classes.values().stream().mapToInt(c -> c.conditionCount).sum();

return Math.sqrt(totalA*totalA + totalB*totalB + totalC*totalC);
}
}
20 changes: 20 additions & 0 deletions src/main/java/metrics/FieldMetricsCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package metrics;

import java.util.Map;

import analyzer.ClassInfo;

public class FieldMetricsCalculator {

public static double averageFields(Map<String, ClassInfo> classes) {
if (classes.isEmpty()) return 0.0;

int sum = 0;

for (ClassInfo cls : classes.values()) {
sum += cls.fieldsCount;
}

return (double) sum / classes.size();
}
}
42 changes: 42 additions & 0 deletions src/main/java/metrics/InheritanceDepthCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package metrics;

import java.util.Map;

import analyzer.ClassInfo;

public class InheritanceDepthCalculator {

public static InheritanceStats calculate(Map<String, ClassInfo> classes) {
if (classes.isEmpty()) {
return new InheritanceStats(0, 0.0);
}

int max = 0;
int sum = 0;

for (ClassInfo cls : classes.values()) {
int depth = calculateDepth(cls, classes);
sum += depth;
max = Math.max(max, depth);
}

double avg = (double) sum / classes.size();

return new InheritanceStats(max, avg);
}

private static int calculateDepth(
ClassInfo cls,
Map<String, ClassInfo> classes
) {
int depth = 1;
String current = cls.superName;

while (current != null && classes.containsKey(current)) {
depth++;
current = classes.get(current).superName;
}

return depth;
}
}
4 changes: 4 additions & 0 deletions src/main/java/metrics/InheritanceStats.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package metrics;

public record InheritanceStats(int maxDepth, double avgDepth) {
}
62 changes: 62 additions & 0 deletions src/main/java/metrics/OverrideMethodsCalculator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package metrics;

import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import analyzer.ClassInfo;
import analyzer.MethodSignature;

public class OverrideMethodsCalculator {

public static double averageOverriddenMethods(Map<String, ClassInfo> classes) {
int totalOverrides = 0;
int classCount = 0;

for (ClassInfo cls : classes.values()) {
if (cls.superName == null) continue;

ClassInfo superCls = classes.get(cls.superName);
if (superCls == null) continue;

int overridden = countOverrides(cls, classes);
totalOverrides += overridden;
classCount = overridden > 0 ? classCount + 1 : classCount;
}

if (classCount == 0) return 0.0;

return (double) totalOverrides / classCount;
}

public static int countOverrides(ClassInfo cls, Map<String, ClassInfo> classes) {
int count = 0;
Set<MethodSignature> seen = new HashSet<>();

String superName = cls.superName;

while (superName != null && classes.containsKey(superName)) {
ClassInfo superCls = classes.get(superName);

for (MethodSignature m : cls.methods) {
if (!seen.contains(m) && (isObjectMethod(m) || superCls.methods.contains(m))) {
count++;
seen.add(m);
}
}

superName = superCls.superName;
}

return count;
}

private static boolean isObjectMethod(MethodSignature method) {
String methodSignature = method.name() + method.descriptor();
return methodSignature.equals("toString()Ljava/lang/String;") ||
methodSignature.equals("equals(Ljava/lang/Object;)Z") ||
methodSignature.equals("hashCode()I") ||
methodSignature.equals("clone()Ljava/lang/Object;") ||
methodSignature.equals("finalize()V");
}
}
30 changes: 30 additions & 0 deletions src/main/java/output/MetricsResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package output;

import metrics.InheritanceStats;

public class MetricsResult {

public int classesAnalyzed;
public double abc;

public int maxInheritanceDepth;
public double avgInheritanceDepth;

public double avgOverriddenMethods;
public double avgFieldsPerClass;

public MetricsResult(
int classesAnalyzed,
double abc,
InheritanceStats inheritanceStats,
double avgOverriddenMethods,
double avgFieldsPerClass
) {
this.classesAnalyzed = classesAnalyzed;
this.abc = abc;
this.maxInheritanceDepth = inheritanceStats.maxDepth();
this.avgInheritanceDepth = inheritanceStats.avgDepth();
this.avgOverriddenMethods = avgOverriddenMethods;
this.avgFieldsPerClass = avgFieldsPerClass;
}
}
Loading