我在编写算法时遇到问题,以帮助我扫描文件系统并找到某个类的所有子类。
详细信息:
我有一个使用nio
Files.walk()
扫描外部应用程序的应用程序,同时检索我检查“extends SuperClass”,如果单词退出则读取文件,我在列表中添加类名如下:
List<String> subclasses = new ArrayList<>();
Files.walk(appPath)
.filter(p->Files.isRegularFile(p) && p.toString()
.endsWith(".java")).forEach(path -> {
try {
List<String> lines = Files.readAllLines(path);
Pattern pattern = Pattern.compile("\\bextends SuperClass\\b");
Matcher matcher = pattern
.matcher(lines.stream()
.collect(Collectors.joining(" ")));
boolean isChild = matcher.find();
if(isChild) subclasses.add(path.getFileName().toString());
}catch (IOException e){
//handle IOE
}
上面的问题是它只获得SuperClass
的直接子类,但我需要检索所有直接和间接的子类。
我想到了递归,因为我不知道有多少SuperClass
的子类,但是我无法实现任何合理的实现。
备注:
SuperClass
的直接/间接子类extends
存在的位置修改
我使用以下正则表达式来比较名称和导入,以确保即使在相同名称的情况下,不同的包输出也是正确的:
Pattern pattern = Pattern.compile("("+superClasss.getPackage()+")[\\s\\S]*(\\bextends "+superClass.getName()+"\\b)[\\s\\S]");
我也尝试过:
Pattern pattern = Pattern.compile("\\bextends "+superClass.getName()+"\\b");
但是也有一些缺少的子类,我相信代码会跳过一些检查,并且不能完全发挥作用:
public static List<SuperClass> getAllSubClasses(Path path, SuperClass parentClass) throws IOException{
classesToDo.add(baseClass);
while(classesToDo.size() > 0) {
SuperClass superClass = classesToDo.remove(0);
List<SuperClass> subclasses = getDirectSubClasses(parentPath,parentClass);
if(subclasses.size() > 0)
classes.addAll(subclasses);
classesToDo.addAll(subclasses);
}
return classes;
}
真的很感激任何帮助!
编辑2
我还注意到另一个问题,就是当我检测到subclass
时,我得到的文件名currentPath.getFileName()
可能是也可能不是子类名,因为子类可能是nested
或非公共class
在同一个文件中。
答案 0 :(得分:13)
我强烈建议解析编译的类文件而不是源代码。由于这些类文件已经过优化以供机器处理,因此消除了源代码文件处理的许多复杂性和极端情况。
因此,使用ASM库构建完整的类层次结构树的解决方案如下所示:
public static Map<String, Set<String>> getClassHierarchy(Path root) throws IOException {
return Files.walk(root)
.filter(p->Files.isRegularFile(p) && isClass(p.getFileName().toString()))
.map(p -> getClassAndSuper(p))
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toSet())));
}
private static boolean isClass(String fName) {
// skip package-info and module-info
return fName.endsWith(".class") && !fName.endsWith("-info.class");
}
private static Map.Entry<String,String> getClassAndSuper(Path p) {
final class CV extends ClassVisitor {
Map.Entry<String,String> result;
public CV() {
super(Opcodes.ASM5);
}
@Override
public void visit(int version, int access,
String name, String signature, String superName, String[] interfaces) {
result = new AbstractMap.SimpleImmutableEntry<>(
Type.getObjectType(name).getClassName(),
superName!=null? Type.getObjectType(superName).getClassName(): "");
}
}
try {
final CV visitor = new CV();
new ClassReader(Files.readAllBytes(p)).accept(visitor, ClassReader.SKIP_CODE);
return visitor.result;
} catch (IOException ex) {
throw new UncheckedIOException(ex);
}
}
作为奖励,resp。为了创建一些测试用例,以下方法增加了为运行时类'source:
构建层次结构的能力public static Map<String, Set<String>> getClassHierarchy(Class<?> context)
throws IOException, URISyntaxException {
Path p;
URI clURI = context.getResource(context.getSimpleName()+".class").toURI();
if(clURI.getScheme().equals("jrt")) p = Paths.get(URI.create("jrt:/modules"));
else {
if(!clURI.getScheme().equals("file")) try {
FileSystems.getFileSystem(clURI);
} catch(FileSystemNotFoundException ex) {
FileSystems.newFileSystem(clURI, Collections.emptyMap());
}
String qn = context.getName();
p = Paths.get(clURI).getParent();
for(int ix = qn.indexOf('.'); ix>0; ix = qn.indexOf('.', ix+1)) p = p.getParent();
}
return getClassHierarchy(p);
}
然后,你可以做
Map<String, Set<String>> hierarchy = getClassHierarchy(Number.class);
System.out.println("Direct subclasses of "+Number.class);
hierarchy.getOrDefault("java.lang.Number", Collections.emptySet())
.forEach(System.out::println);
并获取
Direct subclasses of class java.lang.Number
java.lang.Float
java.math.BigDecimal
java.util.concurrent.atomic.AtomicLong
java.lang.Double
java.lang.Long
java.util.concurrent.atomic.AtomicInteger
java.lang.Short
java.math.BigInteger
java.lang.Byte
java.util.concurrent.atomic.Striped64
java.lang.Integer
或
Map<String, Set<String>> hierarchy = getClassHierarchy(Number.class);
System.out.println("All subclasses of "+Number.class);
printAllClasses(hierarchy, "java.lang.Number", " ");
private static void printAllClasses(
Map<String, Set<String>> hierarchy, String parent, String i) {
hierarchy.getOrDefault(parent, Collections.emptySet())
.forEach(x -> {
System.out.println(i+x);
printAllClasses(hierarchy, x, i+" ");
});
}
获取
All subclasses of class java.lang.Number
java.lang.Float
java.math.BigDecimal
java.util.concurrent.atomic.AtomicLong
java.lang.Double
java.lang.Long
java.util.concurrent.atomic.AtomicInteger
java.lang.Short
java.math.BigInteger
java.lang.Byte
java.util.concurrent.atomic.Striped64
java.util.concurrent.atomic.LongAdder
java.util.concurrent.atomic.LongAccumulator
java.util.concurrent.atomic.DoubleAdder
java.util.concurrent.atomic.DoubleAccumulator
java.lang.Integer
答案 1 :(得分:3)
免责声明:如果您有多个具有相同名称的类,并且未考虑包名称,则此解决方案可能无效。
我认为您可以通过跟踪要在List
中查找的类来执行此操作,并使用while循环直到已经探索了列表中的所有值。
以下是一些创建Map<String, List<String>>
的代码,key是类名,value是子类列表。
public class Test {
private static Path appPath = //your path
private static Map<String, List<String>> classes = new HashMap<>();
private static List<String> classesToDo = new ArrayList<>();
public static void main(String[] args) throws IOException {
classesToDo.add("AnswerValueValidatorBase");
while(classesToDo.size() > 0) {
String className = classesToDo.remove(0);
List<String> subclasses = getDirectSubclasses(className);
if(subclasses.size() > 0)
classes.put(className, subclasses);
classesToDo.addAll(subclasses);
}
System.out.println(classes);
}
private static List<String> getDirectSubclasses(String className) throws IOException {
List<String> subclasses = new ArrayList<>();
Files.walk(appPath)
.filter(p -> Files.isRegularFile(p) && p.toString().endsWith(".java"))
.forEach(path -> {
try {
List<String> lines = Files.readAllLines(path);
Pattern pattern = Pattern.compile("\\bextends "+className+"\\b");
Matcher matcher = pattern.matcher(lines.stream().collect(Collectors.joining(" ")));
boolean isChild = matcher.find();
if(isChild) {
String fileName = path.getFileName().toString();
String clazzName = fileName.substring(0, fileName.lastIndexOf("."));
subclasses.add(clazzName);
}
} catch(IOException e) {
//handle IOE
}
});
return subclasses;
}
}
在我的项目上运行它会返回看起来正确的内容
{
AnswerValueValidatorBase=[SingleNumericValidator, DefaultValidator, RatingValidator, ArrayValidatorBase, DocumentValidator],
ArrayValidatorBase=[MultiNumericValidator, StringArrayValidator, IntegerArrayValidator, MultiCheckboxValidator],
DefaultValidator=[IntegerValidator, DateValidator, StringValidator, CountryValidator, PercentageValidator],
IntegerArrayValidator=[MultiPercentageValidator, RankValidator, MultiDropValidator, MultiRadioValidator, CheckboxValidator],
SingleNumericValidator=[SliderValidator],
MultiNumericValidator=[MultiSliderValidator],
StringArrayValidator=[MultiTextValidator, ChecklistValidator]
}
编辑
这样做的递归方式是
public class Test {
private static Path appPath = // your path
public static void main(String[] args) throws IOException {
List<String> classesToDo = new ArrayList<>();
classesToDo.add("AnswerValueValidatorBase");
Map<String, List<String>> classesMap = getSubclasses(new HashMap<>(), classesToDo);
System.out.println(classesMap);
}
private static Map<String, List<String>> getSubclasses(Map<String, List<String>> classesMap, List<String> classesToDo) throws IOException {
if(classesToDo.size() == 0) {
return classesMap;
} else {
String className = classesToDo.remove(0);
List<String> subclasses = getDirectSubclasses(className);
if(subclasses.size() > 0)
classesMap.put(className, subclasses);
classesToDo.addAll(subclasses);
return getSubclasses(classesMap, classesToDo);
}
}
private static List<String> getDirectSubclasses(String className) throws IOException {
// same as above
}
}