Developing any application usually involves multiple developers. To understand what the others have coded, a developer needs to put in a lot of effort. To make it easier for the developer to understand complex project code, this article introduces a tool that builds the inheritance tree in the input Java package.
In object-oriented programming, inheritance is a mechanism by which a child class can inherit the properties of the parent class. In general, app development evolves across various stages, which are shown in Figure 1.
Code flows across most of the phases/stages shown in this figure. Various different teams tend to work on each stage. Visualising all the classes and their relations in a package such as the list of all classes, inherited classes, inner classes, etc, is a time consuming process (which may be due to a large code base). To avoid wasting the developers’ time and to improve their efficiency, there is now a tool, called the Inheritance Tree Builder, that builds a GUI of the inheritance tree when a Java package is given as the input.
The stages involved in the building of the tool are:
- Listing out all the classes in a package
- Listing out all inner class-outer class relations in a package
- Listing out inheritance relations in a package
- Visualising the above listed classes, the inner classes and the inherited classes in the form of a tree structure
To build the tool, the Java parser is used to get information about the input source code.
What is a Java parser?
A parser takes a sequence of tokens or program instructions as input and builds a tree-like data structure called the Abstract Syntax Tree (AST).
When we compile code, the input source code is first passed to the lexer, which outputs a list of tokens (a token is the smallest meaningful element of the program). This is passed as an input to the Syntactic Analysis stage (parser), which builds the AST. An AST is a hierarchical tree-like data structure that represents the syntax of the program. The stages involved in parsing can be viewed in Figure 2.
How to list all the classes in the input package
The Java parser first parses through each and every file in the input Java package. The next step is to filter the Java files from the list of files. From each of the Java files, we get the tokens and then build the AST.
In the AST, an adapter called the VoidVisitorAdapter is used to list all classes in a file. To learn how to list the inner classes in a file, go to https://tomassetti.me/getting-started-with-javaparser-analyzing-java-code-programmatically/.
A code snippet for listing the classes is given below.
1. private void listClasses(File projectDir) { 2. new DirExplorer((level, path, file) -> path.endsWith(“. java”), (level, path, file) -> { 3. try { 4. new VoidVisitorAdapter<Object>() { 5. @Override 6. public void visit(ClassOrInterfaceDeclaration n, Object arg) { 7. super.visit(n, arg); 8. s.add(n.getName()); 9. try{ 10. Class c = Class.forName(n.getName()); 11. System.out.println(c.getSuperclass()); 12. } 13. catch (Exception e){ 14. } 15. } 16. }.visit(JavaParser.parse(file), null); 17. } catch (ParseException | IOException e) { 18. new RuntimeException(e); 19. } 20. }).explore(projectDir); 21. }
How to list the inner and outer classes in the input package
When given the package as input, the Java parser parses through each file. It builds the AST for each file, which is a CompilationUnit, and this represents all the contents of the file. The code:
CompilationUnit cu = JavaParser.parse(file_name)
…builds the AST for a file. This code is available as an open source project. The code of the input package can be found at the GitHub link https://github.com/javaparser/javasymbolsolver. The code snippet is given below.
1. void Find_InnerClasses(String Directory){ 2. File projectDir = new File(Directory); 3. copyArrayList(ListFiles.listClasses(projectDir)); 4. CompilationUnit cu; 5. for(int i =0; i< files_list.size(); i++){ 6. try (FileInputStream in = new FileInputStream(files_list.get(i))) { 7. cu = JavaParser.parse(in); 8. List<TypeDeclaration> types = cu.getTypes(); 9. ArrayList<String> parents = new ArrayList<>(); 10. for (TypeDeclaration typeDeclaration : types) { 11. printTypes(typeDeclaration, parents); 12. } 13. } 14. catch(Exception e){ 15. System.out.println(e); 16. } 17. new Build_Innerclassestree(s).TreeExample(); 18. s.clear(); 19. } 20. }
How to list the inheritance relations in the package
To list the inheritance relations between the classes in a given Java package, parse through each and every file in the package. By the end of this step, you will get the list of all the words in that file. Then search for the extends keyword (which is the keyword for inheritance). Repeat this process for all the files in a given package. The above results are stored in the form of a HashMap, where the key represents a parent class or interface (or even an abstract class) and the value represents either a class, abstract classes or interfaces that inherit the parent class.
This tool supports single inheritance, multi-level inheritance and also hierarchical inheritance.
The code for listing the inheritance relation among classes is given below:
1. public void getHashMap(){ 2. int flag =0; 3. for(int i=0 ; i<list.size();i++){ 4. if((list.get(i).equals(“extends”)) && i >= 2) { 5. if((!find_keyword(list.get(i-1))) && (find_keywords(list.get(i-2))) && (i <= list.size()-2) && (!find_keyword(list.get(i+1))) && (!find_bracket(list.get(i+1).charAt(0)))){ 6. String s1 = return_substring(list.get(i+1)); 7. if(list.get(i-2).contains(“class”)){ 8. if(i>=3){ 9. if(list.get(i-3).contains(“abstract”)){ 10. flag =0; 11. } 12. else if(list.get(i-3).contains(“interface”)) { flag = 1;} 13. else if(list.get(i-3).contains(“class”)) {flag = 1;} 14. } 15. if(flag ==0) { 16. s.add(list.get(i-1)+ “->” + s1); 17. ArrayList<String> base = hm.get(s1); 18. if(base==null){ 19. base = new ArrayList<String>(); 20. } 21. base.add(list.get(i-1)); 22. hm.put(s1,base); 23. } 24. } 25. else if(list.get(i-2).contains(“interface”)){ 26. if(i>=3){ 27. if(list.get(i-3).contains(“interface”)){flag =1;} 28. if(list.get(i-3).contains(“class”)){flag =1;} 29. if(list.get(i-3).contains(“abstract”)) {flag = 1;} 30. } 31. if(flag ==0) { 32. s.add(list.get(i-1)+ “->” + s1); 33. ArrayList<String> base = hm.get(s1); 34. if(base==null){ 35. base = new ArrayList<String>(); 36. } 37. base.add(list.get(i-1)); 38. hm.put(s1,base); 39. } 40. } 41. } 42. } 43. } 44. }
How to visualise the inner, inherited classes
The JTree library is used for building the tree structure. JTree is a swing component that is generally used to build hierarchical tree-like data structures. It has a root node to which we add the mutable nodes.
How to visualise all the classes
To visualise inner classes, store their relations in an ArrayList. If Class A has inner classes, say B and C; and B in turn has an inner class, say D; then the contents in the ArrayList will be of the form: A, A.B, A.B.D, A.C. To visualise the inheritance relations, store them in a HashMap with the key as the parent class and the value as all the base classes (classes that inherit the parent class directly)—for example, if Class A is extended by Class B and Class C. And also if Class B is extended by Class D.
The HashMap data structure representation to store inheritance relations is as follows:
A → <B, C> B → <D>
The user interface of the application is divided into three panels. The first panel represents the tree structure of the names of the classes in the package. The second panel represents the inner classes in the package (a class is present in the inner classes tree if it has any inner classes). The code snippet for this is given below:
1. public JTree TreeExample(){ 2. Change_List(); 3. s1.clear(); 4. for(int i =0; i< s.size();i++){ 5. s1.add(new DefaultMutableTreeNode(s.get(i))); 6. if(find_dots(s.get(i)) ==0){ 7. inner.add(s1.get(i)); 8. } 9. } 10. for(int i =0; i< s1.size(); i++){ 11. String str = s1.get(i).getUserObject().toString(); 12. int len = str.length(); 13. int count = find_dots(str); 14. for(int j =i+1 ; j< s1.size() ; j++){ 15. String str1 = s1.get(j).getUserObject().toString(); 16. int count1 = find_dots(str1); 17. if((count1 == count+1) && check_substring(str1, str)){ 18. s1.get(i).add(s1.get(j)); 19. } 20. } 21. } 22. add(tree1); 23. return tree1; 24.}
The third panel represents the inheritance relationships between classes, if there are any. The code snippet for this is given below:
1. public JTree TreeBuild() { 2. Set<String> keys = hm.keySet(); 3. // iterate through the key set and display key and values 4. for (String key : keys) { 5. DefaultMutableTreeNode Node1 = new DefaultMutableTreeNode(key); 6. ArrayList<String> s1 = hm.get(key); 7. DefaultMutableTreeNode Node2 = null; 8. for(String node: s1){ 9. Node2 = new DefaultMutableTreeNode(node); 10. Node1.add(Node2); 11. } 12. root.add(Node1); 13. } 14. add(tree1); 15. return tree1; 16.}
We have created a GitHub public repository for the source code for the inheritance tree builder. Interested developers can download this from https://github.com/EpuriLaxmiNiharika/Inheritance-Tree-builder_for-java-codes.
Given javasymbolsolver as the input package to the inheritance tree builder, the output is as shown in Figure 3.
To summarise, Inheritance Tree Builder is a tool that visualises all the classes, inner classes and inheritance relations among classes in the form of a tree structure that provides developers with a quick understanding of the Java input package. This tool can be enhanced by adding hyperlinks on each node of the tree such that, on clicking the node, it navigates to the respective part of the code.