I'm coding a little class hierarchy printing tool for easy show hierarchies of java classes.
This is my current code:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
import javax.swing.JDialog;
import jdk.nashorn.internal.runtime.PropertyMap; // since Java 8, just for testing
import com.sun.javafx.collections.TrackableObservableList;
public class PrintClassHierarchy {
private static final String PADDING = " ";
private static final String PADDING_WITH_COLUMN = " | ";
private static final String PADDING_WITH_ENTRY = " |--- ";
private static final String BASE_CLASS = Object.class.getName();
private TreeMap<String, List<String>> entries;
private boolean[] moreToCome;
public static void main(final String[] args) {
new PrintClassHierarchy().printHierarchy(
PrintStream.class,
FileOutputStream.class,
FileInputStream.class,
TrackableObservableList.class,
PropertyMap.class,
JDialog.class
);
}
public void printHierarchy(final Class<?>... clazzes) {
// clean values
entries = new TreeMap<>();
moreToCome = new boolean[99];
// get all entries of tree
traverseClasses(clazzes);
// print collected entries as ASCII tree
printHierarchy(BASE_CLASS, 0);
}
private void printHierarchy(final String node, final int level) {
for (int i = 1; i < level; i++) {
System.out.print(moreToCome[i - 1] ? PADDING_WITH_COLUMN : PADDING);
}
if (level > 0) {
System.out.print(PADDING_WITH_ENTRY);
}
System.out.println(node);
if (entries.containsKey(node)) {
final List<String> list = entries.get(node);
for (int i = 0; i < list.size(); i++) {
moreToCome[level] = (i < list.size() - 1);
printHierarchy(list.get(i), level + 1);
}
}
}
private void traverseClasses(final Class<?>... clazzes) {
Arrays.asList(clazzes).forEach(c -> traverseClasses(c, 0));
}
private void traverseClasses(final Class<?> clazz, final int level) {
final Class<?> superClazz = clazz.getSuperclass();
if (superClazz == null) {
return;
}
final String name = clazz.getName();
final String superName = superClazz.getName();
if (entries.containsKey(superName)) {
final List<String> list = entries.get(superName);
if (!list.contains(name)) {
list.add(name);
Collections.sort(list); // SortedList
}
} else {
entries.put(superName, new ArrayList<String>(Arrays.asList(name)));
}
traverseClasses(superClazz, level + 1);
}
}
This prints out:
java.lang.Object |--- java.awt.Component | |--- java.awt.Container | |--- java.awt.Window | |--- java.awt.Dialog | |--- javax.swing.JDialog |--- java.io.InputStream | |--- java.io.FileInputStream |--- java.io.OutputStream | |--- java.io.FileOutputStream | |--- java.io.FilterOutputStream | |--- java.io.PrintStream |--- java.util.AbstractCollection | |--- java.util.AbstractList | |--- javafx.collections.ObservableListBase | |--- javafx.collections.ModifiableObservableListBase | |--- com.sun.javafx.collections.ObservableListWrapper | |--- com.sun.javafx.collections.TrackableObservableList |--- jdk.nashorn.internal.runtime.PropertyMap
This output is already valid. I want to know, if this is a good approach to do the task, or if there is another (better) way to do this?
I'm not sure, if the entries
and moreToCome
"global" variables are the way to go.
Or if you have general improvements, please let me know!
Edit 1:
I reworked the code to find out the maximum level in hierarchy to discover how big the boolean array has to be. Also I reworked the method calls and put the traverseClasses
into the constructor as recommended. Furthermore I renamed some variables to more descriptive names and added some comments. moreToCome
is now moreClassesInHierarchy
.
Second approach:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.TreeMap;
import javax.swing.JDialog;
import jdk.nashorn.internal.runtime.PropertyMap; // since Java 8, just for testing
import com.sun.javafx.collections.TrackableObservableList;
public class PrintClassHierarchy {
private static final String PADDING = " ";
private static final String PADDING_WITH_COLUMN = " | ";
private static final String PADDING_WITH_ENTRY = " |--- ";
private static final String BASE_CLASS = Object.class.getName();
private final TreeMap<String, List<String>> subClazzEntries;
private final boolean[] moreClassesInHierarchy;
private int maxLevel = 0;
public static void main(final String[] args) {
new PrintClassHierarchy(
PrintStream.class,
FileOutputStream.class,
FileInputStream.class,
TrackableObservableList.class,
PropertyMap.class,
JDialog.class
).printHierarchy();
}
public PrintClassHierarchy(final Class<?>... clazzes) {
subClazzEntries = new TreeMap<>();
// get all entries of tree
traverseClasses(clazzes);
// initialize array with size of maximum class hierarchy level
moreClassesInHierarchy = new boolean[maxLevel];
}
public void printHierarchy() {
// print collected entries as ASCII tree
printHierarchy(BASE_CLASS, 0);
}
private void printHierarchy(final String clazzName, final int level) {
for (int i = 1; i < level; i++) {
// The flag moreToCome holds an identifier, if there is another class
// on the specific value, that comes beneath the current class.
// So print either '|' or ' '.
System.out.print(moreClassesInHierarchy[i - 1] ? PADDING_WITH_COLUMN : PADDING);
}
if (level > 0) {
System.out.print(PADDING_WITH_ENTRY);
}
System.out.println(clazzName);
if (subClazzEntries.containsKey(clazzName)) {
final List<String> list = subClazzEntries.get(clazzName);
for (int i = 0; i < list.size(); i++) {
// if there is another class that comes beneath the next class,
// flag this level
moreClassesInHierarchy[level] = (i < list.size() - 1);
printHierarchy(list.get(i), level + 1);
}
}
}
private void traverseClasses(final Class<?>... clazzes) {
// do the traverseClasses on each provided class (possible since Java 8)
Arrays.asList(clazzes).forEach(c -> traverseClasses(c, 0));
}
private void traverseClasses(final Class<?> clazz, final int level) {
final Class<?> superClazz = clazz.getSuperclass();
if (level > maxLevel) {
// discover maximum level
maxLevel = level;
}
if (superClazz == null) {
// we arrived java.lang.Object
return;
}
final String name = clazz.getName();
final String superName = superClazz.getName();
if (subClazzEntries.containsKey(superName)) {
final List<String> list = subClazzEntries.get(superName);
if (!list.contains(name)) {
list.add(name);
Collections.sort(list); // SortedList
}
} else {
subClazzEntries.put(superName, new ArrayList<String>(Arrays.asList(name)));
}
traverseClasses(superClazz, level + 1);
}
}
Edit 2:
Now I implemented the Stack
solution from Uri Agassi in my code.
This is much more cleaner, since I don't have to care about the Stack size and the current level. It just fits the requirement!
Furthermore I got rid of the TreeMap
since I don't really need a sorted map. Therefore Map
(HashMap
) remains.
Third approach:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.swing.JDialog;
import jdk.nashorn.internal.runtime.PropertyMap; // since Java 8, just for testing
import com.sun.javafx.collections.TrackableObservableList;
public class PrintClassHierarchy {
private static final String PADDING = " ";
private static final String PADDING_WITH_COLUMN = " | ";
private static final String PADDING_WITH_ENTRY = " |--- ";
private static final String BASE_CLASS = Object.class.getName();
private final Map<String, List<String>> subClazzEntries = new HashMap<>();
public static void main(final String[] args) {
new PrintClassHierarchy(
PrintStream.class,
FileOutputStream.class,
FileInputStream.class,
TrackableObservableList.class,
PropertyMap.class,
JDialog.class
).printHierarchy();
}
public PrintClassHierarchy(final Class<?>... clazzes) {
// get all entries of tree
traverseClasses(clazzes);
}
public void printHierarchy() {
// print collected entries as ASCII tree
printHierarchy(BASE_CLASS, new Stack<Boolean>());
}
private void printHierarchy(final String clazzName, final Stack<Boolean> moreClassesInHierarchy) {
if (!moreClassesInHierarchy.empty()) {
for (final Boolean hasColumn : moreClassesInHierarchy.subList(0, moreClassesInHierarchy.size() - 1)) {
System.out.print(hasColumn.booleanValue() ? PADDING_WITH_COLUMN : PADDING);
}
}
if (!moreClassesInHierarchy.empty()) {
System.out.print(PADDING_WITH_ENTRY);
}
System.out.println(clazzName);
if (subClazzEntries.containsKey(clazzName)) {
final List<String> list = subClazzEntries.get(clazzName);
for (int i = 0; i < list.size(); i++) {
// if there is another class that comes beneath the next class, flag this level
moreClassesInHierarchy.push(new Boolean(i < list.size() - 1));
printHierarchy(list.get(i), moreClassesInHierarchy);
moreClassesInHierarchy.removeElementAt(moreClassesInHierarchy.size() - 1);
}
}
}
private void traverseClasses(final Class<?>... clazzes) {
// do the traverseClasses on each provided class (possible since Java 8)
Arrays.asList(clazzes).forEach(c -> traverseClasses(c, 0));
}
private void traverseClasses(final Class<?> clazz, final int level) {
final Class<?> superClazz = clazz.getSuperclass();
if (superClazz == null) {
// we arrived java.lang.Object
return;
}
final String name = clazz.getName();
final String superName = superClazz.getName();
if (subClazzEntries.containsKey(superName)) {
final List<String> list = subClazzEntries.get(superName);
if (!list.contains(name)) {
list.add(name);
Collections.sort(list); // SortedList
}
} else {
subClazzEntries.put(superName, new ArrayList<String>(Arrays.asList(name)));
}
traverseClasses(superClazz, level + 1);
}
}
entries
andmoreToCome
are private instance variable which is looking fine to me since they look as being use in every method of the class. If they were global that would be a really big problem ;) . \$\endgroup\$