001package com.streamconverter.tools; 002 003import java.io.IOException; 004import java.nio.file.Files; 005import java.nio.file.Path; 006import java.nio.file.Paths; 007import java.nio.file.StandardCopyOption; 008import java.time.LocalDateTime; 009import java.time.format.DateTimeFormatter; 010import java.util.*; 011import java.util.regex.Matcher; 012import java.util.regex.Pattern; 013import java.util.stream.Collectors; 014import javax.xml.parsers.DocumentBuilder; 015import javax.xml.parsers.DocumentBuilderFactory; 016import org.w3c.dom.Document; 017import org.w3c.dom.Element; 018import org.w3c.dom.NodeList; 019 020public class PmdAutoFixer { 021 022 private static final String PMD_REPORT_PATH = "build/reports/pmd/main.xml"; 023 private static final String SOURCE_PATH = "src/main/java"; 024 025 private final Map<String, Integer> fixCounts = new HashMap<>(); 026 private final Set<String> processedFiles = new HashSet<>(); 027 028 public static void main(String[] args) { 029 new PmdAutoFixer().run(); 030 } 031 032 public void run() { 033 System.out.println("๐ง PMD Auto-Fixer Starting..."); 034 035 try { 036 // Create backup 037 createBackup(); 038 039 // Parse PMD report 040 List<PmdViolation> violations = parsePmdReport(); 041 System.out.printf("๐ Found %d violations to process%n", violations.size()); 042 043 // Group violations by rule 044 Map<String, List<PmdViolation>> violationsByRule = 045 violations.stream().collect(Collectors.groupingBy(v -> v.rule)); 046 047 // Apply fixes 048 applyFixes(violationsByRule); 049 050 // Print summary 051 printSummary(); 052 053 } catch (Exception e) { 054 System.err.println("โ Error during auto-fix: " + e.getMessage()); 055 e.printStackTrace(); 056 } 057 } 058 059 private void createBackup() throws IOException { 060 Path sourcePath = Paths.get(SOURCE_PATH); 061 if (!Files.exists(sourcePath)) { 062 throw new IOException("Source path not found: " + sourcePath); 063 } 064 065 String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd-HHmmss")); 066 Path backupPath = Paths.get("pmd-fixes-backup-" + timestamp); 067 068 System.out.println("๐พ Creating backup at: " + backupPath.toAbsolutePath()); 069 070 copyDirectory(sourcePath, backupPath); 071 } 072 073 private void copyDirectory(Path source, Path target) throws IOException { 074 Files.walk(source) 075 .forEach( 076 sourcePath -> { 077 try { 078 Path targetPath = target.resolve(source.relativize(sourcePath)); 079 if (Files.isDirectory(sourcePath)) { 080 Files.createDirectories(targetPath); 081 } else { 082 Files.createDirectories(targetPath.getParent()); 083 Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING); 084 } 085 } catch (IOException e) { 086 throw new RuntimeException(e); 087 } 088 }); 089 } 090 091 private List<PmdViolation> parsePmdReport() throws Exception { 092 Path reportPath = Paths.get(PMD_REPORT_PATH); 093 if (!Files.exists(reportPath)) { 094 throw new IOException("PMD report not found: " + reportPath); 095 } 096 097 List<PmdViolation> violations = new ArrayList<>(); 098 099 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 100 DocumentBuilder builder = factory.newDocumentBuilder(); 101 Document doc = builder.parse(reportPath.toFile()); 102 103 NodeList fileNodes = doc.getElementsByTagName("file"); 104 for (int i = 0; i < fileNodes.getLength(); i++) { 105 Element fileElement = (Element) fileNodes.item(i); 106 String fileName = fileElement.getAttribute("name"); 107 108 NodeList violationNodes = fileElement.getElementsByTagName("violation"); 109 for (int j = 0; j < violationNodes.getLength(); j++) { 110 Element violationElement = (Element) violationNodes.item(j); 111 violations.add( 112 new PmdViolation( 113 fileName, 114 violationElement.getAttribute("rule"), 115 Integer.parseInt(violationElement.getAttribute("beginline")), 116 Integer.parseInt(violationElement.getAttribute("endline")), 117 violationElement.getTextContent().trim())); 118 } 119 } 120 121 return violations; 122 } 123 124 private void applyFixes(Map<String, List<PmdViolation>> violationsByRule) { 125 System.out.println("\n๐ฏ Applying automatic fixes..."); 126 127 // Phase 1: Safe Final Keyword Additions 128 fixMethodArgumentCouldBeFinal(violationsByRule.get("MethodArgumentCouldBeFinal")); 129 fixLocalVariableCouldBeFinal(violationsByRule.get("LocalVariableCouldBeFinal")); 130 131 // Phase 2: Performance Optimizations 132 fixInefficientEmptyStringCheck(violationsByRule.get("InefficientEmptyStringCheck")); 133 fixAppendCharacterWithChar(violationsByRule.get("AppendCharacterWithChar")); 134 fixUseIndexOfChar(violationsByRule.get("UseIndexOfChar")); 135 136 // Phase 3: Code Style Improvements 137 fixRedundantFieldInitializer(violationsByRule.get("RedundantFieldInitializer")); 138 fixUseUnderscoresInNumericLiterals(violationsByRule.get("UseUnderscoresInNumericLiterals")); 139 } 140 141 private void fixMethodArgumentCouldBeFinal(List<PmdViolation> violations) { 142 if (violations == null || violations.isEmpty()) return; 143 144 System.out.println("๐ Fixing MethodArgumentCouldBeFinal violations..."); 145 146 Map<String, List<PmdViolation>> byFile = 147 violations.stream().collect(Collectors.groupingBy(v -> v.fileName)); 148 149 int totalFixed = 0; 150 151 for (Map.Entry<String, List<PmdViolation>> entry : byFile.entrySet()) { 152 try { 153 String content = Files.readString(Paths.get(entry.getKey())); 154 // Simple pattern-based fixing for method parameters 155 // This is a basic implementation - could be enhanced with proper Java parsing 156 Pattern methodParamPattern = Pattern.compile("(\\([^)]*?)\\b(\\w+\\s+)(\\w+)\\s*([,)])"); 157 158 Matcher matcher = methodParamPattern.matcher(content); 159 StringBuffer sb = new StringBuffer(); 160 int fixCount = 0; 161 162 while (matcher.find()) { 163 String paramType = matcher.group(2).trim(); 164 if (!paramType.startsWith("final ")) { 165 matcher.appendReplacement( 166 sb, 167 matcher.group(1) 168 + "final " 169 + paramType 170 + " " 171 + matcher.group(3) 172 + matcher.group(4)); 173 fixCount++; 174 } 175 } 176 matcher.appendTail(sb); 177 178 if (fixCount > 0) { 179 Files.writeString(Paths.get(entry.getKey()), sb.toString()); 180 totalFixed += fixCount; 181 processedFiles.add(entry.getKey()); 182 } 183 184 } catch (IOException e) { 185 System.err.println("โ ๏ธ Error processing file: " + entry.getKey() + " - " + e.getMessage()); 186 } 187 } 188 189 fixCounts.put("MethodArgumentCouldBeFinal", totalFixed); 190 } 191 192 private void fixLocalVariableCouldBeFinal(List<PmdViolation> violations) { 193 if (violations == null || violations.isEmpty()) return; 194 195 System.out.println("๐ Fixing LocalVariableCouldBeFinal violations..."); 196 197 Map<String, List<PmdViolation>> byFile = 198 violations.stream().collect(Collectors.groupingBy(v -> v.fileName)); 199 200 int totalFixed = 0; 201 202 for (Map.Entry<String, List<PmdViolation>> entry : byFile.entrySet()) { 203 try { 204 String content = Files.readString(Paths.get(entry.getKey())); 205 206 // Pattern for local variable declarations 207 Pattern localVarPattern = 208 Pattern.compile( 209 "(^\\s+)((?:String|int|long|double|boolean|List|Map|Set|\\w+)(?:<[^>]+>)?)\\s+(\\w+)\\s*=", 210 Pattern.MULTILINE); 211 212 Matcher matcher = localVarPattern.matcher(content); 213 StringBuffer sb = new StringBuffer(); 214 int fixCount = 0; 215 216 while (matcher.find()) { 217 String indent = matcher.group(1); 218 String type = matcher.group(2); 219 String varName = matcher.group(3); 220 221 if (!type.startsWith("final ")) { 222 matcher.appendReplacement(sb, indent + "final " + type + " " + varName + " ="); 223 fixCount++; 224 } 225 } 226 matcher.appendTail(sb); 227 228 if (fixCount > 0) { 229 Files.writeString(Paths.get(entry.getKey()), sb.toString()); 230 totalFixed += fixCount; 231 processedFiles.add(entry.getKey()); 232 } 233 234 } catch (IOException e) { 235 System.err.println("โ ๏ธ Error processing file: " + entry.getKey() + " - " + e.getMessage()); 236 } 237 } 238 239 fixCounts.put("LocalVariableCouldBeFinal", totalFixed); 240 } 241 242 private void fixInefficientEmptyStringCheck(List<PmdViolation> violations) { 243 if (violations == null || violations.isEmpty()) return; 244 245 System.out.println("๐ Fixing InefficientEmptyStringCheck violations..."); 246 247 int totalFixed = 248 applySimpleReplacement( 249 violations, 250 "\\.length\\(\\)\\s*==\\s*0", 251 ".isEmpty()", 252 "\\.length\\(\\)\\s*!=\\s*0", 253 "!$1.isEmpty()"); 254 255 fixCounts.put("InefficientEmptyStringCheck", totalFixed); 256 } 257 258 private void fixAppendCharacterWithChar(List<PmdViolation> violations) { 259 if (violations == null || violations.isEmpty()) return; 260 261 System.out.println("๐ Fixing AppendCharacterWithChar violations..."); 262 263 int totalFixed = applySimpleReplacement(violations, "\\.append\\(\"(.)\"\\)", ".append('$1')"); 264 265 fixCounts.put("AppendCharacterWithChar", totalFixed); 266 } 267 268 private void fixUseIndexOfChar(List<PmdViolation> violations) { 269 if (violations == null || violations.isEmpty()) return; 270 271 System.out.println("๐ Fixing UseIndexOfChar violations..."); 272 273 int totalFixed = 274 applySimpleReplacement(violations, "\\.indexOf\\(\"(.)\"\\)", ".indexOf('$1')"); 275 276 fixCounts.put("UseIndexOfChar", totalFixed); 277 } 278 279 private void fixRedundantFieldInitializer(List<PmdViolation> violations) { 280 if (violations == null || violations.isEmpty()) return; 281 282 System.out.println("๐ Fixing RedundantFieldInitializer violations..."); 283 284 int totalFixed = 285 applySimpleReplacement( 286 violations, "\\s*=\\s*null;", ";", "\\s*=\\s*false;", ";", "\\s*=\\s*0;", ";"); 287 288 fixCounts.put("RedundantFieldInitializer", totalFixed); 289 } 290 291 private void fixUseUnderscoresInNumericLiterals(List<PmdViolation> violations) { 292 if (violations == null || violations.isEmpty()) return; 293 294 System.out.println("๐ Fixing UseUnderscoresInNumericLiterals violations..."); 295 296 // This would need more sophisticated number parsing 297 // Placeholder implementation 298 fixCounts.put("UseUnderscoresInNumericLiterals", 0); 299 } 300 301 private int applySimpleReplacement(List<PmdViolation> violations, String... replacements) { 302 if (violations == null || violations.isEmpty()) return 0; 303 304 Map<String, List<PmdViolation>> byFile = 305 violations.stream().collect(Collectors.groupingBy(v -> v.fileName)); 306 307 int totalFixed = 0; 308 309 for (String fileName : byFile.keySet()) { 310 try { 311 String content = Files.readString(Paths.get(fileName)); 312 String originalContent = content; 313 int fileFixCount = 0; 314 315 for (int i = 0; i < replacements.length; i += 2) { 316 String pattern = replacements[i]; 317 String replacement = replacements[i + 1]; 318 String beforeReplacement = content; 319 content = content.replaceAll(pattern, replacement); 320 321 // Count actual replacements by checking how many times the pattern matched 322 if (!content.equals(beforeReplacement)) { 323 int matches = beforeReplacement.split(pattern, -1).length - 1; 324 fileFixCount += matches; 325 } 326 } 327 328 if (!content.equals(originalContent)) { 329 Files.writeString(Paths.get(fileName), content); 330 totalFixed += fileFixCount; 331 processedFiles.add(fileName); 332 } 333 334 } catch (IOException e) { 335 System.err.println("โ ๏ธ Error processing file: " + fileName + " - " + e.getMessage()); 336 } 337 } 338 339 return totalFixed; 340 } 341 342 private void printSummary() { 343 System.out.println("\n๐ Auto-Fix Summary:"); 344 System.out.println("โ".repeat(50)); 345 346 int totalFixes = 0; 347 for (Map.Entry<String, Integer> entry : fixCounts.entrySet()) { 348 System.out.printf("%-35s: %3d fixes%n", entry.getKey(), entry.getValue()); 349 totalFixes += entry.getValue(); 350 } 351 352 System.out.println("โ".repeat(50)); 353 System.out.printf("%-35s: %3d fixes%n", "TOTAL", totalFixes); 354 System.out.printf("Files processed: %d%n", processedFiles.size()); 355 356 System.out.println("\n๐งช Next steps:"); 357 System.out.println(" 1. Review changes: git diff"); 358 System.out.println(" 2. Run tests: ./gradlew test"); 359 System.out.println(" 3. Run PMD again: ./gradlew pmdMain"); 360 System.out.println(" 4. If satisfied, commit changes"); 361 362 System.out.println("\n๐ PMD Auto-Fix Complete!"); 363 } 364 365 private static class PmdViolation { 366 final String fileName; 367 final String rule; 368 369 PmdViolation(String fileName, String rule, int beginLine, int endLine, String message) { 370 this.fileName = fileName; 371 this.rule = rule; 372 } 373 } 374}