google-site-verification: google61fe8ba583a51912.html
Code Metrics Calculator in Java

Introduction

A comprehensive Code Metrics Calculator that analyzes Java source code to provide various software metrics including complexity, maintainability, and quality indicators.

Features

  • Line Metrics: Count lines of code, comments, blanks
  • Complexity Metrics: Cyclomatic complexity, method count
  • Object-Oriented Metrics: Inheritance depth, coupling, cohesion
  • Maintainability Index: Calculate code maintainability
  • Halstead Metrics: Program volume, difficulty, effort
  • Code Smell Detection: Identify potential issues

Core Implementation

Main Metrics Calculator Class

package com.codemetrics; import java.io.*; import java.nio.file.*; import java.util.*; import java.util.regex.*; import java.util.stream.*; public class CodeMetricsCalculator { private final List<File> javaFiles; private final MetricsResult totalMetrics; private final List<FileMetrics> fileMetrics; public CodeMetricsCalculator(String directoryPath) throws IOException { this.javaFiles = findJavaFiles(directoryPath); this.totalMetrics = new MetricsResult(); this.fileMetrics = new ArrayList<>(); } public CodeMetricsCalculator(List<File> javaFiles) { this.javaFiles = javaFiles; this.totalMetrics = new MetricsResult(); this.fileMetrics = new ArrayList<>(); } private List<File> findJavaFiles(String directoryPath) throws IOException { Path startDir = Paths.get(directoryPath); if (!Files.exists(startDir) || !Files.isDirectory(startDir)) { throw new IllegalArgumentException("Directory does not exist: " + directoryPath); } try (Stream<Path> paths = Files.walk(startDir)) { return paths .filter(Files::isRegularFile) .filter(path -> path.toString().endsWith(".java")) .map(Path::toFile) .collect(Collectors.toList()); } } public AnalysisResult analyze() { System.out.println("Analyzing " + javaFiles.size() + " Java files..."); for (File javaFile : javaFiles) { try { FileMetrics metrics = analyzeFile(javaFile); fileMetrics.add(metrics); totalMetrics.aggregate(metrics.getMetrics()); } catch (IOException e) { System.err.println("Error analyzing file: " + javaFile.getPath() + " - " + e.getMessage()); } } return new AnalysisResult(fileMetrics, totalMetrics); } private FileMetrics analyzeFile(File javaFile) throws IOException { String content = new String(Files.readAllBytes(javaFile.toPath())); FileMetrics metrics = new FileMetrics(javaFile.getName(), javaFile.getPath()); // Basic line metrics calculateLineMetrics(content, metrics); // Complexity metrics calculateComplexityMetrics(content, metrics); // OO metrics calculateOOMetrics(content, metrics); // Halstead metrics calculateHalsteadMetrics(content, metrics); // Maintainability index calculateMaintainabilityIndex(metrics); // Code smells detectCodeSmells(content, metrics); return metrics; } private void calculateLineMetrics(String content, FileMetrics metrics) { String[] lines = content.split("\r\n|\r|\n"); int totalLines = lines.length; int blankLines = 0; int commentLines = 0; int codeLines = 0; boolean inBlockComment = false; for (String line : lines) { String trimmedLine = line.trim(); if (trimmedLine.isEmpty()) { blankLines++; continue; } if (inBlockComment) { commentLines++; if (trimmedLine.contains("*/")) { inBlockComment = false; } continue; } if (trimmedLine.startsWith("/*")) { commentLines++; inBlockComment = !trimmedLine.contains("*/"); continue; } if (trimmedLine.startsWith("//")) { commentLines++; continue; } // Check for lines with code and comments if (trimmedLine.contains("//")) { commentLines++; codeLines++; // Also counts as code line continue; } codeLines++; } metrics.setLineMetrics(totalLines, blankLines, commentLines, codeLines); } private void calculateComplexityMetrics(String content, FileMetrics metrics) { // Count methods int methodCount = countMethods(content); metrics.setMethodCount(methodCount); // Calculate cyclomatic complexity int complexity = calculateCyclomaticComplexity(content); metrics.setCyclomaticComplexity(complexity); // Average complexity per method double avgComplexity = methodCount > 0 ? (double) complexity / methodCount : 0; metrics.setAverageComplexity(avgComplexity); } private int countMethods(String content) { // Pattern to match method declarations Pattern methodPattern = Pattern.compile( "(public|protected|private|static|\\s) +[\\w\\<\\>\\[\\]]+\\s+(\\w+) *\\([^)]*\\) *\\{? *\\n?", Pattern.MULTILINE ); Matcher matcher = methodPattern.matcher(content); int count = 0; while (matcher.find()) { // Exclude constructors and simple getters/setters for more accurate count String methodName = matcher.group(2); if (!methodName.matches("^[A-Z].*") && // Not constructor !methodName.matches("^(get|set|is).*")) { // Not simple accessor count++; } } return Math.max(count, 1); // At least 1 for the class itself } private int calculateCyclomaticComplexity(String content) { int complexity = 1; // Base complexity // Patterns for decision points String[] decisionPatterns = { "\\bif\\s*\\(", "\\belse\\s*\\{", "\\bfor\\s*\\(", "\\bwhile\\s*\\(", "\\bcase\\b", "\\bdefault\\b", "\\bcatch\\s*\\(", "\\b&&\\b", "\\b\\|\\|\\b", "\\?\\s*[^:]*\\s*:" }; for (String pattern : decisionPatterns) { Pattern p = Pattern.compile(pattern); Matcher matcher = p.matcher(content); while (matcher.find()) { complexity++; } } return complexity; } private void calculateOOMetrics(String content, FileMetrics metrics) { // Count classes int classCount = countPattern(content, "\\bclass\\s+\\w+"); metrics.setClassCount(classCount); // Count interfaces int interfaceCount = countPattern(content, "\\binterface\\s+\\w+"); metrics.setInterfaceCount(interfaceCount); // Estimate inheritance depth (simplified) int inheritanceDepth = estimateInheritanceDepth(content); metrics.setInheritanceDepth(inheritanceDepth); // Count dependencies (import statements) int dependencyCount = countPattern(content, "^import\\s+.*", Pattern.MULTILINE); metrics.setDependencyCount(dependencyCount); } private int countPattern(String content, String pattern) { return countPattern(content, pattern, 0); } private int countPattern(String content, String pattern, int flags) { Pattern p = Pattern.compile(pattern, flags); Matcher matcher = p.matcher(content); int count = 0; while (matcher.find()) { count++; } return count; } private int estimateInheritanceDepth(String content) { // Simplified estimation - count "extends" and "implements" int extendsCount = countPattern(content, "\\bextends\\b"); int implementsCount = countPattern(content, "\\bimplements\\b"); return extendsCount + implementsCount; } private void calculateHalsteadMetrics(String content, FileMetrics metrics) { HalsteadMetrics halstead = new HalsteadMetrics(); // Extract operators and operands (simplified) Set<String> operators = extractOperators(content); Set<String> operands = extractOperands(content); halstead.setUniqueOperators(operators.size()); halstead.setUniqueOperands(operands.size()); // Count total occurrences (simplified) int totalOperators = countTotalOccurrences(content, operators); int totalOperands = countTotalOccurrences(content, operands); halstead.setTotalOperators(totalOperators); halstead.setTotalOperands(totalOperands); // Calculate derived metrics halstead.calculateDerivedMetrics(); metrics.setHalsteadMetrics(halstead); } private Set<String> extractOperators(String content) { Set<String> operators = new HashSet<>(); // Common Java operators String[] javaOperators = { "=", "+", "-", "*", "/", "%", "++", "--", "==", "!=", ">", "<", ">=", "<=", "&&", "||", "!", "&", "|", "^", "~", "<<", ">>", ">>>", "+=", "-=", "*=", "/=", "%=", "&=", "|=", "^=", "<<=", ">>=", ">>>=", "instanceof" }; for (String op : javaOperators) { if (content.contains(op)) { operators.add(op); } } // Add control flow keywords as operators String[] controlFlow = {"if", "else", "for", "while", "do", "switch", "case", "break", "continue", "return"}; for (String cf : controlFlow) { if (content.contains(cf)) { operators.add(cf); } } return operators; } private Set<String> extractOperands(String content) { Set<String> operands = new HashSet<>(); // Extract identifiers (variable names, method names, etc.) Pattern identifierPattern = Pattern.compile("\\b[a-zA-Z_$][a-zA-Z0-9_$]*\\b"); Matcher matcher = identifierPattern.matcher(content); while (matcher.find()) { String identifier = matcher.group(); // Exclude keywords if (!isJavaKeyword(identifier)) { operands.add(identifier); } } return operands; } private boolean isJavaKeyword(String word) { String[] keywords = { "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while", "true", "false", "null" }; return Arrays.asList(keywords).contains(word); } private int countTotalOccurrences(String content, Set<String> items) { int count = 0; for (String item : items) { Pattern pattern = Pattern.compile("\\b" + Pattern.quote(item) + "\\b"); Matcher matcher = pattern.matcher(content); while (matcher.find()) { count++; } } return count; } private void calculateMaintainabilityIndex(FileMetrics metrics) { MetricsResult m = metrics.getMetrics(); HalsteadMetrics h = metrics.getHalsteadMetrics(); // Simplified Maintainability Index calculation double halsteadVolume = h.getVolume(); double cyclomaticComplexity = m.getCyclomaticComplexity(); double linesOfCode = m.getLinesOfCode(); // Avoid division by zero if (halsteadVolume == 0) halsteadVolume = 1; if (linesOfCode == 0) linesOfCode = 1; // MI = 171 - 5.2 * ln(Halstead Volume) - 0.23 * (Cyclomatic Complexity) - 16.2 * ln(LOC) double maintainabilityIndex = 171 - 5.2 * Math.log(halsteadVolume) - 0.23 * cyclomaticComplexity - 16.2 * Math.log(linesOfCode); // Clamp between 0 and 100 maintainabilityIndex = Math.max(0, Math.min(100, maintainabilityIndex)); metrics.setMaintainabilityIndex(maintainabilityIndex); } private void detectCodeSmells(String content, FileMetrics metrics) { List<CodeSmell> smells = new ArrayList<>(); // Detect long method if (metrics.getMetrics().getLinesOfCode() > 50) { smells.add(new CodeSmell("LONG_METHOD", "Method might be too long (" + metrics.getMetrics().getLinesOfCode() + " lines)")); } // Detect high complexity if (metrics.getMetrics().getCyclomaticComplexity() > 10) { smells.add(new CodeSmell("HIGH_COMPLEXITY", "High cyclomatic complexity (" + metrics.getMetrics().getCyclomaticComplexity() + ")")); } // Detect many parameters detectManyParameters(content, smells); // Detect duplicated code (simplified) detectDuplicatedCode(content, smells); metrics.setCodeSmells(smells); } private void detectManyParameters(String content, List<CodeSmell> smells) { Pattern methodPattern = Pattern.compile("\\w+\\s+\\w+\\s*\\(([^)]*)\\)"); Matcher matcher = methodPattern.matcher(content); while (matcher.find()) { String params = matcher.group(1); if (!params.trim().isEmpty()) { int paramCount = params.split(",").length; if (paramCount > 5) { smells.add(new CodeSmell("MANY_PARAMETERS", "Method has too many parameters (" + paramCount + ")")); } } } } private void detectDuplicatedCode(String content, List<CodeSmell> smells) { // Simplified duplication detection - look for repeated code blocks String[] lines = content.split("\n"); Map<String, Integer> lineCounts = new HashMap<>(); for (String line : lines) { String trimmed = line.trim(); if (trimmed.length() > 20 && !trimmed.startsWith("//")) { lineCounts.put(trimmed, lineCounts.getOrDefault(trimmed, 0) + 1); } } for (Map.Entry<String, Integer> entry : lineCounts.entrySet()) { if (entry.getValue() > 3) { smells.add(new CodeSmell("DUPLICATED_CODE", "Possible code duplication (line repeated " + entry.getValue() + " times)")); break; // Report only one instance to avoid spam } } } // Getters public List<File> getJavaFiles() { return javaFiles; } public List<FileMetrics> getFileMetrics() { return fileMetrics; } }

Data Model Classes

package com.codemetrics; import java.util.*; public class MetricsResult { private int linesOfCode; private int blankLines; private int commentLines; private int codeLines; private int methodCount; private int classCount; private int interfaceCount; private int cyclomaticComplexity; private double averageComplexity; private int inheritanceDepth; private int dependencyCount; public MetricsResult() {} public void aggregate(MetricsResult other) { this.linesOfCode += other.linesOfCode; this.blankLines += other.blankLines; this.commentLines += other.commentLines; this.codeLines += other.codeLines; this.methodCount += other.methodCount; this.classCount += other.classCount; this.interfaceCount += other.interfaceCount; this.cyclomaticComplexity += other.cyclomaticComplexity; this.inheritanceDepth += other.inheritanceDepth; this.dependencyCount += other.dependencyCount; // Recalculate average if (this.methodCount > 0) { this.averageComplexity = (double) this.cyclomaticComplexity / this.methodCount; } } // Getters and Setters public int getLinesOfCode() { return linesOfCode; } public void setLinesOfCode(int linesOfCode) { this.linesOfCode = linesOfCode; } public int getBlankLines() { return blankLines; } public void setBlankLines(int blankLines) { this.blankLines = blankLines; } public int getCommentLines() { return commentLines; } public void setCommentLines(int commentLines) { this.commentLines = commentLines; } public int getCodeLines() { return codeLines; } public void setCodeLines(int codeLines) { this.codeLines = codeLines; } public int getMethodCount() { return methodCount; } public void setMethodCount(int methodCount) { this.methodCount = methodCount; } public int getClassCount() { return classCount; } public void setClassCount(int classCount) { this.classCount = classCount; } public int getInterfaceCount() { return interfaceCount; } public void setInterfaceCount(int interfaceCount) { this.interfaceCount = interfaceCount; } public int getCyclomaticComplexity() { return cyclomaticComplexity; } public void setCyclomaticComplexity(int cyclomaticComplexity) { this.cyclomaticComplexity = cyclomaticComplexity; } public double getAverageComplexity() { return averageComplexity; } public void setAverageComplexity(double averageComplexity) { this.averageComplexity = averageComplexity; } public int getInheritanceDepth() { return inheritanceDepth; } public void setInheritanceDepth(int inheritanceDepth) { this.inheritanceDepth = inheritanceDepth; } public int getDependencyCount() { return dependencyCount; } public void setDependencyCount(int dependencyCount) { this.dependencyCount = dependencyCount; } } class FileMetrics { private final String fileName; private final String filePath; private final MetricsResult metrics; private HalsteadMetrics halsteadMetrics; private double maintainabilityIndex; private List<CodeSmell> codeSmells; public FileMetrics(String fileName, String filePath) { this.fileName = fileName; this.filePath = filePath; this.metrics = new MetricsResult(); this.halsteadMetrics = new HalsteadMetrics(); this.codeSmells = new ArrayList<>(); } public void setLineMetrics(int totalLines, int blankLines, int commentLines, int codeLines) { metrics.setLinesOfCode(totalLines); metrics.setBlankLines(blankLines); metrics.setCommentLines(commentLines); metrics.setCodeLines(codeLines); } // Delegated methods for MetricsResult public void setMethodCount(int count) { metrics.setMethodCount(count); } public void setCyclomaticComplexity(int complexity) { metrics.setCyclomaticComplexity(complexity); } public void setAverageComplexity(double avg) { metrics.setAverageComplexity(avg); } public void setClassCount(int count) { metrics.setClassCount(count); } public void setInterfaceCount(int count) { metrics.setInterfaceCount(count); } public void setInheritanceDepth(int depth) { metrics.setInheritanceDepth(depth); } public void setDependencyCount(int count) { metrics.setDependencyCount(count); } // Getters public String getFileName() { return fileName; } public String getFilePath() { return filePath; } public MetricsResult getMetrics() { return metrics; } public HalsteadMetrics getHalsteadMetrics() { return halsteadMetrics; } public void setHalsteadMetrics(HalsteadMetrics halsteadMetrics) { this.halsteadMetrics = halsteadMetrics; } public double getMaintainabilityIndex() { return maintainabilityIndex; } public void setMaintainabilityIndex(double maintainabilityIndex) { this.maintainabilityIndex = maintainabilityIndex; } public List<CodeSmell> getCodeSmells() { return codeSmells; } public void setCodeSmells(List<CodeSmell> codeSmells) { this.codeSmells = codeSmells; } } class HalsteadMetrics { private int uniqueOperators; private int uniqueOperands; private int totalOperators; private int totalOperands; private double vocabulary; private double length; private double volume; private double difficulty; private double effort; public void calculateDerivedMetrics() { this.vocabulary = uniqueOperators + uniqueOperands; this.length = totalOperators + totalOperands; if (vocabulary > 0) { this.volume = length * (Math.log(vocabulary) / Math.log(2)); } if (uniqueOperators > 0 && uniqueOperands > 0) { this.difficulty = ((double) uniqueOperators / 2) * ((double) totalOperands / uniqueOperands); this.effort = difficulty * volume; } } // Getters and Setters public int getUniqueOperators() { return uniqueOperators; } public void setUniqueOperators(int uniqueOperators) { this.uniqueOperators = uniqueOperators; } public int getUniqueOperands() { return uniqueOperands; } public void setUniqueOperands(int uniqueOperands) { this.uniqueOperands = uniqueOperands; } public int getTotalOperators() { return totalOperators; } public void setTotalOperators(int totalOperators) { this.totalOperators = totalOperators; } public int getTotalOperands() { return totalOperands; } public void setTotalOperands(int totalOperands) { this.totalOperands = totalOperands; } public double getVocabulary() { return vocabulary; } public double getLength() { return length; } public double getVolume() { return volume; } public double getDifficulty() { return difficulty; } public double getEffort() { return effort; } } class CodeSmell { private final String type; private final String description; private final Date detectedAt; public CodeSmell(String type, String description) { this.type = type; this.description = description; this.detectedAt = new Date(); } // Getters public String getType() { return type; } public String getDescription() { return description; } public Date getDetectedAt() { return detectedAt; } @Override public String toString() { return type + ": " + description; } } class AnalysisResult { private final List<FileMetrics> fileMetrics; private final MetricsResult totalMetrics; public AnalysisResult(List<FileMetrics> fileMetrics, MetricsResult totalMetrics) { this.fileMetrics = fileMetrics; this.totalMetrics = totalMetrics; } // Getters public List<FileMetrics> getFileMetrics() { return fileMetrics; } public MetricsResult getTotalMetrics() { return totalMetrics; } }

Report Generators

package com.codemetrics; import java.util.*; import java.util.stream.Collectors; public class MetricsReporter { public static void generateConsoleReport(AnalysisResult result) { System.out.println("=== CODE METRICS ANALYSIS REPORT ==="); System.out.println(); generateSummaryReport(result); System.out.println(); generateDetailedReport(result); System.out.println(); generateCodeSmellsReport(result); System.out.println(); generateRecommendations(result); } private static void generateSummaryReport(AnalysisResult result) { MetricsResult total = result.getTotalMetrics(); System.out.println("SUMMARY METRICS"); System.out.println("==============="); System.out.printf("Files Analyzed: %d%n", result.getFileMetrics().size()); System.out.printf("Total Lines: %d%n", total.getLinesOfCode()); System.out.printf("Code Lines: %d (%.1f%%)%n", total.getCodeLines(), percentage(total.getCodeLines(), total.getLinesOfCode())); System.out.printf("Comment Lines: %d (%.1f%%)%n", total.getCommentLines(), percentage(total.getCommentLines(), total.getLinesOfCode())); System.out.printf("Blank Lines: %d (%.1f%%)%n", total.getBlankLines(), percentage(total.getBlankLines(), total.getLinesOfCode())); System.out.printf("Comment Density: %.1f%%%n", percentage(total.getCommentLines(), total.getCommentLines() + total.getCodeLines())); System.out.println(); System.out.printf("Classes: %d%n", total.getClassCount()); System.out.printf("Interfaces: %d%n", total.getInterfaceCount()); System.out.printf("Methods: %d%n", total.getMethodCount()); System.out.printf("Total Cyclomatic Complexity: %d%n", total.getCyclomaticComplexity()); System.out.printf("Average Complexity/Method: %.2f%n", total.getAverageComplexity()); System.out.printf("Average Inheritance Depth: %.2f%n", (double) total.getInheritanceDepth() / Math.max(total.getClassCount(), 1)); } private static void generateDetailedReport(AnalysisResult result) { System.out.println("DETAILED FILE METRICS"); System.out.println("====================="); System.out.printf("%-30s %6s %6s %6s %6s %6s %6s %10s%n", "File", "Lines", "Code", "Comments", "Blanks", "Methods", "Complex", "Maintain"); System.out.printf("%-30s %6s %6s %6s %6s %6s %6s %10s%n", "----", "-----", "----", "-------", "------", "-------", "-------", "--------"); for (FileMetrics fileMetric : result.getFileMetrics()) { MetricsResult metrics = fileMetric.getMetrics(); System.out.printf("%-30s %6d %6d %6d %6d %6d %6d %9.1f%n", truncateFileName(fileMetric.getFileName(), 30), metrics.getLinesOfCode(), metrics.getCodeLines(), metrics.getCommentLines(), metrics.getBlankLines(), metrics.getMethodCount(), metrics.getCyclomaticComplexity(), fileMetric.getMaintainabilityIndex()); } } private static void generateCodeSmellsReport(AnalysisResult result) { List<CodeSmell> allSmells = result.getFileMetrics().stream() .flatMap(fm -> fm.getCodeSmells().stream()) .collect(Collectors.toList()); if (allSmells.isEmpty()) { System.out.println("CODE SMELLS ANALYSIS"); System.out.println("===================="); System.out.println("No significant code smells detected."); return; } System.out.println("CODE SMELLS DETECTED"); System.out.println("===================="); Map<String, Long> smellCounts = allSmells.stream() .collect(Collectors.groupingBy(CodeSmell::getType, Collectors.counting())); System.out.println("Summary by Type:"); smellCounts.forEach((type, count) -> System.out.printf(" %s: %d occurrences%n", type, count)); System.out.println(); System.out.println("Detailed Findings:"); for (FileMetrics fileMetric : result.getFileMetrics()) { if (!fileMetric.getCodeSmells().isEmpty()) { System.out.println(" " + fileMetric.getFileName() + ":"); for (CodeSmell smell : fileMetric.getCodeSmells()) { System.out.println(" - " + smell.getDescription()); } } } } private static void generateRecommendations(AnalysisResult result) { MetricsResult total = result.getTotalMetrics(); List<String> recommendations = new ArrayList<>(); // Complexity recommendations if (total.getAverageComplexity() > 10) { recommendations.add("High average complexity detected. Consider refactoring complex methods."); } // Comment density recommendations double commentDensity = percentage(total.getCommentLines(), total.getCommentLines() + total.getCodeLines()); if (commentDensity < 10) { recommendations.add("Low comment density. Consider adding more documentation."); } else if (commentDensity > 40) { recommendations.add("High comment density. Some comments might be redundant."); } // Method count recommendations double avgMethodsPerClass = (double) total.getMethodCount() / Math.max(total.getClassCount(), 1); if (avgMethodsPerClass > 20) { recommendations.add("Large classes detected. Consider applying Single Responsibility Principle."); } // Maintainability recommendations double avgMaintainability = result.getFileMetrics().stream() .mapToDouble(FileMetrics::getMaintainabilityIndex) .average() .orElse(0); if (avgMaintainability < 65) { recommendations.add("Low maintainability index. Code may be difficult to maintain."); } if (!recommendations.isEmpty()) { System.out.println("RECOMMENDATIONS"); System.out.println("==============="); recommendations.forEach(rec -> System.out.println("• " + rec)); } } private static String truncateFileName(String fileName, int maxLength) { if (fileName.length() <= maxLength) { return fileName; } return "..." + fileName.substring(fileName.length() - maxLength + 3); } private static double percentage(int part, int total) { if (total == 0) return 0; return (double) part / total * 100; } } // HTML Report Generator class HTMLReportGenerator { public static String generateHTMLReport(AnalysisResult result) { StringBuilder html = new StringBuilder(); html.append(""" <!DOCTYPE html> <html> <head> <title>Code Metrics Analysis Report</title> <style> body { font-family: Arial, sans-serif; margin: 20px; } .header { background: #f0f0f0; padding: 20px; border-radius: 5px; } .section { margin: 20px 0; } table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } .good { color: green; } .warning { color: orange; } .bad { color: red; } .metric-card { display: inline-block; width: 200px; margin: 10px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; text-align: center; } </style> </head> <body> <div class="header"> <h1>Code Metrics Analysis Report</h1> <p>Generated on: """ + new Date() + """</p> </div> """); generateHTMLSummary(html, result); generateHTMLDetailedTable(html, result); generateHTMLCodeSmells(html, result); html.append("</body></html>"); return html.toString(); } private static void generateHTMLSummary(StringBuilder html, AnalysisResult result) { MetricsResult total = result.getTotalMetrics(); html.append(""" <div class="section"> <h2>Summary Metrics</h2> <div class="metric-card"> <h3>Files Analyzed</h3> <p style="font-size: 24px; font-weight: bold;">""" + result.getFileMetrics().size() + """</p> </div> <div class="metric-card"> <h3>Total Lines</h3> <p style="font-size: 24px; font-weight: bold;">""" + total.getLinesOfCode() + """</p> </div> <div class="metric-card"> <h3>Code Lines</h3> <p style="font-size: 24px; font-weight: bold;">""" + total.getCodeLines() + """</p> </div> <div class="metric-card"> <h3>Methods</h3> <p style="font-size: 24px; font-weight: bold;">""" + total.getMethodCount() + """</p> </div> </div> """); } private static void generateHTMLDetailedTable(StringBuilder html, AnalysisResult result) { html.append(""" <div class="section"> <h2>Detailed File Metrics</h2> <table> <thead> <tr> <th>File</th> <th>Lines</th> <th>Code</th> <th>Comments</th> <th>Blanks</th> <th>Methods</th> <th>Complexity</th> <th>Maintainability</th> </tr> </thead> <tbody> """); for (FileMetrics fileMetric : result.getFileMetrics()) { MetricsResult metrics = fileMetric.getMetrics(); String maintainabilityClass = getMaintainabilityClass(fileMetric.getMaintainabilityIndex()); html.append(String.format(""" <tr> <td>%s</td> <td>%d</td> <td>%d</td> <td>%d</td> <td>%d</td> <td>%d</td> <td>%d</td> <td class="%s">%.1f</td> </tr> """, fileMetric.getFileName(), metrics.getLinesOfCode(), metrics.getCodeLines(), metrics.getCommentLines(), metrics.getBlankLines(), metrics.getMethodCount(), metrics.getCyclomaticComplexity(), maintainabilityClass, fileMetric.getMaintainabilityIndex() )); } html.append("</tbody></table></div>"); } private static void generateHTMLCodeSmells(StringBuilder html, AnalysisResult result) { List<CodeSmell> allSmells = result.getFileMetrics().stream() .flatMap(fm -> fm.getCodeSmells().stream()) .collect(Collectors.toList()); if (!allSmells.isEmpty()) { html.append(""" <div class="section"> <h2>Code Smells Detected</h2> <ul> """); for (CodeSmell smell : allSmells) { html.append("<li><strong>").append(smell.getType()) .append(":</strong> ").append(smell.getDescription()).append("</li>"); } html.append("</ul></div>"); } } private static String getMaintainabilityClass(double index) { if (index >= 85) return "good"; if (index >= 65) return "warning"; return "bad"; } }

Main Application Class

package com.codemetrics; import java.io.*; import java.nio.file.*; public class CodeMetricsApplication { public static void main(String[] args) { if (args.length == 0) { System.out.println("Usage: java CodeMetricsApplication <directory-path> [--html]"); System.out.println("Example: java CodeMetricsCalculator /path/to/java/source --html"); return; } String directoryPath = args[0]; boolean generateHTML = args.length > 1 && "--html".equals(args[1]); try { CodeMetricsCalculator calculator = new CodeMetricsCalculator(directoryPath); AnalysisResult result = calculator.analyze(); // Generate console report MetricsReporter.generateConsoleReport(result); // Generate HTML report if requested if (generateHTML) { String htmlReport = HTMLReportGenerator.generateHTMLReport(result); Path htmlPath = Paths.get("code-metrics-report.html"); Files.write(htmlPath, htmlReport.getBytes()); System.out.println("\nHTML report generated: " + htmlPath.toAbsolutePath()); } } catch (IOException e) { System.err.println("Error analyzing code: " + e.getMessage()); e.printStackTrace(); } catch (Exception e) { System.err.println("Unexpected error: " + e.getMessage()); e.printStackTrace(); } } }

Usage Examples

Basic Usage

// Analyze a directory CodeMetricsCalculator calculator = new CodeMetricsCalculator("/path/to/java/project"); AnalysisResult result = calculator.analyze(); // Generate console report MetricsReporter.generateConsoleReport(result); // Generate HTML report String html = HTMLReportGenerator.generateHTMLReport(result); Files.write(Paths.get("report.html"), html.getBytes());

Programmatic Usage

// Analyze specific files List<File> javaFiles = Arrays.asList( new File("src/main/java/com/example/MyClass.java"), new File("src/main/java/com/example/AnotherClass.java") ); CodeMetricsCalculator calculator = new CodeMetricsCalculator(javaFiles); AnalysisResult result = calculator.analyze(); // Access metrics programmatically for (FileMetrics fileMetric : result.getFileMetrics()) { System.out.println("File: " + fileMetric.getFileName()); System.out.println("Maintainability: " + fileMetric.getMaintainabilityIndex()); System.out.println("Complexity: " + fileMetric.getMetrics().getCyclomaticComplexity()); }

Features Summary

  1. Comprehensive Metrics: LOC, complexity, Halstead metrics, maintainability index
  2. Code Smell Detection: Identifies common anti-patterns
  3. Multiple Output Formats: Console and HTML reports
  4. Flexible Analysis: Can analyze directories or specific files
  5. Extensible Architecture: Easy to add new metrics and analyzers

This Code Metrics Calculator provides valuable insights into code quality and helps identify areas for improvement in Java projects.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper