Thursday, January 26, 2012

Representing Java classes in diagrams

You probably wanted to display relationships from the Java classes in diagram if you needed to understand how the projects and files are related to each other. Eclipse can show you Call and Type hierarchy, but seeing it all in one graph can help you understand how project is structured. I have created the diagram for the docx4j using small application (using Apache BCEL 5.2) to create it (to import it I converted CSV file to XLSX and used yEd):


Application code that is creating CVS is this:
package ca.sapiens.main;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.bcel.classfile.ClassParser;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.JavaClass;

// TODO: Auto-generated Javadoc
/**
 * The Class MainApp.
 */
public class MainApp {

 /** The result. */
 private static Map<String, Set<String>> result = new HashMap<String, Set<String>>();

 /** The class files. */
 private static Set<File> classFiles = new HashSet<File>();

 /**
  * The main method.
  * 
  * @param args
  *            the arguments
  * @throws IOException
  *             Signals that an I/O exception has occurred.
  * @throws ClassNotFoundException
  *             the class not found exception
  */
 public static void main(String[] args) throws IOException, ClassNotFoundException {
  List<String> regexFilters = new ArrayList<String>();
  regexFilters.add("^java.*");
  regexFilters.add("^org.apache.*");

  List<String> projects = new ArrayList<String>();
  projects.add("c:\\workspace-rapidox\\docx4j-sapiens-2.7.1\\target\\classes\\");

  System.out.println("Starting with file processing");

  int filesCount = 0;

  for (String directory : projects) {
   classFiles.clear();
   System.out.println("Getting class files");
   getAllClassFiles(new File(directory));
   for (File file : classFiles) {
    filesCount++;
    System.out.println("Processing file " + file.getName());
    ClassParser parser = new ClassParser(file.getPath());
    JavaClass clazz = parser.parse();
    int index = clazz.getClassName().indexOf("$");
    if (index == -1)
     index = clazz.getClassName().length();
    String clazzName = clazz.getClassName().substring(0, index);
    if (result.get(clazzName) == null) {
     result.put(clazzName, new HashSet<String>());
    }

    for (Constant constant : clazz.getConstantPool().getConstantPool()) {
     if (constant != null) {
      if (constant instanceof ConstantClass) {
       ConstantUtf8 constantUtf8 = (ConstantUtf8) clazz.getConstantPool().getConstant(
         ((ConstantClass) constant).getNameIndex());
       String name = constantUtf8.getBytes().replaceAll("/", ".");
       // remove inner classes and enumerations
       int index2 = name.indexOf("$");
       if (index2 == -1)
        index2 = name.length();
       // remove arrays indicators
       String correctedName = name.substring(0, index2).replaceFirst("\\[\\w", "")
         .replaceAll("\\;", "");
       boolean pass = true;
       for (String regex : regexFilters) {
        if (Pattern.matches(regex, correctedName)) {
         pass = false;
         System.out.println("Removing " + correctedName);
        }
       }

       if (pass && !clazzName.equalsIgnoreCase(correctedName))
        result.get(clazzName).add(correctedName);
      }
     }
    }
   }
  }

  System.out.println("Writing data...");
  if (result.size() > 0) {
   writeResult("c:\\temp\\output.csv");
  }

  System.out.println("Finished with file processing. Processed " + filesCount + " files");
 }

 /**
  * Gets the all class files.
  * 
  * @param processingFile
  *            the processing file
  * @return the all class files
  */
 private static void getAllClassFiles(File processingFile) {
  if (processingFile.isDirectory()) {
   File[] files = processingFile.listFiles();
   for (File file : files) {
    getAllClassFiles(file);
   }
  } else if (processingFile.isFile() && processingFile.getName().endsWith(".class")) {
   classFiles.add(processingFile);
  }
 }

 /**
  * Write result.
  * 
  * @param filename
  *            the filename
  * @throws IOException
  *             Signals that an I/O exception has occurred.
  */
 private static void writeResult(String filename) throws IOException {
  BufferedWriter out = new BufferedWriter(new FileWriter(filename));

  out.write("Name,Link\n");
  for (Entry<String, Set<String>> entry : result.entrySet()) {
   for (String reference : entry.getValue()) {
    out.write(entry.getKey() + "," + reference + "\n");
   }
  }
  out.close();
 }
}
Once you have run this code, open file in Excel, save as XLSX and then open file in yEd. After that, choose Edge List and fill in your ranges. Import as Circular and then change Layout to Organic (I think that it is faster this way and it looks better). With graphs big as this one, some layouts will be extremely slow. What is useful in the end is that you can select neighboring nodes, and create new document out of that. That should give you a good overview of what your class is using or where it is being used.

No comments:

Post a Comment