Academic Integrity: tutoring, explanations, and feedback — we don’t complete graded work or submit on a student’s behalf.

Design Analyzer The Design Analyzer (DA) computes and displays several design me

ID: 3691761 • Letter: D

Question

Design Analyzer

The Design Analyzer (DA) computes and displays several design metrics for all of the classes in some user specified package.

The DA Metrics

For every class, A, in the specified package, the design analyzer computes and displays the following design metrics:

instability(A)
responsibility(A)
inDepth(A)
workLoad(A)

Where:

instability(A) = #providers(A)/#P
responsibility(A) = #clients(A)/#P

However, DA defines class X to be a client of class Y if the interface of X references Y. The interface of a class is the class without its method implementations. In other words, X references Y if X contains a field, parameter, or return type of type Y. This means that we don't need to consider local variables or implicit references inside of X.

The inheritance depth of class A, inDepth(A), is the number of superclasses of A in the package. Recall that B is a superclass of A if A is a subclass of B. Also recall that A is a subclass of itself. A class with a relatively large inheritance depth is difficult to understand, use, and replace.

The workload of a class is the number of methods in the class (scope doesn't matter) divided by the number of methods in the package. Generally speaking, all classes should have about the same workload.

Example

DA assumes the parent directory of the user-specified package is on the classpath and that the user has specified the path to the package at the command prompt.

For example, assume the demo package is located in the directory C:projects. Also assume that C:projects is on the classpath. Here is how the design analyzer is invoked on the demo package:

or (in Mac/Unix)

In this example the demo package consists of seven classes, A - F and Demo. Check Demo.java under Files -> SampleCode -> Design Analyzer.

Here's a class diagram of the demo package:

Note that the dashed dependency arrows are being used to indicate the types of the method parameters.

Also note that in the demo package there are 7 classes and 14 methods,

The output of DA is:

C inDepth(C) instability(C) responsibility(C)   workload(C)

A 1    0 .28   .14
B 2 .14 .28 .14
C    3 .14   0 .28
D 1   .14 .28 .07
E    1 .28   .14   .14
F 1   .28 0 .14
Demo 1 0   0   .07

Note that decimals are rounded off to two places. (UseSystem.out.printf for this.)

Design and Implementation

The design of DA is pretty simple:

Note that there are separate methods for computing each metric, for displaying the results, and for loading the package from the pathname. (Some methods are shown to be private, but should be public.)

DA depends heavily on Java reflection.

The package is a set of class objects:

Set<Class> package;

This set must be initialized and loaded by the loadPackage method:

The main method is simple:

DA da = new DA();
da.loadPackage(args[0]);
da.displayMetrics();

Explanation / Answer

DA.java
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;

public class DA {
private Set<Class> classes = null;
private double packageClassCount = 0;
private double packageMethodCount = 0;

public static void main(String[] args) throws IOException {
    DA fl = new DA();
    if (args.length != 1) {
      System.out.println("Usage:java DA <path>");
    }
    fl.loadPackage(args[0]);
    fl.displayMetrics();
}

/**
   * loads package at given path
   *
   * @param path string representing a path from CLASSPATH
   */
public void loadPackage(String path) {
    String[] split = path.split("/");
    if (split.length != 2) {
      return;
    }
    String dir = split[0];
    String packageName = split[1];

    classes = new HashSet<>();
    File packagePath = new File(dir);//parent dir of package
    try {
      FilenameFilter classFilter;
      classFilter = new FilenameFilter() {
        public boolean accept(File dir, String name) {
          return name.toLowerCase().endsWith(".class");
        }
      };

      URL url = packagePath.toURI().toURL();//get URL object from file
      ClassLoader cl = new URLClassLoader(new URL[]{url});// Wrapping url

      for (File file : (new File(path)).listFiles(classFilter)) {
        Class c = cl.loadClass(packageName + "." + file.getName().replaceAll("\.class", ""));// remove '.class'
        packageMethodCount += c.getDeclaredMethods().length;
        classes.add(c);
      }
      packageClassCount = classes.size();
    } catch (MalformedURLException | ClassNotFoundException | NullPointerException e) {
      e.printStackTrace();
    }
}

/**
   * Depth is the number of super classes in the package for
   * a given class. A class is a subclass of itself, therefore
   * it also is a superclass of itself.
   *
   * @param cl class to calculate depth
   * @return the number of super classes in the package
   */
public int inDepth(Class cl) {
    int count = 1;
    Class parent = cl.getSuperclass();
    while (!parent.getName().equals("java.lang.Object")) {
      count++;
      parent = parent.getSuperclass();
    }
    return count;
}

/**
   * responsibility(A) = #clients(A)/#P
   * #client(A): number of classes that reference A
   * #p: number of classes in package
   *
   * @param a the class that is being referenced
   * @return ratio of #c(A)/#p
   */
public double responsiblity(Class a) {
    int count = 0;
    for (Class aClass : classes) {
      if (aClass.getSuperclass().getName().equals(a.getName())) {// This if statement must go first.
        count++;
      } else if (!aClass.getName().equals(a.getName())) {
        if (doesLeftClassReferenceRightClass(aClass, a)) {
          count++;
        }
      }
    }

    return Math.floor(100 * count / packageClassCount) / 100;
}

/**
   * instability(A) = #providers(A)/#P
   * #provider(A): number of classes that A references
   * #p: number of classes in the package
   *
   * @param a class that is doing the referencing
   * @return ratio of #p(A)/#p
   */
public double instability(Class a) {
    int count = 0;
    if (!a.getSuperclass().getName().equals("java.lang.Object")) {
      count++;
    }
    for (Class aClass : classes) {
      if (!aClass.getName().equals(a.getName())) {
        if (doesLeftClassReferenceRightClass(a, aClass)) {
          count++;
        }
      }
    }

    return Math.floor(100 * count / packageClassCount) / 100;
}

/**
   * helper method to responsibility(Class) and instability(Class)
   * This method does not check if there is a extends relationship
   * since both methods must deal with that in different manners.
   *
   * @param left class who is doing referencing
   * @param right class that is being potentially referenced
   * @return boolean representing the answer to the method name
   */
private boolean doesLeftClassReferenceRightClass(Class left, Class right) {
    Method[] methods = left.getDeclaredMethods();
    Field[] fields = left.getDeclaredFields();
    for (Field field : fields) {
      if (field.getType().getName().equals(right.getName())) {
        return true;
      }
    }
    for (Method method : methods) {
      if (method.getReturnType().getName().equals(right.getName())) {
        return true;
      }
      for (Class c : method.getParameterTypes()) {
        if (c.getName().equals(right.getName())) {
          return true;
        }
      }
    }
    return false;
}

/**
   * Workload is the ratio of # of methods in class cl over
   * the # of methods in package.
   *
   * @param cl the Class objects who's Methods we want to count
   * @return #methods(cl)/#methods(Package)
   */
public double workload(Class cl) {
    return Math.floor(100 * cl.getDeclaredMethods().length / packageMethodCount) / 100;
}

/**
   * Prints metrics for each Class in Set. Sorts Classes in alphabetical order unless
   * the Class contains main. This Class will always be at the bottom of the list.
   * <p/>
   * Comparator logic: if Class o didn't throw NoSuchMethodException it contains main
   * therefore consider o larger and vice versa. Otherwise compare using
   * simpleName(name without package info).
   **/
public void displayMetrics() {
    System.out.printf("%-20s%-20s%-20s%-20s%-20s%n",
        "C", "inDepth(C)", "instability(C)", "responsibility(C)", "workload(C)");
    List<Class> list = new ArrayList<>(classes);
    Collections.sort(list, new Comparator<Class>() {//sorts by name, moves Class with main to the bottom.
      @Override
      @SuppressWarnings("unchecked")
      public int compare(Class o, Class t1) {
        Class[] parameterType = new Class[]{(new String[]{}).getClass()};//String[] type parameter for main
        try {// if o contains main consider o larger than t1
          o.getDeclaredMethod("main", parameterType);
          return 1;
        } catch (NoSuchMethodException e) {/* do nothing */}
        try {// if t1 contains main consider t1 larger than o
          t1.getDeclaredMethod("main", parameterType);
          return -1;
        } catch (NoSuchMethodException e) {/* do nothing */}
        return o.getSimpleName().compareTo(t1.getSimpleName());//simpleName comparison
      }
    });

    for (Class c : list) {
      System.out.printf("%-20s%-20d%-20.2f%-20.2f%-20.2f%n",
          c.getSimpleName(), inDepth(c), instability(c), responsiblity(c), workload(c));
    }
}
}