package de.tutorials.training;
import org.objectweb.asm.ClassReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ClassHierarchyAnalysis {
private final static String CLASS_FILE_EXTENSION = ".class";
private final static String JAR_FILE_EXTENSION = ".jar";
public static void main(String[] args) throws Exception {
ClassHierarchyAnalysis cha = new ClassHierarchyAnalysis();
System.out.println(cha.subTypesOf(CharSequence.class)); //Interface
System.out.println(cha.subTypesOf(AbstractList.class)); //AbstractClass
//System.out.println(cha.subTypesOf(Set.class,new File("./bin/").getAbsolutePath())); -> MySet extends LinkedHashSet<String>
}
public Set<String> subTypesOf(Class<?> type, String... classpathComponents) {
Set<String> alreadyKnownSubTypeNames = new HashSet<String>();
if (Modifier.isFinal(type.getModifiers())) {
return alreadyKnownSubTypeNames;
}
String cp = classpathComponents.length == 0 ? getFullClassPath() : makeClasspath(classpathComponents);
System.out.println("Using classpath: " + cp);
for (String classPathComponent : cp.split(File.pathSeparator)) {
searchSubTypesInClassPathComponent(type, alreadyKnownSubTypeNames, classPathComponent);
}
return alreadyKnownSubTypeNames;
}
private void searchSubTypesInClassPathComponent(Class<?> baseType, Set<String> alreadyKnownSubTypeNames, String classPathComponent) {
File classPathComponentFile = new File(classPathComponent);
if (!classPathComponentFile.exists()) {
System.out.println("Skipping non existing classpath component: " + classPathComponent);
return;
}
if (classPathComponent.endsWith(JAR_FILE_EXTENSION)) {
System.out.println("Scanning Jar File: " + classPathComponent);
searchSubTypesInJar(baseType, alreadyKnownSubTypeNames, classPathComponentFile);
} else if (classPathComponentFile.isDirectory()) {
System.out.println("Scanning Directory: " + classPathComponent);
searchSubTypesInDirectory(baseType, alreadyKnownSubTypeNames, classPathComponentFile);
} else {
System.out.println("Skipping unknown classpath component: " + classPathComponent);
}
}
private void searchSubTypesInDirectory(Class<?> type, Set<String> knwonSubTypeNames, File classPathDirectory) {
Deque<File> stack = new LinkedList<File>();
stack.push(classPathDirectory);
while (!stack.isEmpty()) {
File file = stack.pop();
if (file.isDirectory()) {
for (File nestedFiles : file.listFiles()) {
stack.push(nestedFiles);
}
} else {
String classFileCandidate = file.getName();
if (classFileCandidate.endsWith(CLASS_FILE_EXTENSION)) {
try {
String classFile = classPathDirectory.toURI().relativize(file.toURI()).getPath();
analyzePotentialSubType(classFile, type,knwonSubTypeNames);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
private void searchSubTypesInJar(Class<?> baseType, Set<String> alreadyKnownSubTypeNames, File classPathJar) {
JarFile jarFile = null;
try {
jarFile = new JarFile(classPathJar);
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String classFileCandidate = entry.getName();
if (classFileCandidate.endsWith(CLASS_FILE_EXTENSION)) {
analyzePotentialSubType(classFileCandidate, baseType,alreadyKnownSubTypeNames);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (jarFile != null) {
try {
jarFile.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void analyzePotentialSubType(String classFilePath, Class<?> baseType, Set<String> alreadyKnownSubTypeNames) throws IOException {
boolean subtypeFound = false;
boolean stopSearch = false;
String baseTypePath = baseType.getName().replace('.', '/');
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
String currentClassFileCandidate = classFilePath;
do {
InputStream classInputStream = null;
try {
classInputStream = classLoader.getResourceAsStream(currentClassFileCandidate);
ClassReader cr = new ClassReader(classInputStream);
if (baseType.isInterface()) {
subtypeFound = Arrays.asList(cr.getInterfaces()).contains(baseTypePath);
} else {
subtypeFound = baseTypePath.equals(cr.getSuperName());
}
if (subtypeFound || cr.getSuperName() == null /* we reached java.lang.Object */) {
stopSearch = true;
}else{
currentClassFileCandidate = cr.getSuperName() +CLASS_FILE_EXTENSION;
}
} catch (Throwable t) {
System.err.printf("Could not analyze class: %s due to error: %s",toClassName(classFilePath), t.getMessage()).println();
} finally {
if (classInputStream != null) {
classInputStream.close();
}
}
} while (!stopSearch);
if (subtypeFound) {
String subTypeName = toClassName(classFilePath);
alreadyKnownSubTypeNames.add(subTypeName);
System.out.printf("%s is a subtype of %s", subTypeName, baseType.getName()).println();
}
}
private String toClassName(String classFileCandidate) {
return classFilePathToClassName(classFileCandidate).substring(0,classFileCandidate.length() - CLASS_FILE_EXTENSION.length());
}
private String classFilePathToClassName(String classFileCandidate) {
return classFileCandidate.replace('/', '.');
}
private String getFullClassPath() {
RuntimeMXBean rmx = ManagementFactory.getRuntimeMXBean();
return makeClasspath(rmx.getBootClassPath(), rmx.getClassPath());
}
private String makeClasspath(String... classpathComponents) {
String cp = "";
for (String cpc : classpathComponents) {
cp += cpc + File.pathSeparatorChar;
}
return cp;
}
}