CodeClimate is a platform for automated code review and quality analysis. While CodeClimate itself is a SaaS platform, we can implement similar functionality in Java to analyze code quality, maintainability, and security issues. Here's a comprehensive guide to building a CodeClimate-like analysis system in Java.
Project Setup
Dependencies
pom.xml
<properties>
<pmd.version>6.55.0</pmd.version>
<checkstyle.version>10.12.0</checkstyle.version>
<spotbugs.version>4.7.3</spotbugs.version>
<jacoco.version>0.8.10</jacoco.version>
<cyclonedx.version>2.7.10</cyclonedx.version>
</properties>
<dependencies>
<!-- Static Analysis -->
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-core</artifactId>
<version>${pmd.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.pmd</groupId>
<artifactId>pmd-java</artifactId>
<version>${pmd.version}</version>
</dependency>
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>${checkstyle.version}</version>
</dependency>
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs</artifactId>
<version>${spotbugs.version}</version>
</dependency>
<!-- Code Metrics -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
<dependency>
<groupId>io.github.java-diff-utils</groupId>
<artifactId>java-diff-utils</artifactId>
<version>4.12</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.15.2</version>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.24.2</version>
<scope>test</scope>
</dependency>
</dependencies>
Core Implementation
1. Configuration Models
CodeClimateConfig.java - Main configuration class
package com.yourcompany.codeclimate;
import java.nio.file.Path;
import java.util.*;
public class CodeClimateConfig {
private final Path projectRoot;
private final Set<String> includedPaths;
private final Set<String> excludedPaths;
private final Map<String, Object> engineConfigs;
private final boolean enableGitAwareAnalysis;
private final int maxIssuesPerCategory;
private final QualityGate qualityGate;
private CodeClimateConfig(Builder builder) {
this.projectRoot = builder.projectRoot;
this.includedPaths = Set.copyOf(builder.includedPaths);
this.excludedPaths = Set.copyOf(builder.excludedPaths);
this.engineConfigs = Map.copyOf(builder.engineConfigs);
this.enableGitAwareAnalysis = builder.enableGitAwareAnalysis;
this.maxIssuesPerCategory = builder.maxIssuesPerCategory;
this.qualityGate = builder.qualityGate;
}
public static class Builder {
private Path projectRoot;
private Set<String> includedPaths = new HashSet<>();
private Set<String> excludedPaths = new HashSet<>();
private Map<String, Object> engineConfigs = new HashMap<>();
private boolean enableGitAwareAnalysis = true;
private int maxIssuesPerCategory = 100;
private QualityGate qualityGate = new QualityGate.Builder().build();
public Builder projectRoot(Path projectRoot) {
this.projectRoot = projectRoot;
return this;
}
public Builder includePath(String path) {
this.includedPaths.add(path);
return this;
}
public Builder includePaths(Collection<String> paths) {
this.includedPaths.addAll(paths);
return this;
}
public Builder excludePath(String path) {
this.excludedPaths.add(path);
return this;
}
public Builder excludePaths(Collection<String> paths) {
this.excludedPaths.addAll(paths);
return this;
}
public Builder engineConfig(String engine, Object config) {
this.engineConfigs.put(engine, config);
return this;
}
public Builder enableGitAwareAnalysis(boolean enable) {
this.enableGitAwareAnalysis = enable;
return this;
}
public Builder maxIssuesPerCategory(int maxIssues) {
this.maxIssuesPerCategory = maxIssues;
return this;
}
public Builder qualityGate(QualityGate qualityGate) {
this.qualityGate = qualityGate;
return this;
}
public CodeClimateConfig build() {
if (projectRoot == null) {
throw new IllegalStateException("Project root is required");
}
// Set default includes if none provided
if (includedPaths.isEmpty()) {
includedPaths.add("src/main/java");
includedPaths.add("src/test/java");
}
// Set default excludes
if (excludedPaths.isEmpty()) {
excludedPaths.add("target/**");
excludedPaths.add("build/**");
excludedPaths.add("**/test/**");
}
return new CodeClimateConfig(this);
}
}
// Getters
public Path getProjectRoot() { return projectRoot; }
public Set<String> getIncludedPaths() { return includedPaths; }
public Set<String> getExcludedPaths() { return excludedPaths; }
public Map<String, Object> getEngineConfigs() { return engineConfigs; }
public boolean isEnableGitAwareAnalysis() { return enableGitAwareAnalysis; }
public int getMaxIssuesPerCategory() { return maxIssuesPerCategory; }
public QualityGate getQualityGate() { return qualityGate; }
}
**QualityGate.java** - Quality gate configuration
java
public class QualityGate {
private final double maintainabilityThreshold;
private final double testCoverageThreshold;
private final int maxComplexity;
private final int maxDuplication;
private final int maxIssues;
private final Map categoryLimits;
private QualityGate(Builder builder) {
this.maintainabilityThreshold = builder.maintainabilityThreshold;
this.testCoverageThreshold = builder.testCoverageThreshold;
this.maxComplexity = builder.maxComplexity;
this.maxDuplication = builder.maxDuplication;
this.maxIssues = builder.maxIssues;
this.categoryLimits = Map.copyOf(builder.categoryLimits);
}
public static class Builder {
private double maintainabilityThreshold = 4.0; // A-F scale (A=4.0)
private double testCoverageThreshold = 80.0; // Percentage
private int maxComplexity = 10; // Cyclomatic complexity
private int maxDuplication = 5; // Percentage
private int maxIssues = 50;
private Map<String, Integer> categoryLimits = new HashMap<>();
public Builder maintainabilityThreshold(double threshold) {
this.maintainabilityThreshold = threshold;
return this;
}
public Builder testCoverageThreshold(double threshold) {
this.testCoverageThreshold = threshold;
return this;
}
public Builder maxComplexity(int maxComplexity) {
this.maxComplexity = maxComplexity;
return this;
}
public Builder maxDuplication(int maxDuplication) {
this.maxDuplication = maxDuplication;
return this;
}
public Builder maxIssues(int maxIssues) {
this.maxIssues = maxIssues;
return this;
}
public Builder categoryLimit(String category, int limit) {
this.categoryLimits.put(category, limit);
return this;
}
public QualityGate build() {
// Set default category limits
if (categoryLimits.isEmpty()) {
categoryLimits.put("bug", 10);
categoryLimits.put("security", 5);
categoryLimits.put("style", 20);
categoryLimits.put("complexity", 15);
}
return new QualityGate(this);
}
}
// Getters
public double getMaintainabilityThreshold() { return maintainabilityThreshold; }
public double getTestCoverageThreshold() { return testCoverageThreshold; }
public int getMaxComplexity() { return maxComplexity; }
public int getMaxDuplication() { return maxDuplication; }
public int getMaxIssues() { return maxIssues; }
public Map<String, Integer> getCategoryLimits() { return categoryLimits; }
}
### 2. Issue Models **CodeIssue.java** - Represents a code quality issue
java
package com.yourcompany.codeclimate.model;
import java.nio.file.Path;
import java.time.Instant;
import java.util.*;
public class CodeIssue {
private final String checkName;
private final String description;
private final Category category;
private final Severity severity;
private final Location location;
private final String remediationPoints;
private final List tags;
private final Map metadata;
private final String fingerprint;
public enum Category {
BUG_RISK("Bug Risk"),
CLARITY("Clarity"),
COMPATIBILITY("Compatibility"),
COMPLEXITY("Complexity"),
DUPLICATION("Duplication"),
PERFORMANCE("Performance"),
SECURITY("Security"),
STYLE("Style");
private final String displayName;
Category(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
public enum Severity {
INFO("info"),
MINOR("minor"),
MAJOR("major"),
CRITICAL("critical"),
BLOCKER("blocker");
private final String level;
Severity(String level) {
this.level = level;
}
public String getLevel() {
return level;
}
}
public static class Location {
private final Path path;
private final Position begin;
private final Position end;
public Location(Path path, Position begin, Position end) {
this.path = path;
this.begin = begin;
this.end = end;
}
public static class Position {
private final int line;
private final int column;
public Position(int line, int column) {
this.line = line;
this.column = column;
}
// Getters
public int getLine() { return line; }
public int getColumn() { return column; }
}
// Getters
public Path getPath() { return path; }
public Position getBegin() { return begin; }
public Position getEnd() { return end; }
}
private CodeIssue(Builder builder) {
this.checkName = builder.checkName;
this.description = builder.description;
this.category = builder.category;
this.severity = builder.severity;
this.location = builder.location;
this.remediationPoints = builder.remediationPoints;
this.tags = List.copyOf(builder.tags);
this.metadata = Map.copyOf(builder.metadata);
this.fingerprint = builder.fingerprint;
}
public static class Builder {
private String checkName;
private String description;
private Category category;
private Severity severity = Severity.MINOR;
private Location location;
private String remediationPoints = "50000";
private List<String> tags = new ArrayList<>();
private Map<String, Object> metadata = new HashMap<>();
private String fingerprint;
public Builder checkName(String checkName) {
this.checkName = checkName;
return this;
}
public Builder description(String description) {
this.description = description;
return this;
}
public Builder category(Category category) {
this.category = category;
return this;
}
public Builder severity(Severity severity) {
this.severity = severity;
return this;
}
public Builder location(Location location) {
this.location = location;
return this;
}
public Builder remediationPoints(String points) {
this.remediationPoints = points;
return this;
}
public Builder tag(String tag) {
this.tags.add(tag);
return this;
}
public Builder tags(List<String> tags) {
this.tags.addAll(tags);
return this;
}
public Builder metadata(String key, Object value) {
this.metadata.put(key, value);
return this;
}
public Builder fingerprint(String fingerprint) {
this.fingerprint = fingerprint;
return this;
}
public CodeIssue build() {
if (checkName == null) throw new IllegalStateException("Check name is required");
if (description == null) throw new IllegalStateException("Description is required");
if (category == null) throw new IllegalStateException("Category is required");
if (location == null) throw new IllegalStateException("Location is required");
// Generate fingerprint if not provided
if (fingerprint == null) {
fingerprint = generateFingerprint();
}
return new CodeIssue(this);
}
private String generateFingerprint() {
return String.format("%s:%s:%d:%d",
checkName,
location.getPath().toString(),
location.getBegin().getLine(),
location.getBegin().getColumn());
}
}
// Getters
public String getCheckName() { return checkName; }
public String getDescription() { return description; }
public Category getCategory() { return category; }
public Severity getSeverity() { return severity; }
public Location getLocation() { return location; }
public String getRemediationPoints() { return remediationPoints; }
public List<String> getTags() { return tags; }
public Map<String, Object> getMetadata() { return metadata; }
public String getFingerprint() { return fingerprint; }
}
### 3. Analysis Engine Interface **AnalysisEngine.java** - Base interface for analysis engines
java
package com.yourcompany.codeclimate.engine;
import com.yourcompany.codeclimate.CodeClimateConfig;
import com.yourcompany.codeclimate.model.CodeIssue;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public interface AnalysisEngine {
String getName();
String getVersion();
List getSupportedFileTypes();
CompletableFuture> analyze(CodeClimateConfig config);
boolean isEnabled(CodeClimateConfig config);
void initialize(CodeClimateConfig config);
void cleanup();
}
### 4. PMD Analysis Engine **PmdAnalysisEngine.java** - PMD-based analysis engine
java
package com.yourcompany.codeclimate.engine.pmd;
import com.yourcompany.codeclimate.CodeClimateConfig;
import com.yourcompany.codeclimate.engine.AnalysisEngine;
import com.yourcompany.codeclimate.model.CodeIssue;
import net.sourceforge.pmd.*;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.LanguageRegistry;
import net.sourceforge.pmd.lang.java.JavaLanguageModule;
import net.sourceforge.pmd.lang.java.rule.JavaRuleViolation;
import net.sourceforge.pmd.renderers.Renderer;
import net.sourceforge.pmd.renderers.RendererFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class PmdAnalysisEngine implements AnalysisEngine {
private static final Logger logger = LoggerFactory.getLogger(PmdAnalysisEngine.class);
private static final String ENGINE_NAME = "pmd";
private static final String ENGINE_VERSION = "6.55.0";
private PMDConfiguration pmdConfig;
private RuleSet ruleset;
@Override
public String getName() {
return ENGINE_NAME;
}
@Override
public String getVersion() {
return ENGINE_VERSION;
}
@Override
public List<String> getSupportedFileTypes() {
return List.of(".java");
}
@Override
public CompletableFuture<List<CodeIssue>> analyze(CodeClimateConfig config) {
return CompletableFuture.supplyAsync(() -> {
List<CodeIssue> issues = new ArrayList<>();
try {
// Configure PMD
configurePmd(config);
// Create rule context
RuleContext ruleContext = new RuleContext();
ruleContext.setReport(new Report());
ruleContext.setSourceCodeFilename(config.getProjectRoot().toString());
// Process files
List<Path> javaFiles = findJavaFiles(config);
for (Path javaFile : javaFiles) {
processFile(javaFile, ruleContext, issues);
}
logger.info("PMD analysis completed. Found {} issues.", issues.size());
} catch (Exception e) {
logger.error("PMD analysis failed", e);
throw new RuntimeException("PMD analysis failed", e);
}
return issues;
});
}
@Override
public boolean isEnabled(CodeClimateConfig config) {
return true; // Always enabled for Java projects
}
@Override
public void initialize(CodeClimateConfig config) {
this.pmdConfig = new PMDConfiguration();
this.pmdConfig.setDefaultLanguageVersion(LanguageRegistry.getLanguage(JavaLanguageModule.NAME).getDefaultVersion());
this.pmdConfig.setInputPaths(config.getProjectRoot().toString());
this.pmdConfig.setRuleSets("rulesets/java/quickstart.xml,rulesets/java/bestpractices.xml");
// Load custom ruleset if specified
Object rulesetConfig = config.getEngineConfigs().get("pmd.ruleset");
if (rulesetConfig instanceof String) {
pmdConfig.setRuleSets((String) rulesetConfig);
}
try {
this.ruleset = RuleSetLoader.fromPmdConfig(pmdConfig).loadFromResources(pmdConfig.getRuleSets());
} catch (Exception e) {
logger.warn("Failed to load PMD ruleset, using default", e);
// Fall back to default ruleset
}
}
@Override
public void cleanup() {
this.pmdConfig = null;
this.ruleset = null;
}
private void configurePmd(CodeClimateConfig config) {
// Configure additional PMD settings from config
Object maxViolations = config.getEngineConfigs().get("pmd.maxViolations");
if (maxViolations instanceof Integer) {
pmdConfig.setMaximumPriority((Integer) maxViolations);
}
Object failOnViolation = config.getEngineConfigs().get("pmd.failOnViolation");
if (failOnViolation instanceof Boolean) {
// PMD doesn't have direct failOnViolation, but we can handle it in our logic
}
}
private List<Path> findJavaFiles(CodeClimateConfig config) {
List<Path> javaFiles = new ArrayList<>();
try {
java.nio.file.Files.walk(config.getProjectRoot())
.filter(path -> path.toString().endsWith(".java"))
.filter(this::isIncludedPath)
.forEach(javaFiles::add);
} catch (Exception e) {
logger.error("Failed to find Java files", e);
}
return javaFiles;
}
private boolean isIncludedPath(Path path) {
// Implement path inclusion/exclusion logic
return true;
}
private void processFile(Path javaFile, RuleContext ruleContext, List<CodeIssue> issues) {
try {
ruleContext.setSourceCodeFile(javaFile.toFile());
ruleContext.setReport(new Report()); // Clear previous violations
// Run PMD analysis
PmdProcessor processor = new PmdProcessor();
processor.processFile(javaFile.toFile(), ruleset, ruleContext);
// Convert PMD violations to CodeClimate issues
for (RuleViolation violation : ruleContext.getReport()) {
if (violation instanceof JavaRuleViolation) {
issues.add(convertViolationToIssue((JavaRuleViolation) violation));
}
}
} catch (Exception e) {
logger.warn("Failed to process file: {}", javaFile, e);
}
}
private CodeIssue convertViolationToIssue(JavaRuleViolation violation) {
return new CodeIssue.Builder()
.checkName(violation.getRule().getName())
.description(violation.getDescription())
.category(mapPmdCategory(violation.getRule()))
.severity(mapPmdPriority(violation.getRule().getPriority()))
.location(createLocation(violation))
.remediationPoints(calculateRemediationPoints(violation.getRule().getPriority()))
.tag("pmd")
.metadata("rule", violation.getRule().getName())
.metadata("priority", violation.getRule().getPriority())
.build();
}
private CodeIssue.Category mapPmdCategory(net.sourceforge.pmd.Rule rule) {
String ruleName = rule.getName().toLowerCase();
if (ruleName.contains("security") || ruleName.contains("insecure")) {
return CodeIssue.Category.SECURITY;
} else if (ruleName.contains("complexity") || ruleName.contains("cyclomatic")) {
return CodeIssue.Category.COMPLEXITY;
} else if (ruleName.contains("performance")) {
return CodeIssue.Category.PERFORMANCE;
} else if (ruleName.contains("duplicate") || ruleName.contains("copy")) {
return CodeIssue.Category.DUPLICATION;
} else if (ruleName.contains("style") || ruleName.contains("naming")) {
return CodeIssue.Category.STYLE;
} else {
return CodeIssue.Category.BUG_RISK;
}
}
private CodeIssue.Severity mapPmdPriority(int priority) {
return switch (priority) {
case 1, 2 -> CodeIssue.Severity.CRITICAL;
case 3 -> CodeIssue.Severity.MAJOR;
case 4 -> CodeIssue.Severity.MINOR;
case 5 -> CodeIssue.Severity.INFO;
default -> CodeIssue.Severity.MINOR;
};
}
private CodeIssue.Location createLocation(JavaRuleViolation violation) {
return new CodeIssue.Location(
violation.getFilename() != null ?
java.nio.file.Paths.get(violation.getFilename()) :
java.nio.file.Paths.get("unknown"),
new CodeIssue.Location.Position(violation.getBeginLine(), violation.getBeginColumn()),
new CodeIssue.Location.Position(violation.getEndLine(), violation.getEndColumn())
);
}
private String calculateRemediationPoints(int priority) {
return switch (priority) {
case 1 -> "100000"; // Critical - high effort
case 2 -> "50000"; // High - medium effort
case 3 -> "30000"; // Medium - low effort
case 4 -> "10000"; // Low - minimal effort
case 5 -> "5000"; // Info - trivial
default -> "50000";
};
}
}
### 5. Checkstyle Analysis Engine **CheckstyleAnalysisEngine.java** - Checkstyle-based analysis engine
java
package com.yourcompany.codeclimate.engine.checkstyle;
import com.yourcompany.codeclimate.CodeClimateConfig;
import com.yourcompany.codeclimate.engine.AnalysisEngine;
import com.yourcompany.codeclimate.model.CodeIssue;
import com.puppycrawl.tools.checkstyle.*;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
public class CheckstyleAnalysisEngine implements AnalysisEngine {
private static final Logger logger = LoggerFactory.getLogger(CheckstyleAnalysisEngine.class);
private static final String ENGINE_NAME = "checkstyle";
private static final String ENGINE_VERSION = "10.12.0";
private Configuration checkstyleConfig;
@Override
public String getName() {
return ENGINE_NAME;
}
@Override
public String getVersion() {
return ENGINE_VERSION;
}
@Override
public List<String> getSupportedFileTypes() {
return List.of(".java");
}
@Override
public CompletableFuture<List<CodeIssue>> analyze(CodeClimateConfig config) {
return CompletableFuture.supplyAsync(() -> {
List<CodeIssue> issues = new ArrayList<>();
try {
// Create checker
Checker checker = createChecker();
// Process files
List<File> javaFiles = findJavaFiles(config);
for (File javaFile : javaFiles) {
processFile(javaFile, checker, issues);
}
checker.destroy();
logger.info("Checkstyle analysis completed. Found {} issues.", issues.size());
} catch (Exception e) {
logger.error("Checkstyle analysis failed", e);
throw new RuntimeException("Checkstyle analysis failed", e);
}
return issues;
});
}
@Override
public boolean isEnabled(CodeClimateConfig config) {
return true;
}
@Override
public void initialize(CodeClimateConfig config) {
try {
// Load configuration
Object configPath = config.getEngineConfigs().get("checkstyle.config");
if (configPath instanceof String) {
this.checkstyleConfig = ConfigurationLoader.loadConfiguration(
(String) configPath, new PropertiesExpander(System.getProperties())
);
} else {
// Use default configuration
this.checkstyleConfig = createDefaultConfiguration();
}
} catch (CheckstyleException e) {
logger.warn("Failed to load Checkstyle configuration, using default", e);
this.checkstyleConfig = createDefaultConfiguration();
}
}
@Override
public void cleanup() {
this.checkstyleConfig = null;
}
private Checker createChecker() throws CheckstyleException {
Checker checker = new Checker();
checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
checker.configure(checkstyleConfig);
return checker;
}
private Configuration createDefaultConfiguration() throws CheckstyleException {
// Create a default configuration with common checks
return ConfigurationLoader.loadConfiguration(
"<?xml version=\"1.0\"?>\n" +
"<!DOCTYPE module PUBLIC \"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN\" \"https://checkstyle.org/dtds/configuration_1_3.dtd\">\n" +
"<module name=\"Checker\">\n" +
" <module name=\"TreeWalker\">\n" +
" <module name=\"IllegalImport\"/>\n" +
" <module name=\"RedundantImport\"/>\n" +
" <module name=\"UnusedImports\"/>\n" +
" <module name=\"NeedBraces\"/>\n" +
" <module name=\"HiddenField\"/>\n" +
" <module name=\"MagicNumber\"/>\n" +
" </module>\n" +
"</module>",
new PropertiesExpander(System.getProperties())
);
}
private List<File> findJavaFiles(CodeClimateConfig config) {
List<File> javaFiles = new ArrayList<>();
try {
java.nio.file.Files.walk(config.getProjectRoot())
.filter(path -> path.toString().endsWith(".java"))
.filter(this::isIncludedPath)
.map(Path::toFile)
.forEach(javaFiles::add);
} catch (Exception e) {
logger.error("Failed to find Java files", e);
}
return javaFiles;
}
private boolean isIncludedPath(Path path) {
// Implement path inclusion/exclusion logic
return true;
}
private void processFile(File javaFile, Checker checker, List<CodeIssue> issues) {
try {
List<AuditEvent> events = new ArrayList<>();
AuditListener listener = new AuditListener() {
@Override
public void auditStarted(AuditEvent event) {}
@Override
public void auditFinished(AuditEvent event) {}
@Override
public void fileStarted(AuditEvent event) {}
@Override
public void fileFinished(AuditEvent event) {}
@Override
public void addError(AuditEvent event) {
events.add(event);
}
@Override
public void addException(AuditEvent event, Throwable throwable) {
logger.warn("Checkstyle exception for file: {}", event.getFileName(), throwable);
}
};
checker.addListener(listener);
checker.fireFileStarted(javaFile.getPath());
int result = checker.process(List.of(javaFile));
checker.fireFileFinished(javaFile.getPath());
// Convert events to issues
for (AuditEvent event : events) {
issues.add(convertEventToIssue(event));
}
} catch (Exception e) {
logger.warn("Failed to process file: {}", javaFile, e);
}
}
private CodeIssue convertEventToIssue(AuditEvent event) {
return new CodeIssue.Builder()
.checkName(event.getSourceName())
.description(event.getMessage())
.category(mapCheckstyleCategory(event.getSourceName()))
.severity(mapCheckstyleSeverity(event.getSeverityLevel()))
.location(createLocation(event))
.remediationPoints("50000")
.tag("checkstyle")
.metadata("source", event.getSourceName())
.build();
}
private CodeIssue.Category mapCheckstyleCategory(String sourceName) {
if (sourceName.contains("Import")) {
return CodeIssue.Category.STYLE;
} else if (sourceName.contains("MagicNumber")) {
return CodeIssue.Category.CLARITY;
} else if (sourceName.contains("HiddenField")) {
return CodeIssue.Category.BUG_RISK;
} else {
return CodeIssue.Category.STYLE;
}
}
private CodeIssue.Severity mapCheckstyleSeverity(com.puppycrawl.tools.checkstyle.api.SeverityLevel level) {
switch (level.getName()) {
case "error": return CodeIssue.Severity.MAJOR;
case "warning": return CodeIssue.Severity.MINOR;
case "info": return CodeIssue.Severity.INFO;
default: return CodeIssue.Severity.MINOR;
}
}
private CodeIssue.Location createLocation(AuditEvent event) {
return new CodeIssue.Location(
java.nio.file.Paths.get(event.getFileName()),
new CodeIssue.Location.Position(event.getLine(), event.getColumn()),
new CodeIssue.Location.Position(event.getLine(), event.getColumn() + 1)
);
}
}
### 6. Code Metrics Engine **CodeMetricsEngine.java** - Calculates code metrics and maintainability
java
package com.yourcompany.codeclimate.engine.metrics;
import com.yourcompany.codeclimate.CodeClimateConfig;
import com.yourcompany.codeclimate.engine.AnalysisEngine;
import com.yourcompany.codeclimate.model.CodeIssue;
import org.apache.commons.lang3.tuple.Pair;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class CodeMetricsEngine implements AnalysisEngine {
private static final String ENGINE_NAME = "metrics";
private static final String ENGINE_VERSION = "1.0.0";
@Override
public String getName() {
return ENGINE_NAME;
}
@Override
public String getVersion() {
return ENGINE_VERSION;
}
@Override
public List<String> getSupportedFileTypes() {
return List.of(".java");
}
@Override
public CompletableFuture<List<CodeIssue>> analyze(CodeClimateConfig config) {
return CompletableFuture.supplyAsync(() -> {
List<CodeIssue> issues = new ArrayList<>();
try {
List<Path> javaFiles = findJavaFiles(config);
for (Path javaFile : javaFiles) {
analyzeFileMetrics(javaFile, issues);
}
// Calculate overall project metrics
calculateProjectMetrics(javaFiles, issues);
} catch (Exception e) {
logger.error("Metrics analysis failed", e);
}
return issues;
});
}
private void analyzeFileMetrics(Path javaFile, List<CodeIssue> issues) {
try {
String content = Files.readString(javaFile);
// Calculate various metrics
int lineCount = countLines(content);
int complexity = calculateCyclomaticComplexity(content);
int methodCount = countMethods(content);
int classCount = countClasses(content);
// Create issues for metric violations
if (lineCount > 1000) {
issues.add(createMetricIssue(javaFile, "File too long",
String.format("File has %d lines (max recommended: 1000)", lineCount),
CodeIssue.Category.COMPLEXITY, CodeIssue.Severity.MAJOR));
}
if (complexity > 50) {
issues.add(createMetricIssue(javaFile, "High complexity",
String.format("Cyclomatic complexity: %d (max recommended: 50)", complexity),
CodeIssue.Category.COMPLEXITY, CodeIssue.Severity.MAJOR));
}
if (methodCount > 20) {
issues.add(createMetricIssue(javaFile, "Too many methods",
String.format("Class has %d methods (max recommended: 20)", methodCount),
CodeIssue.Category.COMPLEXITY, CodeIssue.Severity.MINOR));
}
} catch (Exception e) {
logger.warn("Failed to analyze metrics for file: {}", javaFile, e);
}
}
private void calculateProjectMetrics(List<Path> javaFiles, List<CodeIssue> issues) {
int totalLines = 0;
int totalComplexity = 0;
int totalMethods = 0;
int totalClasses = 0;
for (Path file : javaFiles) {
try {
String content = Files.readString(file);
totalLines += countLines(content);
totalComplexity += calculateCyclomaticComplexity(content);
totalMethods += countMethods(content);
totalClasses += countClasses(content);
} catch (Exception e) {
// Skip files that can't be read
}
}
double averageComplexity = totalClasses > 0 ? (double) totalComplexity / totalClasses : 0;
double averageMethods = totalClasses > 0 ? (double) totalMethods / totalClasses : 0;
// Create project-level metric issues
if (averageComplexity > 10) {
issues.add(createProjectMetricIssue("High average complexity",
String.format("Average cyclomatic complexity: %.2f (max recommended: 10)", averageComplexity),
CodeIssue.Category.COMPLEXITY, CodeIssue.Severity.MAJOR));
}
if (averageMethods > 15) {
issues.add(createProjectMetricIssue("High average method count",
String.format("Average methods per class: %.2f (max recommended: 15)", averageMethods),
CodeIssue.Category.COMPLEXITY, CodeIssue.Severity.MINOR));
}
}
private int countLines(String content) {
return content.split("\r\n|\r|\n").length;
}
private int calculateCyclomaticComplexity(String content) {
// Simplified cyclomatic complexity calculation
// In practice, use a proper complexity calculator
int complexity = 1; // Base complexity
// Count decision points
complexity += countOccurrences(content, "if\\s*\\(");
complexity += countOccurrences(content, "while\\s*\\(");
complexity += countOccurrences(content, "for\\s*\\(");
complexity += countOccurrences(content, "case\\s+");
complexity += countOccurrences(content, "catch\\s*\\(");
complexity += countOccurrences(content, "&&");
complexity += countOccurrences(content, "\\|\\|");
return complexity;
}
private int countMethods(String content) {
return countOccurrences(content, "(public|private|protected)\\s+\\w+\\s+\\w+\\s*\\(");
}
private int countClasses(String content) {
return countOccurrences(content, "class\\s+\\w+");
}
private int countOccurrences(String content, String regex) {
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex);
java.util.regex.Matcher matcher = pattern.matcher(content);
int count = 0;
while (matcher.find()) {
count++;
}
return count;
}
private CodeIssue createMetricIssue(Path file, String checkName, String description,
CodeIssue.Category category, CodeIssue.Severity severity) {
return new CodeIssue.Builder()
.checkName(checkName)
.description(description)
.category(category)
.severity(severity)
.location(new CodeIssue.Location(file,
new CodeIssue.Location.Position(1, 1),
new CodeIssue.Location.Position(1, 1)))
.remediationPoints("50000")
.tag("metrics")
.build();
}
private CodeIssue createProjectMetricIssue(String checkName, String description,
CodeIssue.Category category, CodeIssue.Severity severity) {
return new CodeIssue.Builder()
.checkName(checkName)
.description(description)
.category(category)
.severity(severity)
.location(new CodeIssue.Location(
java.nio.file.Paths.get("PROJECT"),
new CodeIssue.Location.Position(0, 0),
new CodeIssue.Location.Position(0, 0)))
.remediationPoints("100000")
.tag("metrics")
.tag("project")
.build();
}
@Override
public boolean isEnabled(CodeClimateConfig config) {
return true;
}
@Override
public void initialize(CodeClimateConfig config) {
// No initialization needed for metrics
}
@Override
public void cleanup() {
// No cleanup needed for metrics
}
}
### 7. Main CodeClimate Analyzer **CodeClimateAnalyzer.java** - Main analysis orchestrator
java
package com.yourcompany.codeclimate;
import com.yourcompany.codeclimate.engine.AnalysisEngine;
import com.yourcompany.codeclimate.model.CodeIssue;
import com.yourcompany.codeclimate.model.AnalysisResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class CodeClimateAnalyzer {
private static final Logger logger = LoggerFactory.getLogger(CodeClimateAnalyzer.class);
private final CodeClimateConfig config;
private final List<AnalysisEngine> engines;
private final IssueDeduplicator deduplicator;
public CodeClimateAnalyzer(CodeClimateConfig config) {
this.config = config;
this.engines = new ArrayList<>();
this.deduplicator = new IssueDeduplicator();
initializeEngines();
}
public CompletableFuture<AnalysisResult> analyze() {
logger.info("Starting code analysis for project: {}", config.getProjectRoot());
List<CompletableFuture<List<CodeIssue>>> engineFutures = engines.stream()
.filter(engine -> engine.isEnabled(config))
.map(engine -> engine.analyze(config))
.collect(Collectors.toList());
return CompletableFuture.allOf(engineFutures.toArray(new CompletableFuture[0]))
.thenApply(v -> {
// Collect all issues from all engines
List<CodeIssue> allIssues = engineFutures.stream()
.flatMap(future -> future.join().stream())
.collect(Collectors.toList());
// Deduplicate issues
List<CodeIssue> uniqueIssues = deduplicator.deduplicate(allIssues);
// Apply limits
List<CodeIssue> finalIssues = applyIssueLimits(uniqueIssues);
// Calculate metrics
AnalysisResult result = calculateAnalysisResult(finalIssues);
logger.info("Analysis completed. Found {} unique issues.", finalIssues.size());
return result;
});
}
public AnalysisResult analyzeSync() {
try {
return analyze().get();
} catch (Exception e) {
logger.error("Synchronous analysis failed", e);
throw new RuntimeException("Analysis failed", e);
}
}
private void initializeEngines() {
// Register all analysis engines
registerEngine(new com.yourcompany.codeclimate.engine.pmd.PmdAnalysisEngine());
registerEngine(new com.yourcompany.codeclimate.engine.checkstyle.CheckstyleAnalysisEngine());
registerEngine(new com.yourcompany.codeclimate.engine.metrics.CodeMetricsEngine());
// Add more engines as needed
// Initialize all engines
engines.forEach(engine -> {
try {
engine.initialize(config);
logger.info("Initialized engine: {}", engine.getName());
} catch (Exception e) {
logger.warn("Failed to initialize engine: {}", engine.getName(), e);
}
});
}
private void registerEngine(AnalysisEngine engine) {
engines.add(engine);
}
private List<CodeIssue> applyIssueLimits(List<CodeIssue> issues) {
Map<CodeIssue.Category, List<CodeIssue>> issuesByCategory = issues.stream()
.collect(Collectors.groupingBy(CodeIssue::getCategory));
List<CodeIssue> limitedIssues = new ArrayList<>();
for (Map.Entry<CodeIssue.Category, List<CodeIssue>> entry : issuesByCategory.entrySet()) {
CodeIssue.Category category = entry.getKey();
List<CodeIssue> categoryIssues = entry.getValue();
int limit = config.getQualityGate().getCategoryLimits()
.getOrDefault(category.name().toLowerCase(), config.getMaxIssuesPerCategory());
// Sort by severity and take the most important issues
categoryIssues.sort(Comparator.comparing(CodeIssue::getSeverity));
if (categoryIssues.size() > limit) {
limitedIssues.addAll(categoryIssues.subList(0, limit));
logger.info("Limited {} issues to {} for category {}",
categoryIssues.size(), limit, category);
} else {
limitedIssues.addAll(categoryIssues);
}
}
// Apply overall limit
if (limitedIssues.size() > config.getQualityGate().getMaxIssues()) {
limitedIssues = limitedIssues.subList(0, config.getQualityGate().getMaxIssues());
logger.info("Limited total issues to {}", config.getQualityGate().getMaxIssues());
}
return limitedIssues;
}
private AnalysisResult calculateAnalysisResult(List<CodeIssue> issues) {
Map<CodeIssue.Category, Long> issuesByCategory = issues.stream()
.collect(Collectors.groupingBy(CodeIssue::getCategory, Collectors.counting()));
Map<CodeIssue.Severity, Long> issuesBySeverity = issues.stream()
.collect(Collectors.groupingBy(CodeIssue::getSeverity, Collectors.counting()));
double maintainabilityIndex = calculateMaintainabilityIndex(issues);
boolean passedQualityGate = evaluateQualityGate(issues, maintainabilityIndex);
return new AnalysisResult.Builder()
.issues(issues)
.issuesByCategory(issuesByCategory)
.issuesBySeverity(issuesBySeverity)
.maintainabilityIndex(maintainabilityIndex)
.passedQualityGate(passedQualityGate)
.timestamp(java.time.Instant.now())
.build();
}
private double calculateMaintainabilityIndex(List<CodeIssue> issues) {
// Simplified maintainability index calculation
// In practice, this would consider complexity, duplication, test coverage, etc.
double baseScore = 100.0;
// Deduct points based on issues
double deduction = issues.stream()
.mapToDouble(issue -> {
return switch (issue.getSeverity()) {
case CRITICAL -> 5.0;
case MAJOR -> 3.0;
case MINOR -> 1.0;
case INFO -> 0.5;
case BLOCKER -> 10.0;
};
})
.sum();
double score = Math.max(0, baseScore - deduction);
// Convert to A-F scale (similar to CodeClimate)
return score / 25.0; // 100/25 = 4.0 (A), 0/25 = 0.0 (F)
}
private boolean evaluateQualityGate(List<CodeIssue> issues, double maintainabilityIndex) {
QualityGate gate = config.getQualityGate();
// Check maintainability threshold
if (maintainabilityIndex < gate.getMaintainabilityThreshold()) {
return false;
}
// Check critical issues
long criticalIssues = issues.stream()
.filter(issue -> issue.getSeverity() == CodeIssue.Severity.CRITICAL ||
issue.getSeverity() == CodeIssue.Severity.BLOCKER)
.count();
if (criticalIssues > 0) {
return false;
}
// Check category limits
Map<CodeIssue.Category, Long> issuesByCategory = issues.stream()
.collect(Collectors.groupingBy(CodeIssue::getCategory, Collectors.counting()));
for (Map.Entry<CodeIssue.Category, Long> entry : issuesByCategory.entrySet()) {
String categoryKey = entry.getKey().name().toLowerCase();
int limit = gate.getCategoryLimits().getOrDefault(categoryKey, Integer.MAX_VALUE);
if (entry.getValue() > limit) {
return false;
}
}
return true;
}
public void cleanup() {
engines.forEach(engine -> {
try {
engine.cleanup();
} catch (Exception e) {
logger.warn("Failed to cleanup engine: {}", engine.getName(), e);
}
});
}
}
### 8. Issue Deduplication **IssueDeduplicator.java** - Deduplicates similar issues
java
package com.yourcompany.codeclimate;
import com.yourcompany.codeclimate.model.CodeIssue;
import java.util.*;
import java.util.stream.Collectors;
public class IssueDeduplicator {
public List<CodeIssue> deduplicate(List<CodeIssue> issues) {
Map<String, CodeIssue> uniqueIssues = new LinkedHashMap<>();
for (CodeIssue issue : issues) {
String fingerprint = generateEnhancedFingerprint(issue);
// If we haven't seen this fingerprint, add the issue
// If we have seen it, keep the one with higher severity
if (!uniqueIssues.containsKey(fingerprint)) {
uniqueIssues.put(fingerprint, issue);
} else {
CodeIssue existing = uniqueIssues.get(fingerprint);
if (issue.getSeverity().ordinal() < existing.getSeverity().ordinal()) {
uniqueIssues.put(fingerprint, issue); // Lower ordinal = higher severity
}
}
}
return new ArrayList<>(uniqueIssues.values());
}
private String generateEnhancedFingerprint(CodeIssue issue) {
// Create a fingerprint that considers the issue type, location, and context
StringBuilder fingerprint = new StringBuilder();
// Basic components
fingerprint.append(issue.getCheckName());
fingerprint.append(":");
fingerprint.append(issue.getLocation().getPath().toString());
fingerprint.append(":");
fingerprint.append(issue.getLocation().getBegin().getLine());
fingerprint.append(":");
fingerprint.append(issue.getLocation().getBegin().getColumn());
// Include part of the description to catch similar but not identical issues
String description = issue.getDescription();
if (description != null && description.length() > 10) {
// Take first 10 chars of description for context
fingerprint.append(":");
fingerprint.append(description.substring(0, Math.min(10, description.length())).hashCode());
}
return fingerprint.toString();
}
public List<CodeIssue> groupSimilarIssues(List<CodeIssue> issues) {
Map<String, List<CodeIssue>> grouped = issues.stream()
.collect(Collectors.groupingBy(this::generateGroupKey));
return grouped.values().stream()
.map(this::selectRepresentativeIssue)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
private String generateGroupKey(CodeIssue issue) {
// Group by check name and file
return issue.getCheckName() + ":" + issue.getLocation().getPath().toString();
}
private Optional<CodeIssue> selectRepresentativeIssue(List<CodeIssue> similarIssues) {
if (similarIssues.isEmpty()) {
return Optional.empty();
}
// Select the most severe issue from the group
return similarIssues.stream()
.min(Comparator.comparing(CodeIssue::getSeverity))
.map(representative -> {
// Update the description to indicate this represents multiple issues
if (similarIssues.size() > 1) {
return new CodeIssue.Builder()
.checkName(representative.getCheckName())
.description(representative.getDescription() +
" (and " + (similarIssues.size() - 1) + " similar issues)")
.category(representative.getCategory())
.severity(representative.getSeverity())
.location(representative.getLocation())
.remediationPoints(representative.getRemediationPoints())
.tags(representative.getTags())
.metadata(representative.getMetadata())
.fingerprint(representative.getFingerprint())
.build();
}
return representative;
});
}
}
### 9. Analysis Result and Reporting **AnalysisResult.java** - Comprehensive analysis result
java
package com.yourcompany.codeclimate.model;
import java.time.Instant;
import java.util.List;
import java.util.Map;
public class AnalysisResult {
private final List issues;
private final Map issuesByCategory;
private final Map issuesBySeverity;
private final double maintainabilityIndex;
private final boolean passedQualityGate;
private final Instant timestamp;
private final Map metrics;
private AnalysisResult(Builder builder) {
this.issues = List.copyOf(builder.issues);
this.issuesByCategory = Map.copyOf(builder.issuesByCategory);
this.issuesBySeverity = Map.copyOf(builder.issuesBySeverity);
this.maintainabilityIndex = builder.maintainabilityIndex;
this.passedQualityGate = builder.passedQualityGate;
this.timestamp = builder.timestamp;
this.metrics = Map.copyOf(builder.metrics);
}
public static class Builder {
private List<CodeIssue> issues = List.of();
private Map<CodeIssue.Category, Long> issuesByCategory = Map.of();
private Map<CodeIssue.Severity, Long> issuesBySeverity = Map.of();
private double maintainabilityIndex = 0.0;
private boolean passedQualityGate = false;
private Instant timestamp = Instant.now();
private Map<String, Object> metrics = Map.of();
public Builder issues(List<CodeIssue> issues) {
this.issues = issues;
return this;
}
public Builder issuesByCategory(Map<CodeIssue.Category, Long> issuesByCategory) {
this.issuesByCategory = issuesByCategory;
return this;
}
public Builder issuesBySeverity(Map<CodeIssue.Severity, Long> issuesBySeverity) {
this.issuesBySeverity = issuesBySeverity;
return this;
}
public Builder maintainabilityIndex(double index) {
this.maintainabilityIndex = index;
return this;
}
public Builder passedQualityGate(boolean passed) {
this.passedQualityGate = passed;
return this;
}
public Builder timestamp(Instant timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder metrics(Map<String, Object> metrics) {
this.metrics = metrics;
return this;
}
public AnalysisResult build() {
return new AnalysisResult(this);
}
}
// Getters
public List<CodeIssue> getIssues() { return issues; }
public Map<CodeIssue.Category, Long> getIssuesByCategory() { return issuesByCategory; }
public Map<CodeIssue.Severity, Long> getIssuesBySeverity() { return issuesBySeverity; }
public double getMaintainabilityIndex() { return maintainabilityIndex; }
public boolean isPassedQualityGate() { return passedQualityGate; }
public Instant getTimestamp() { return timestamp; }
public Map<String, Object> getMetrics() { return metrics; }
public String getMaintainabilityGrade() {
if (maintainabilityIndex >= 3.5) return "A";
if (maintainabilityIndex >= 3.0) return "B";
if (maintainabilityIndex >= 2.5) return "C";
if (maintainabilityIndex >= 2.0) return "D";
if (maintainabilityIndex >= 1.0) return "E";
return "F";
}
public long getTotalIssues() {
return issues.size();
}
public long getIssuesBySeverity(CodeIssue.Severity severity) {
return issuesBySeverity.getOrDefault(severity, 0L);
}
}
### 10. Demonstration and Usage **CodeClimateDemo.java** - Complete demonstration
java
package com.yourcompany.codeclimate.demo;
import com.yourcompany.codeclimate.CodeClimateAnalyzer;
import com.yourcompany.codeclimate.CodeClimateConfig;
import com.yourcompany.codeclimate.model.AnalysisResult;
import com.yourcompany.codeclimate.model.CodeIssue;
import java.nio.file.Paths;
import java.util.Map;
public class CodeClimateDemo {
public static void main(String[] args) {
try {
// Example 1: Basic analysis
basicAnalysisExample();
// Example 2: Custom configuration
customConfigurationExample();
// Example 3: Quality gate evaluation
qualityGateExample();
} catch (Exception e) {
System.err.println("Demo failed: " + e.getMessage());
e.printStackTrace();
}
}
private static void basicAnalysisExample() {
System.out.println("=== Basic CodeClimate Analysis ===");
CodeClimateConfig config = new CodeClimateConfig.Builder()
.projectRoot(Paths.get("/path/to/your/java/project"))
.includePath("src/main/java")
.excludePath("src/test/java")
.maxIssuesPerCategory(50)
.build();
try (CodeClimateAnalyzer analyzer = new CodeClimateAnalyzer(config)) {
AnalysisResult result = analyzer.analyzeSync();
printAnalysisResult(result);
} catch (Exception e) {
System.err.println("Analysis failed: " + e.getMessage());
}
}
private static void customConfigurationExample() {
System.out.println("\n=== Custom Configuration Analysis ===");
CodeClimateConfig config = new CodeClimateConfig.Builder()
.projectRoot(Paths.get("/path/to/your/java/project"))
.includePaths(java.util.List.of("src/main/java", "src/lib/java"))
.excludePaths(java.util.List.of("**/test/**", "**/generated/**"))
.engineConfig("pmd.ruleset", "custom-rules.xml")
.engineConfig("checkstyle.config", "google_checks.xml")
.enableGitAwareAnalysis(true)
.maxIssuesPerCategory(25)
.qualityGate(new com.yourcompany.codeclimate.QualityGate.Builder()
.maintainabilityThreshold(3.0) // Minimum B grade
.testCoverageThreshold(80.0) // 80% test coverage
.maxComplexity(15) // Max cyclomatic complexity
.maxDuplication(5) // Max 5% duplication
.maxIssues(100) // Max total issues
.categoryLimit("bug", 5) // Max 5 bug risk issues
.categoryLimit("security", 3) // Max 3 security issues
.build())
.build();
try (CodeClimateAnalyzer analyzer = new CodeClimateAnalyzer(config)) {
AnalysisResult result = analyzer.analyzeSync();
printAnalysisResult(result);
// Generate reports
generateReports(result);
} catch (Exception e) {
System.err.println("Analysis failed: " + e.getMessage());
}
}
private static void qualityGateExample() {
System.out.println("\n=== Quality Gate Evaluation ===");
CodeClimateConfig config = new CodeClimateConfig.Builder()
.projectRoot(Paths.get("/path/to/your/java/project"))
.qualityGate(new com.yourcompany.codeclimate.QualityGate.Builder()
.maintainabilityThreshold(3.5) // A grade
.maxIssues(10) // Very strict
.categoryLimit("bug", 0) // Zero bugs allowed
.build())
.build();
try (CodeClimateAnalyzer analyzer = new CodeClimateAnalyzer(config)) {
AnalysisResult result = analyzer.analyzeSync();
System.out.println("Quality Gate Result: " +
(result.isPassedQualityGate() ? "PASSED" : "FAILED"));
if (!result.isPassedQualityGate()) {
System.out.println("Reasons for failure:");
if (result.getMaintainabilityIndex() < 3.5) {
System.out.println("- Maintainability grade below A (current: " +
result.getMaintainabilityGrade() + ")");
}
if (result.getIssuesBySeverity(CodeIssue.Severity.CRITICAL) > 0) {
System.out.println("- Critical issues found: " +
result.getIssuesBySeverity(CodeIssue.Severity.CRITICAL));
}
long bugIssues = result.getIssuesByCategory().getOrDefault(
CodeIssue.Category.BUG_RISK, 0L);
if (bugIssues > 0) {
System.out.println("- Bug risk issues found: " + bugIssues);
}
}
} catch (Exception e) {
System.err.println("Analysis failed: " + e.getMessage());
}
}
private static void printAnalysisResult(AnalysisResult result) {
System.out.println("=== Analysis Results ===");
System.out.printf("Maintainability: %s (%.2f)%n",
result.getMaintainabilityGrade(), result.getMaintainabilityIndex());
System.out.printf("Total Issues: %d%n", result.getTotalIssues());
System.out.printf("Quality Gate: %s%n",
result.isPassedQualityGate() ? "PASSED" : "FAILED");
System.out.println("\nIssues by Severity:");
result.getIssuesBySeverity().forEach((severity, count) -> {
System.out.printf(" %s: %d%n", severity, count);
});
System.out.println("\nIssues by Category:");
result.getIssuesByCategory().forEach((category, count) -> {
System.out.printf(" %s: %d%n", category.getDisplayName(), count);
});
// Print top 5 issues
System.out.println("\nTop 5 Issues:");
result.getIssues().stream()
.limit(5)
.forEach(issue -> {
System.out.printf(" [%s] %s: %s (%s:%d)%n",
issue.getSeverity(),
issue.getCheckName(),
issue.getDescription(),
issue.getLocation().getPath().getFileName(),
issue.getLocation().getBegin().getLine());
});
}
private static void generateReports(AnalysisResult result) {
// Generate JSON report (CodeClimate compatible)
String jsonReport = generateJsonReport(result);
// Save to file...
// Generate HTML report
String htmlReport = generateHtmlReport(result);
// Save to file...
// Generate GitHub annotations
String githubAnnotations = generateGitHubAnnotations(result);
// Output for GitHub Actions...
System.out.println("Reports generated successfully");
}
private static String generateJsonReport(AnalysisResult result) {
// Generate CodeClimate-compatible JSON report
com.fasterxml.jackson.databind.ObjectMapper mapper =
new com.fasterxml.jackson.databind.ObjectMapper();
try {
List<Map<String, Object>> issues = result.getIssues().stream()
.map(CodeClimateDemo::convertIssueToCodeClimateFormat)
.collect(java.util.stream.Collectors.toList());
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(issues);
} catch (Exception e) {
return "{}";
}
}
private static Map<String, Object> convertIssueToCodeClimateFormat(CodeIssue issue) {
Map<String, Object> ccIssue = new java.util.HashMap<>();
ccIssue.put("type", "issue");
ccIssue.put("check_name", issue.getCheckName());
ccIssue.put("description", issue.getDescription());
ccIssue.put("categories", java.util.List.of(issue.getCategory().getDisplayName()));
ccIssue.put("location", Map.of(
"path", issue.getLocation().getPath().toString(),
"positions", Map.of(
"begin", Map.of(
"line", issue.getLocation().getBegin().getLine(),
"column", issue.getLocation().getBegin().getColumn()
),
"end", Map.of(
"line", issue.getLocation().getEnd().getLine(),
"column", issue.getLocation().getEnd().getColumn()
)
)
));
ccIssue.put("remediation_points", Integer.parseInt(issue.getRemediationPoints()));
ccIssue.put("severity", issue.getSeverity().getLevel());
return ccIssue;
}
private static String generateHtmlReport(AnalysisResult result) {
// Generate a comprehensive HTML report
return String.format("""
<!DOCTYPE html>
<html>
<head>
<title>Code Quality Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.grade { font-size: 2em; font-weight: bold; }
.passed { color: green; }
.failed { color: red; }
.issue { border: 1px solid #ccc; margin: 10px 0; padding: 10px; }
.critical { border-left: 5px solid red; }
.major { border-left: 5px solid orange; }
.minor { border-left: 5px solid yellow; }
.info { border-left: 5px solid blue; }
</style>
</head>
<body>
<h1>Code Quality Report</h1>
<div class="grade %s">Maintainability: %s</div>
<p>Total Issues: %d | Quality Gate: <span class="%s">%s</span></p>
<!-- Issues would be listed here -->
</body>
</html>
""",
result.getMaintainabilityGrade(),
result.getMaintainabilityGrade(),
result.getTotalIssues(),
result.isPassedQualityGate() ? "passed" : "failed",
result.isPassedQualityGate() ? "PASSED" : "FAILED"
);
}
private static String generateGitHubAnnotations(AnalysisResult result) {
// Generate GitHub Actions annotations
StringBuilder annotations = new StringBuilder();
result.getIssues().stream()
.filter(issue -> issue.getSeverity() == CodeIssue.Severity.CRITICAL ||
issue.getSeverity() == CodeIssue.Severity.BLOCKER)
.forEach(issue -> {
annotations.append(String.format(
"::error file=%s,line=%d::%s: %s%n",
issue.getLocation().getPath(),
issue.getLocation().getBegin().getLine(),
issue.getCheckName(),
issue.getDescription()
));
});
return annotations.toString();
}
}
## Configuration Examples ### .codeclimate.yml
yaml
version: "2"
checks:
method-lines:
enabled: true
config:
threshold: 50
complex-logic:
enabled: true
config:
threshold: 10
file-lines:
enabled: true
config:
threshold: 500
exclude_patterns:
- "/test/"
- "/generated/"
- "/build/"
- "/target/"
plugins:
pmd:
enabled: true
channel: "stable"
config:
ruleset: "custom-rules.xml"
checkstyle:
enabled: true
config:
file: "google_checks.xml"
metrics:
enabled: true
ratings:
paths:
- "src/main/java/*/"
categories:
bug_risk:
weight: 1.0
security:
weight: 2.0
complexity:
weight: 0.5
style:
weight: 0.25
## Integration Examples ### GitHub Actions Integration
yaml
name: Code Quality
on: [push, pull_request]
jobs:
codeclimate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Java uses: actions/setup-java@v3 with: java-version: '17' distribution: 'temurin' - name: Run CodeClimate Analysis run: | mvn compile exec:java -Dexec.mainClass="com.yourcompany.codeclimate.demo.CodeClimateDemo" - name: Upload CodeClimate Report uses: actions/upload-artifact@v3 with: name: codeclimate-report path: reports/codeclimate.json
### Maven Plugin Integration
xml
com.yourcompany codeclimate-maven-plugin 1.0.0 analyze ${project.basedir} JSON 3.0 50
```
Key Features
- Multi-Engine Analysis: PMD, Checkstyle, custom metrics
- Issue Deduplication: Smart fingerprinting to avoid duplicates
- Quality Gates: Configurable pass/fail criteria
- Multiple Output Formats: JSON, HTML, GitHub annotations
- Git Integration: Git-aware analysis for PRs
- Configurable Limits: Prevent issue overload
- Maintainability Index: CodeClimate-compatible grading
- Extensible Architecture: Easy to add new analysis engines
This implementation provides a comprehensive CodeClimate-like analysis system that can be integrated into your Java development workflow to maintain high code quality standards.
Secure Java Dependency Management, Vulnerability Scanning & Software Supply Chain Protection (SBOM, SCA, CI Security & License Compliance)
https://macronepal.com/blog/github-code-scanning-in-java-complete-guide/
Explains GitHub Code Scanning for Java using tools like CodeQL to automatically analyze source code and detect security vulnerabilities directly inside CI/CD pipelines before deployment.
https://macronepal.com/blog/license-compliance-in-java-comprehensive-guide/
Explains software license compliance in Java projects, ensuring dependencies follow legal requirements (MIT, Apache, GPL, etc.) and preventing license violations in enterprise software.
https://macronepal.com/blog/container-security-for-java-uncovering-vulnerabilities-with-grype/
Explains using Grype to scan Java container images and filesystems for known CVEs in OS packages and application dependencies to improve container security.
https://macronepal.com/blog/syft-sbom-generation-in-java-comprehensive-software-bill-of-materials-for-jvm-applications/
Explains using Syft to generate SBOMs (Software Bill of Materials) for Java applications, listing all dependencies, libraries, and components for supply chain transparency.
https://macronepal.com/blog/comprehensive-dependency-analysis-generating-and-scanning-sboms-with-trivy-for-java/
Explains using Trivy to generate SBOMs and scan Java dependencies and container images for vulnerabilities, integrating security checks into CI/CD pipelines.
https://macronepal.com/blog/dependabot-for-java-in-java/
Explains GitHub Dependabot for Java projects, which automatically detects vulnerable dependencies and creates pull requests to update them securely.
https://macronepal.com/blog/parasoft-jtest-in-java-comprehensive-guide-to-code-analysis-and-testing/
Explains Parasoft Jtest, a static analysis and testing tool for Java that helps detect bugs, security issues, and code quality problems early in development.
https://macronepal.com/blog/snyk-open-source-in-java-comprehensive-dependency-vulnerability-management-2/
Explains Snyk Open Source for Java, which continuously scans dependencies for vulnerabilities and provides automated fix suggestions and monitoring.
https://macronepal.com/blog/owasp-dependency-check-in-java-complete-vulnerability-scanning-guide/
Explains OWASP Dependency-Check, which scans Java dependencies against the National Vulnerability Database (NVD) to detect known security vulnerabilities.
https://macronepal.com/blog/securing-your-dependencies-a-java-developers-guide-to-whitesource-mend-bolt/
Explains Mend (WhiteSource) Bolt for Java, a dependency management and SCA tool that provides vulnerability detection, license compliance, and security policy enforcement in enterprise environments.