package io.github.linwancen.code.modify;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 删除未使用的类
* <br>仅依赖 JDK
*/
public class DeleteNotUsage {
/**
* 多线程电脑会很卡,约快 3 倍
*/
private static final boolean PARALLEL = true;
private static final boolean DELETE = false;
private static final String CLASS_PATH = ClassLoader.getSystemResource("").getPath();
/**
* 扫描根目录
* <br>根据需要添加.getParentFile()
*/
private static final File ROOT;
static {
if (CLASS_PATH.endsWith("test-classes")) {
// maven
ROOT= new File(CLASS_PATH) // target/test-classes
.getParentFile() // target
.getParentFile(); //
} else if (CLASS_PATH.endsWith("main")) {
// gradle
ROOT = new File(CLASS_PATH) // main
.getParentFile() // java
.getParentFile() // classes
.getParentFile() // build
.getParentFile();
} else {
ROOT = new File("");
}
}
private static final int ROOT_LEN = ROOT.getPath().length();
private static final Pattern EXT_PATTERN = Pattern.compile("\\.(?:java|xml|properties|conf|yml)|services$");
private static final Pattern EXCLUDE_PATTERN = Pattern.compile("target|.git");
/**
* 添加自行定义的会被调用到的注解或关键字
*/
private static final Pattern USED_PATTERN = Pattern.compile(
"@(?:Service|Repository|Component|Test|Controller|Configuration)|main\\(String");
private static final AtomicInteger N = new AtomicInteger();
private static final class Data {
final String pathString;
final Set<String> usages = new ConcurrentSkipListSet<>();
final File file;
public Data(String pathString, File file) {
this.pathString = pathString;
this.file = file;
}
}
public static void main(String[] args) throws Exception {
List<Path> paths = Files.walk(ROOT.toPath())
.filter(path -> path.toFile().isFile())
.filter(path -> {
String s = path.toString();
return EXT_PATTERN.matcher(s).find() && !EXCLUDE_PATTERN.matcher(s).find();
})
.collect(Collectors.toList());
int fileCount = paths.size();
System.out.println("walk complete:" + fileCount);
Map<String, Data> usageMap = new HashMap<>();
StringBuilder patternStr = new StringBuilder("\\b(?:");
for (Path path : paths) {
filterClassSimpleName(usageMap, patternStr, path);
}
System.out.println("filter complete:" + usageMap.size());
int len = patternStr.length();
if (len == 0) {
return;
}
patternStr.delete(len - 1, len);
patternStr.append(")\\b");
Pattern pattern = Pattern.compile(patternStr.toString());
System.out.println("pattern compile complete:" + patternStr.length());
long start = System.currentTimeMillis();
if (PARALLEL) {
paths.parallelStream().forEach(path ->
check(fileCount, usageMap, pattern, start, N.incrementAndGet(), path));
} else {
for (int i = 0; i < fileCount; i++) {
Path path = paths.get(i);
check(fileCount, usageMap, pattern, start, i, path);
}
}
long useTime = (System.currentTimeMillis() - start) / 1000;
System.out.println("check complete, size:{}" + fileCount + " use " + useTime / 60 + ":" + useTime % 60);
long count = usageMap.entrySet().stream()
.filter(entry -> {
if (!entry.getValue().usages.isEmpty()) {
return false;
}
String symbol = "-";
if (DELETE) {
symbol = entry.getValue().file.delete() ? "√" : "×";
}
String path = entry.getValue().pathString.substring(ROOT_LEN);
System.out.println(symbol + "\t.(" + entry.getKey() + ".java:0)\t" + path);
return true;
})
.count();
System.out.println("check print complete, " + count + "/" + usageMap.size());
}
private static void filterClassSimpleName(Map<String, Data> usageMap, StringBuilder patternStr, Path path) {
String s = path.toString();
if (!s.endsWith(".java")) {
return;
}
try {
byte[] bytes = Files.readAllBytes(path);
String code = new String(bytes, StandardCharsets.UTF_8);
if (USED_PATTERN.matcher(code).find()) {
return;
}
} catch (IOException e) {
e.printStackTrace();
return;
}
String classSimpleName = s.substring(s.lastIndexOf(File.separator) + 1, s.length() - 5);
usageMap.put(classSimpleName, new Data(s, path.toFile()));
patternStr.append(classSimpleName).append("|");
}
private static void check(int fileCount, Map<String, Data> usageMap, Pattern pattern, long start, int i, Path p) {
if (i != 0 && (i % 1000) == 0) {
long use = System.currentTimeMillis() - start;
long remain = use / i * (fileCount - i);
use = use / 1000;
remain = remain / 1000;
System.out.println("check " + i + "/" + fileCount
+ ", use " + use / 60 + ":" + use % 60
+ ", remain " + remain / 60 + ":" + remain % 60);
}
String pathStr = p.toString();
try {
byte[] bytes = Files.readAllBytes(p);
String code = new String(bytes, StandardCharsets.UTF_8);
Matcher m = pattern.matcher(code);
while (m.find()) {
String s = m.group();
Data data = usageMap.get(s);
if (!data.pathString.equals(pathStr)) {
data.usages.add(pathStr);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}