The Dependency Finder User Manual

by Jean Tessier


Table of Contents


Introduction

This application extracts information from compiled Java code and makes it available to users in a numbers of ways.

Many authors have stressed the importance of managing depencencies (Robert C. Martin, John Lakos). Managing dependencies means securing encapsulation and making sure that the code follows the architecture. It is also essential for the modularization of code and favors reuse. By extracting the information from the code itself, we can detect where we were sloppy, where we took shortcuts that we shouldn't have. Managing the dependencies can shorten the compilation time of the code.

This application also includes tools for computing metrics based on information extracted from compiled Java code. These measurements can be helpful to guide development and can point out areas where the code needs improvement.

Finally, this application includes a tool that computes the changes in the API of a system between successive versions. If you use third-party libraries, you can use it to verity that the parts you are dependending upon have not changed. If you provide libraries to others, you can use it to assertain the impact changes might have on your customers. You can even use it to monitor the evolution of your own software.

History Behind Dependency Finder

This project started because another project of mine became so complex that I couldn't track dependencies in my head anymore. I looked for a tool to help me and the only ones I could find were very expensive. So I resolved to build my own tool.

I haven't touched that other project since I started working on Dependency Finder.

At first, I thought of using the Reflection API, but it shows only the class's attributes and method signatures. If a method uses other classes as part of its processing, the Reflection API will not show that dependency.

Java compilers take source code and produce bytecode that they put in .class files. The format of these files is described in The class File Format. I undertook to parse these files and extract the information I needed directly from the compiled code.

Once I had access to the code structure, I got started on tools to report interesting information about it. I started with dependencies and quickly moved up to metrics and API differences. At the beginning, the output was only to standard output or a file. Quickly, I added XSL stylesheets so it would look a little better. Eventually, I added Swing application to keep the data in memory and play with it a while.


Installation

Java Application on Windows

Dependency Finder comes as a ZIP file that includes everything you need to run it.

Step 1: Java Virtual Machine

You need to have a Java Virtual Machine installed. You can get one for free from Sun Microsytems. Make sure you define the JAVA_HOME environment variable.

For example, if you installed JDK 1.3 in C:\jdk1.3:

    SET JAVA_HOME=C:\jdk1.3

You can either add this line to your AUTOEXEC.BAT file, or on WinNT use Start | Settings | Control Panel | System and select the Environment tab to create and edit variables.

Step 2: Deploy Dependency Finder

Extract the ZIP file to some directory you like. The extraction will create a subdirectory named "DependencyFinder" that is the root of the package.

For example, if you extract it to C:\, you will endup with a directory called C:\DependencyFinder\.

Step 3: Environment Variables

This is only required on Win95/98. Under WinNT, the scripts are smart enough to find out where you installed Dependency Finder on their own.

Create an environment variable named DEPENDENCYFINDER_HOME and have it point to the directory where you installed Dependency Finder.

For example, if you extracted the ZIP file in C:\

    SET DEPENDENCYFINDER_HOME=C:\DependencyFinder

You can either enter this line in your AUTOEXEC.BAT file, or on WinNT use Start | Settings | Control Panel | System and select the Environment tab to create and edit variables.

Step 4: PATH

Add the "bin" subdirectory to your PATH variable.

For example, if you extracted the ZIP file in C:\

    SET PATH=C:\DependencyFinder\bin;%PATH%

Step 5: Reboot

This is only required on Win95/98.

Under Win95/98, you will need to reboot your computer for the changes to the environment to take effect.

Miscellaneous

If the VM runs out of memory, try setting the DEPENDENCYFINDER_OPTS environment variable.

    SET DEPENDENCYFINDER_OPTS=-Xmx128m

If you get the message "Out of environment space" while running the scripts on a Windows machine, you will need to add the following line to your C:\CONFIG.SYS file and then reboot.

    shell=command.com /e:8192 /p

This reserves more space for environment variables in the DOS shell. You can type:

    C:\>command /?

for more details.

Java Application on Unix

At this time, Dependency Finder does not have shell scripts to launch any of the tools the way it does under DOS.

Step 1: Java Virtual Machine

You need to have a Java Virtual Machine installed. You can get one for free from Linux or Solaris from Sun Microsytems. Make sure you define the JAVA_HOME environment variable.

Suppose you installed JDK 1.3 in /usr/java.

For sh or bash:

    JAVA_HOME=/usr/java
    export JAVA_HOME

For csh or tcsh:

    setenv JAVA_HOME /usr/java

You can put this line in your .cshrc file.

Step 2: Deploy Dependency Finder

Extract the ZIP file to some directory you like. The extraction will create a subdirectory named "DependencyFinder" that is the root of the package.

    $ unzip DependencyFinder.zip

For example, if you extract it to $HOME, you will endup with a directory called $HOME/DependencyFinder/.

Step 3: Environment Variables

Create an environment variable named DEPENDENCYFINDER_HOME and have it point to the directory where you installed Dependency Finder.

If the VM runs out of memory, try modifying the DEPENDENCYFINDER_OPTS environment variable to allocate more memory to the JVM.

Suppose you extracted the ZIP file in $HOME.

For sh or bash:

    DEPENDENCYFINDER_HOME=$HOME/DependencyFinder
    export DEPENDENCYFINDER_HOME
    DEPENDENCYFINDER_OPTS=-Xmx128m
    export DEPENDENCYFINDER_OPTS

For csh or tcsh:

    setenv DEPENDENCYFINDER_HOME $HOME/DependencyFinder
    setenv DEPENDENCYFINDER_OPTS -Xmx128m

You can put these lines in your .cshrc file.

Step 4: PATH

Add the directory where you put the Unix scripts to your PATH variable.

Suppose you extracted the ZIP file in $HOME.

For sh or bash:

    PATH=$DEPENDENCYFINDER_HOME\bin;$PATH
    export PATH

For csh or tcsh:

    set path = ($DEPENDENCYFINDER_HOME\bin $path)

You can put this line in your .cshrc file.

Web Application

Dependency Finder also comes as a web application that you can easily deploy in just about any J2EE servlet container.

Step 1: Deploy WAR File

Use your favorite servlet container to deploy the WAR file. Most of them have tools for deploying WAR files. For some of them, it can be as simple as deploying the WAR file to a specific directory.

For instance, under Tomcat on Windows:

    C:\>%CATALINA_HOME%\bin\shutdown
    C:\>copy DependencyFinder.war %CATALINA_HOME%\webapps
    C:\>%CATALINA_HOME%\bin\startup

Step 2: Configure web.xml file

Next, you need to tell the wep application where to find the Java classes to extract dependencies from. This is done via the web.xml file in the WEB-INF\ directory. Some application servers expose the content of this file through some form of user interface. Under Tomcat, you have to edit the file yourself using a text editor.

There are three application variable to adjust.

name Used in the JSPs to tell the users what that are looking at. Put the name of the application that you are extracting from here, it will be shown in titles by the JSPs.
source Use this variable to tell Dependency Finder where to locate classes find which to extract a dependency graph. You can separate multiple locations by using commas. Each location can either be a JAR file, a ZIP file, a .class file, or a directory that will be recursively searched for .class files.
mode Optional. Tells Dependency Finder whether to minimize or maximize the resulting graph, or just leave it alone (raw, the default value). See below for the implications of minimizing or maximizing a graph.

Under some servlet containers, for example Tomcat, you will need to restart the servlet container for the changes to take effect.

    C:\>%CATALINA_HOME%\bin\shutdown
    C:\>%CATALINA_HOME%\bin\startup

Or you can you the deployment tool in Tomcat Manager to install the web application.


Running Dependency Finder

Once you have installed Dependency Finder, you are ready to use all the tools now at your disposal. Almost all of them are Java applications that you launch using a batch file or shell script.

At the beginning, the toolset was just a group of batch files that offered a basic command-line interface (CLI). Later, I added a Swing graphical interface (GUI) that simply replicated the controls available through the CLI. Then I build a web application that mirrored the GUI. This might help explain why the the interface looks the way it does and why it may not always be very intuitive. Finally, I added a group of Ant tasks that mirror the major functions of the CLI.

We will start with the GUI-based tools, and then talk about the CLI-based tools and end with the Ant tasks.

Equivalence Table

CLI Ant GUI Web
ClassDump n/a n/a n/a
ClassList n/a n/a n/a
ClassMetrics <classmetrics> n/a n/a
ClassReader n/a n/a n/a
ClosureToText <xslt style="${dependencyfinder.home}/etc/ClosureToText.xsl"> n/a n/a
DependablesToHTML <xslt style="${dependencyfinder.home}/etc/DependablesToHTML.xsl"> n/a n/a
DependablesToText <xslt style="${dependencyfinder.home}/etc/DependablesToText.xsl"> n/a n/a
DependencyClosure <dependencyclosure> DependencyFinder >> File >> Closure closure.jsp & advancedclosure.jsp
DependencyExtractor <dependencyextractor> DependencyFinder >> File >> Extract extract.jsp
DependencyFinder n/a n/a n/a
DependencyGraphToHTML <xslt style="${dependencyfinder.home}/etc/DependencyGraphToHTML.xsl"> n/a n/a
DependencyGraphToText <xslt style="${dependencyfinder.home}/etc/DependencyGraphToText.xsl"> n/a n/a
DependencyMetrics <dependencymetrics> DependencyFinder >> File >> Metrics n/a
DependencyReporter <dependencyreporter> DependencyFinder >> File >> Dependency query.jsp & advancedquery.jsp
DependentsToHTML <xslt style="${dependencyfinder.home}/etc/DependentsToHTML.xsl"> n/a n/a
DependentsToText <xslt style="${dependencyfinder.home}/etc/DependentsToText.xsl"> n/a n/a
DiffToHTML <xslt style="${dependencyfinder.home}/etc/DiffToHTML.xsl"> n/a n/a
HideOutboundDependencyGraphToHTML <xslt style="${dependencyfinder.home}/etc/HideOutboundDependencyGraphToHTML.xsl"> n/a n/a
HideOutboundDependencyGraphToText <xslt style="${dependencyfinder.home}/etc/HideOutboundDependencyGraphToText.xsl"> n/a n/a
HideInboundDependencyGraphToHTML <xslt style="${dependencyfinder.home}/etc/HideInboundDependencyGraphToHTML.xsl"> n/a n/a
HideInboundDependencyGraphToText <xslt style="${dependencyfinder.home}/etc/HideInboundDependencyGraphToText.xsl"> n/a n/a
JarJarDiff <jarjardiff> n/a n/a
ListDiff <listdiff> n/a n/a
ListDiffToHTML <xslt style="${dependencyfinder.home}/etc/ListDiffToHTML.xsl"> n/a n/a
ListDiffToText <xslt style="${dependencyfinder.home}/etc/ListDiffToText.xsl"> n/a n/a
ListDocumentedElements
<javadoc>
    <doclet name="com.jeantessier.diff.ListDocumentedElements"
	    path="${classesDir}">
	<param name="-tag" value="level"/>
	<param name="-valid" value="published"/>
	<param name="-out" value="documented_elements.txt"/>
    </doclet>
</javadoc>
n/a n/a
ListInheritanceDiffToText <xslt style="${dependencyfinder.home}/etc/ListInheritanceDiffToText.xsl"> n/a n/a
ListUnused <xslt style="${dependencyfinder.home}/etc/ListUnused.xsl"> n/a n/a
OOMetrics <oometrics> OOMetricsGUI n/a
PublishedDiffToHTML <xslt style="${dependencyfinder.home}/etc/PublishedDiffToHTML.xsl"> n/a n/a
XSLTProcess <xslt> n/a n/a
c2c <dependencyreporter c2c="yes"> DependencyFinder >> File >> Dependency query.jsp & advancedquery.jsp
c2p <dependencyreporter c2p="yes"> DependencyFinder >> File >> Dependency query.jsp & advancedquery.jsp
f2f <dependencyreporter f2f="yes"> DependencyFinder >> File >> Dependency query.jsp & advancedquery.jsp
p2p <dependencyreporter p2p="yes"> DependencyFinder >> File >> Dependency query.jsp & advancedquery.jsp

GUI

The main tool is DependencyFinder. It extracts dependencies from compiled code, maintains the dependency graph in memory, and provides an interface for querying the graph and display the results.


DependencyFinder

The other tool computes metrics on a given codebase. It displays results in a table with sortable columns. Different tabs control the level of detail: package, class, or method.


OOMetricsGUI

CLI


Document Flow

All the tools support -help command-line switch, which prints the list of valid command-line switches for that particular tool. Many of the tools also support some or all of the following four command-line switches:

-in Many of the CLI tools are automated translations of XML data into either HTML or plain text. The scripts have hard-coded XSL stylesheets. You specify the XML input with this switch.
-out filename Writes the output of the command to filename. Most commands default to the standard output if you do not use -out.
-time Prints the time it took to run the command. If you run commands against a large codebase or dependency graph, it can take a substantial amount of time before it returns. This information can help you plan lenghty operations in the future.
-verbose [filename] Writes summary processing information to filename. If you do not specify a file, the tool will write the information to the standard output stream.

Ant

You can run the functions of the CLI as part of your build script if you are using the Ant build tool from the Apache Foundation. There is a special help page on how to use Dependency Finder with Ant.


Class Files

Dependency Finder uses specialized classes to parse .class files. It also includes some tools for looking at the contents of these files.

ClassReader

Use this tool to put the class in a .class file in human-readable form. With the -xml switch, it dumps the complete structure to an XML file that you can manipulate with your favorite XML editor.

ClassMetrics

This is a simple tally of how many classses, interfaces, methods, publics, statics, finals, etc. there are in a given codebase. These tallies are not as powerful as the metrics of OOMetrics, but they can give you a rough idea of the size and complexity of a piece of software.


Dependencies

Dependencies occur when one class uses the services of another class. For example, this can happen when a class inherits from another, has an attribute whose type is of another class, or when one of its methods calls a method on an object of another class.

Knowing about dependencies is useful for programmers when deciding the impact of a change. When you're about to rename a feature or class, it is useful to know all the places in the codebase where it is being used.

This is also useful for code reviewers and architects when assessing the coupling within an application or library.

Dependency Finder builds dependency graphs based on the information in class files.

So, a dependency is when the functioning of one element A requires the presence of another element B. We say that A depends on B and we write it:

    A --> B

We say that A has an outbound dependency while B has an inbound dependency. It is the same dependency, but whether it is inbound or outbound is relative to how you look at it. We also say that A is a dependent and B is dependable.

A dependency graph comprises nodes for software artifacts linked together by two types of relationships. Artifacts are packages, classes, and features. We use the term feature to designate class attributes, constructors, and methods; we will be treating them the same from here on. For the purpose of analyzing dependencies, we do not distinguish between different types of features, whether they are constructors or regular methods, and regardless of the feature's characteristics, such as being marked as final or static.

Packages have classes, which themselves have features; this is called composition. Classes refer to each other and so do features; this is called a dependency.

So a feature node is linked to its class node through composition. The class node is also linked to its package node through composition. Each is linked to various other nodes of various type using dependency links.

Extracting

Going through class files and collecting dependencies is called extracting. The application keeps dependencies in a graph. It begins with one node per Java package. To this it adds one node per Java class (including inner classes), and to those it adds one node per feature.

Dependency Finder can read all types of compiled Java: JAR files, ZIP files, or class files. You can point it to a JAR, ZIP, or .class file. You can also point it to a directory and it will explore that directory and all its subdirectories for files ending in .class. You can select more than one target, mix and match JARs and ZIPs and class files and directories, and Dependency Finder will aggregate all their contents together. It will sort them all out.

The dependencies are gathered by the class com.jeantessier.dependency.CodeDependencyCollector. It traverses the structure of a .class file, as read by com.jeantessier.classreader.Classfile, and collects explicit dependencies in the bytecode.

The dependency collector can detect three kinds of explicit dependencies, from which Dependency Finder can infer all implicit dependencies:

  1. feature to feature
  2. feature to class
  3. class to class

Explicit and Implicit Dependencies

There are explicit dependencies, as present in the code itself. There are also implicit dependencies derived from these explicit ones. A dependency between two classes in two separate packages implies a dependency between the packages.

Limitations

Dependency Finder analyzes compiled Java code, and as a result it is at the mercy of the information produced by the compiler. Some of the following limitations are the result of using Sun's compiler that comes with the JDK. But I will start with one limitation that applies regardless of the compiler.

Dependencies on Self

If we look closely at this model, we see that recursive method calls and certain types of recursive structures can generate dependencies between a programming elemend (i.e., a class or a method) and itself. Or, if a method has a parameter or local variable whose type is the enclosing class itself, or if an attribute's type is its enclosing class, this introduce a dependency from the feature to the class of which it is a part of.


Dependencies on Self

Dependency Finder does not keep track of dependencies on self. Dependency analysis is useful for two things:

  1. Compile-time dependencies
  2. Runtime dependencies

You use compile-time dependency, or change impact analysis, to answer questions such as "If I change X, what else might I have to change too?" Dependencies on self are not all that useful here. If I am changing X, I don't need to be told that X will be impacted. Likewise, if I am making changes to features of a class, I don't need to be told that the class will be impacted; I am already editing it. I do, however, need to know what other features in this class might be impacted.

You use runtime dependency analysis to answer questions such as "For class X to run, what other classes do I need?" The class as a whole is the unit of loading. If a feature needs its class, the class is forcibly already loaded otherwise the JVM would never had gotten to the feature to begin with. Likewise, if a class needs itself, well it is already there.

So Dependency Finder does not keep track of dependencies on self. This includes implicit dependencies from a class or feature to its package, or from a package to itself.

Constants

It is often the case that Dependency Finder cannot track dependencies on simple static final constants.

In Java, you can define constants in classes and interfaces by marking them as static and final. Subclasses or implementations cannot override the constants because they are static. No one can change the value because it is final. As a result, the compiler can use local optimizations to speed up execution. When these constants are of primitive types or String, the compiler can inline the value directly in the client class, so that it makes no reference at all to the class that owns the constant.

Under these circumstances, Dependency Finder cannot track dependencies on simple constants that are referenced from outside their class. If the constant are of some object type other than String, the compiler cannot perform the optimization and Dependency Finder will be able to track dependencis on them.

Local Variables

Dependency Finder does not track the type of local variables in methods.

The compiler does not need to keep track of the type of local variables. It validates the assignment and then discards type information. The virtual machine will validate the bytecode and make sure that it was not tampered with. The compiler makes sure that an assignment to a local variable is type-safe. Any method call on the variable will be made either on an interface or a class that is compatible with the type of the local variable.

Most compilers have options to retain information about local variables, such as their name and type. With javac, you have to supply the -g switch. Doing this helps symbolic debuggers give more useful context information to their users.

Given all this, it barely matters if Dependency Finder tracks dependencies on the type of local variables very closely. Assignments will to objects whose type eventually reaches the exact type of the variable, and method calls will be made on that type's set of methods. Dependency Finder loses no information by ignoring possible local variables, just a little precision. And since the virtual machine does not use that information, it does not impact runtime dependencies anyway.

Extracting With the GUI


Extract Dialog

Select File | Extract; this will popup a file dialog. Select the files and/or directories to extract from and click Extract. You can repeat this command as often as needed; each time, the extracted dependencies will be added to the current dependency graph in memory.

You can save the current dependency graph to an XML file by using File | Save. You can later reload the graph by using File | Open. Note that it is usually faster to extract the information again from class files than to load it from an XML file.

Extracting With the CLI

You use DependencyExtractor to extract dependencies. Simply string out the files and directories on the command-line. All the dependencies are merged into a single output.

E.g.,

    C:\>DependencyExtractor classes %JAVA_HOME%\JRE\lib\rt.jar

You can specify an output file with the -out switch:

    C:\>DependencyExtractor %DEPENDENCYFINDER_HOME%\lib\DependencyFinder.jar -out df.txt

You can save the graph to XML with the -xml switch:

    C:\>DependencyExtractor %DEPENDENCYFINDER_HOME%\lib\DependencyFinder.jar -xml -out df.xml

Or you can use Java serialization to save the graph:

    C:\>DependencyExtractor %DEPENDENCYFINDER_HOME%\lib\DependencyFinder.jar -serialize -out df.ser
NOTE:
At this time, Dependency Finder cannot deserialize the graph back.
Example

Here is a simple, concrete example. We start with a trivial Java class in the file test.java and compiled into test.class.

    import java.util.*;
    public class test {
        public static void main(String[] args) {
            Collection c = Collections.singleton(new Object());
        }
    }

Here is the raw output dependency graph.

    C:\>DependencyExtractor test.class
        test
            --> java.lang.Object
            main(java.lang.String[])
                --> java.lang.Object
                --> java.lang.Object.Object()
                --> java.lang.String
                --> java.util.Collections.singleton(java.lang.Object)
                --> java.util.Set
            test()
                --> java.lang.Object.Object()
    java.lang
        Object
            <-- test
            <-- test.main(java.lang.String[])
            Object()
                <-- test.main(java.lang.String[])
                <-- test.test()
        String
            <-- test.main(java.lang.String[])
    java.util
        Collections
            singleton(java.lang.Object)
                <-- test.main(java.lang.String[])
        Set
            <-- test.main(java.lang.String[])

We can save it to an XML document for use with the other tools.

    C:\>DependencyFinder -xml -out test.xml test.class

And narrow down the graph to class-to-class dependencies.

    C:\>c2c -scope-includes /test/ test.xml
        test
            --> java.lang.Object
            --> java.lang.String
            --> java.util.Collections
            --> java.util.Set

c2c is a shortcut for a set of switches to DependencyReporter. All the following commands are equivalent.

    C:\>c2c -scope-includes /test/ test.xml
    C:\>c2c -class-scope-includes /test/ test.xml
    C:\>DependencyReporter -c2c -class-scope-includes /test/ test.xml
    C:\>DependencyReporter -class-scope -class-scope-includes /test/ -class-filter test.xml

For fun, I entered manually this graph description into Poseidon for UML.


Class-to-Class Dependencies

We can narrow the graph further to class-to-package dependencies. Again, all these commands are equivalent.

    C:\>c2p -scope-includes /test/ test.xml
    C:\>c2p -class-scope-includes /test/ test.xml
    C:\>DependencyReporter -c2p -scope-includes /test/ test.xml
    C:\>DependencyReporter -class-scope -class-scope-includes /test/ -package-filter test.xml
        test
            --> java.lang
            --> java.util

Class-to-Package Dependencies

Finally, a package-to-package dependency graph. All these commands are equivalent.

    C:\>p2p test.xml
    C:\>DependencyReporter -p2p test.xml
    C:\>DependencyReporter -package-scope -package-filter test.xml
        --> java.lang
        --> java.util
    java.lang
        <-- 
    java.util
        <-- 

Package-to-Package Dependencies

Extracting With the Web Application

You use extract.jsp to generate a dependency graph for the web application. The graph will stay in memory for the lifetime of the process serving the application, or until you extract it again with the JSP. This graph can get pretty big, so make sure you allocate enough memory to the application server.

If you just call extract.jsp, it will print statistics on the current graph, if any. These include the number of nodes in the graph, how long it took to extract it, and a timestamp of when it was extracted.

If you click on the Launch button, or call extract.jsp?launch=Launch, it will start to extract a new dependency graph. The previous one will remain in use for other callers until the extraction terminates successfully. The JSP prints the name of classes as it extracts them. And at the end, it prints how many classes it read and how it it took to analyze them.

You can use the URL "extract.jsp?launch=Launch" to automatically update the graph after automated compilation runs, such as during nightly builds.

    <target name="extract" depends="init">
        <get src="${WEB_APP_URL}/extract.jsp?launch=Launch"
             dest="log.html"/>
    </target>

Extracting With Ant

You use the com.jeantessier.dependencyfinder.ant.DependencyExtractor task to extract a graph as part of your build. You need to associate the class to a tag using Ant's <taskdef> task. You also need to tell Ant where to find the Dependency Finder classes. There is a special help page on how to do this.

    <target name="extract" depends="init">
        <dependencyextractor destfile="df.xml" xml="yes">
            <path>
                <pathelement location="."/>
            </path>
        </dependencyextractor>
        <xslt style="${dependencyfinder.home}/etc/DependencyGraphToHTML.xsl"
              in="df.xml"
              out="df.html"/>
    </target>

Minimizing And Maximizing

It is often the case that an explicit dependency in the code can be implied from another explicit dependency in that code. For example, take the following code snippet:

    class A extends B {
        public void a() {
            b();
        }
    }
    class B {
        public void b() {}
    }

We can analyze the dependencies. A is a subclass of B, so there is a dependency A --> B. And A.a() calls b(), which is defined in B, yielding the dependency A.a() --> B.b(). Here is a subset of the resulting dependency graph:


Raw

Clearly, the dependency A.a() --> B.b() implies the dependency A --> B. We say that the latter is redundant; it does not add anything to the overall connectivity of the graph. Another, very frequent, example of this situation occurs with constructors.

    class A extends B {}
    class B {}

Raw

Another variation occurs when a method either calls a method on an object it received as a parameter or throws a new Exception that is part of its throws clause.

    class A {
        public void a(B b) {
            b.b();
        }
    }
    class B {
        public void b() {}
    }

Raw

We minimize the dependency graph when we remove redundant dependencies. In the first example above, we would remove the dependency A --> B. Here is what would be the resulting graph:


Minimized

We maximize the dependency graph when we expose all implicit dependencies by making them explicit. Staying with our example, A.a() --> B.b(), maximizing it would introduce these dependencies:


Maximized

Maximizing and minimizing a dependency graph deals with the trade off between space and time. Minimizing the graph saves on space, but it takes longer to traverse the graph to find if there is a dependency between two given nodes. Maximizing the graph saves on time, since all the dependencies are there for the picking, but the size of the graph goes up dramatically.

The illustrations below show how combinations of these are minimized or maximized. You can minimize a graph that has been maximized and obtain the same minimized graph as if you had minimized the original graph. Similarily, if you maximize a graph that has been minimized, you end up with the same graph as if you had minimized the original graph.










Example

Here is a simple, concrete example. We start with a our trivial Java class from before.

    import java.util.*;
    public class test {
        public static void main(String[] args) {
            Collection c = Collections.singleton(new Object());
        }
    }

Let's start with the obvious dependencies. test.main() has a parameter that's a String array, a local variable of type Collection, calls the no-arg constructor for Object, and calls a static method on Collections. That's four so far. Also, since test extends Object by default, that's one more dependency. It also gets an implicit no-arg constructor that calls Object's no-arg constructor. We are now up to six dependencies.

The compiler does not keep track of local variables unless you call it with the -g switch. This means we are not guaranteed to find the dependency test.main(java.lang.String[]) --> java.util.Collection by simply looking at compiled code. But we can examine the signature of singleton() and since we have to call it, we'll need to get objects of these types. The method takes a parameter of type Object and returns a Set. This last one makes up for our loosing Collection earlier since Set extends Collection.

Total: seven dependencies.


Raw

The redundant dependencies are shown in bold. We can remove them by minimizing the graph.


Minimized

Or, we can maximize the graph. The bold dependencies below show the original dependencies. If the graph gets this complicated for something as trivial as test.java, imagine what your code will look like.


Maximized

Here are the dimentions of various graphs.

Software Artifacts Dependencies
Packages Classes Features Minimized Raw Maximized
test.java 3 5 4 5 (-29%) 7 27 (+286%)
Jakarta-ORO 9 101 835 2,301 (-13%) 2,652 4,641 (+75%)
Log4J 44 542 3,331 10,880 (-16%) 12,961 27,215 (+110%)
Xerces 51 880 11,218 38,194 (-13%) 43,893 80,472 (+83%)
Xalan 58 855 10,763 35,317 (-15%) 41,594 81,745 (+97%)
Ant 38 568 5,635 22,057 (-16%) 26,184 50,585 (+93%)
Ant w/ optional 113 1,117 10,854 42,140 (-16%) 50,165 99,178 (+98%)
Dependency Finder 27 364 3,587 13,098 (-18%) 15,897 28,923 (+82%)

Reporting

Reporting means displaying a dependency graph. More often than not, the graph on display is a subset of a larger graph. A graph is composed of nodes and links. When we display a subset of a graph, we must choose which nodes and which links to include in the subset. We use Perl regular expressions to indicate what we are interested in.

So, reporting deals with selecting a subset of the dependency graph; that is, choosing a subset of the nodes making up the graph and a subset of the links to or from these nodes. The scope selects the nodes in the source graph that will be copied into the destination graph. We then filter the links (i.e., the dependencies) to or from nodes in the scope that will be copied into the destination graph. We must also copy the nodes at the other end of these link if they were not already in the destination graph.

We use the term scoping when talking about the selection process for nodes. The GUI and the JSPs have a section labeled "Select programming elements" to group the controls that govern scoping.


Scoping

We use the term filtering when talking about the selection process for links. The GUI and the JSPs have a section labeled "Show dependencies" to group the controls that govern filtering.


Filtering

Now both scoping and filtering have similar user interfaces. I will talk about the one for scoping here, but the filtering interface works the same way.

First, a set of checkboxes narrow the search to only packages, classes, features, or combinations thereof. In the CLI, you achieve the same effect through command-line switches.


    C:\>DependencyReporter -package-scope  \
                           -class-scope    \
                           -feature-scope
    C:\>DependencyReporter -all
    C:\>DependencyReporter -f2f
Scope Controls

Secondly, you specify Perl regular expressions in an including and an excluding text fields. A node must match the regular expressions in the including field and not match the ones in the excluding field. You can put multiple regular expressions in each field, separate them with commas and they will be or'ed together. On the command-line, just repeat the switches.


    C:\>DependencyReporter -scope-includes //           \
                           -scope-excludes /Test/
Scope Criteria

Perl Regular Expressions

The selective graph copier uses Perl regular expressions to select scope nodes and filtered dependencies.

Here are links to pages explaining Perl regular expressions:

Special characters in regular expressions:

[A-Z] Capitals
\w Alphanumerics [A-Za-z0-9_]
\W Non-alphanumerics [^A-Za-z0-9_]
\w+ At least one alphanumeric
\s Space, including tab, carriage return, and line feed
\s* Zero or more white space
\. Matches '.', as opposed to any character

Sample rules:

// Matches everything.
/abc/ Matches anything with "abc" in it. Case-sensitive.
/abc/i Matches anything with "abc" in it. Case-insensitive.
/^abc/ Matches anything starting with "abc" in it.
/abc\(/ Matches anything with "abc(" in it. Parentheses are special characters in Perl regular expressions and must be escaped with a backslash "\" character.

Examples:

/java/ Matches anything with "java" in it.
/^java/ Matches anything that starts with "java".
/^com.jeantessier/ Matches anything that starts with "com.jeantessier". Here, the '.' really matches any single character, which obviously includes the '.' character itself.
/Node.Accept\(/ Matches any Accept() method of any class ending in "Node".
/Test/ Matches anything with "Test" in it.
/(Node)\.\1\(/ Matches any constructor for Node.
/(\w*Measurement)\.\1\(/ Matches any constructor for classes ending in Measurement.

Advanced View

So far, we have seen an interface where the Perl regular expressions apply to all software artifacts, regardless of whether they are packages, classes, or features. Dependency Finder has a more elaborate interface, called the advanced view, which allows you to specify regular expressions that apply only to packages, or only to classes, or only to features.


The checkboxes to select packages, classes, and features are arrayed on the left of the user interface. The text fields next to each one are for regular expressions that will apply only to that type of programming element or software artifact. The top field in each column is from the simple view and applies to all artifacts.

We achieve the same effect in the CLI by using multiple switches. For instance, -feature-scope-includes is for the "including" field in the "Select programming elements" group that applies only to features. It is used in scoping feature nodes, if you use it along with -feature-scope.

E.g.,

    C:\>DependencyReporter -package-scope  \
                           -class-scope    \
                           -feature-scope
    C:\>DependencyReporter -all
    C:\>DependencyReporter -f2f
    C:\>DependencyReporter -scope-includes //
    C:\>DependencyReporter -scope-includes //                       \
                           -scope-excludes /Test/                   \
                           -feature-scope-includes /someMethod/     \
                           -feature-scope-includes /anotherMethod/

Showing Only Inbound Dependencies

Inbound dependencies show you who depends on a given programming element. They are very useful when assessing the impact of a change. Dependency Finder uses a textual notation to display them:

    programming element
        <-- dependent 1
        <-- dependent 2
            ...

When there are a lot of dependencies, either because the graph is very big or an element is highly connected, it is useful to filter out the other dependencies so that inbound ones stand out and are easier to read.

In DependencyFinder and the web application, there are three checkboxes at the bottom of the control area. They control how the graph is rendered. To see only inbound dependencies, check the one labeled "inbounds" and uncheck the one labeled "outbounds". If a node has no inbound dependencies, it will appear empty. You can remove empty nodes by unchecking the last checkbox, the one labeled "empty nodes".

If you are using the command-line tools, what you have to work from is most likely a graph rendered in XML, either from DependencyExtractor or DependencyReporter. You can use XML stylesheets to convert the XML to something more user-friendly. Dependency Finder provides two sample stylesheets to show only nodes with inbound dependencies. You can apply them with the following tools:

E.g.,

    C:\>DependablesToText -in test.xml
    java.lang
        java.lang.Object
            <-- test
            <-- test.main(java.lang.String[])
            java.lang.Object.Object()
                <-- test.main(java.lang.String[])
                <-- test.test()
        java.lang.String
            <-- test.main(java.lang.String[])
    java.util
        java.util.Collections
            java.util.Collections.singleton(java.lang.Object)
                <-- test.main(java.lang.String[])
        java.util.Set
            <-- test.main(java.lang.String[])

The HTML version weaves cross-references between all the elements for easy navigation.

If you only want to remove outbound dependencies and keep empty nodes, use:

E.g.,

    C:\>HideOutboundDependenciesToText -in test.xml
        test
            test.main(java.lang.String[])
            test.test()
    java.lang
        java.lang.Object
            <-- test
            <-- test.main(java.lang.String[])
            java.lang.Object.Object()
                <-- test.main(java.lang.String[])
                <-- test.test()
        java.lang.String
            <-- test.main(java.lang.String[])
    java.util
        java.util.Collections
            java.util.Collections.singleton(java.lang.Object)
                <-- test.main(java.lang.String[])
        java.util.Set
            <-- test.main(java.lang.String[])

Showing Only Outbound Dependencies

Outbound dependencies show you who a given programming element depends upon. They are very useful when partitioning programming elements and figuring out what other classes a given class needs in order to run. Dependency Finder uses a textual notation to display them:

    programming element
        --> dependable 1
        --> dependable 2
            ...

When there are a lot of dependencies, either because the graph is very big or an element is highly connected, it is useful to filter out the other dependencies so that outbound ones stand out and are easier to read.

In DependencyFinder and the web application, there are three checkboxes at the bottom of the control area. They control how the graph is rendered. To see only outbound dependencies, check the one labeled "outbounds" and uncheck the one labeled "inbounds". If a node has no outbound dependencies, it will appear empty. You can remove empty nodes by unchecking the last checkbox, the one labeled "empty nodes".

If you are using the command-line tools, what you have to work from is most likely a graph rendered in XML, either from DependencyExtractor or DependencyReporter. You can use XML stylesheets to convert the XML to something more user-friendly. Dependency Finder provides two sample stylesheets to show only outbound dependencies. You can apply them with the following tools:

E.g.,

    C:\>DependentsToText -in test.xml
        test
            --> java.lang.Object
            test.main(java.lang.String[])
                --> java.lang.Object
                --> java.lang.Object.Object()
                --> java.lang.String
                --> java.util.Collections.singleton(java.lang.Object)
                --> java.util.Set
            test.test()
                --> java.lang.Object.Object()

The HTML version weaves cross-references between all the elements for easy navigation.

If you only want to remove inbound dependencies and keep empty nodes, use:

E.g.,

    C:\>HideInboundDependenciesToText -in test.xml
        test
            --> java.lang.Object
            test.main(java.lang.String[])
                --> java.lang.Object
                --> java.lang.Object.Object()
                --> java.lang.String
                --> java.util.Collections.singleton(java.lang.Object)
                --> java.util.Set
            test.test()
                --> java.lang.Object.Object()
    java.lang
        java.lang.Object
            java.lang.Object.Object()
        java.lang.String
    java.util
        java.util.Collections
            java.util.Collections.singleton(java.lang.Object)
        java.util.Set

Showing Both Inbounds And Outbound Dependencies

You can combine the two approaches we just looked at and display the full graph. For the sake of brevity, when a node A depends on a node B and that node B also depends on node A, Dependency Finder writes it as:

    A
        <-> B
    B
        <-> A

If you are using the command-line tools, what you have to work from is most likely a graph rendered in XML, either from DependencyExtractor or DependencyReporter. You can use XML stylesheets to convert the XML to something more user-friendly. Dependency Finder provides two sample stylesheets to show whole dependency graphs. You can apply them with the following tools:

The HTML version weaves cross-references between all the elements for easy navigation.

E.g.,

    C:\>DependencyGraphToText -in test.xml
        test
            --> java.lang.Object
            test.main(java.lang.String[])
                --> java.lang.Object
                --> java.lang.Object.Object()
                --> java.lang.String
                --> java.util.Collections.singleton(java.lang.Object)
                --> java.util.Set
            test.test()
                --> java.lang.Object.Object()
    java.lang
        java.lang.Object
            <-- test
            <-- test.main(java.lang.String[])
            java.lang.Object.Object()
                <-- test.main(java.lang.String[])
                <-- test.test()
        java.lang.String
            <-- test.main(java.lang.String[])
    java.util
        java.util.Collections
            java.util.Collections.singleton(java.lang.Object)
                <-- test.main(java.lang.String[])
        java.util.Set
            <-- test.main(java.lang.String[])

Summary

This table summarizes which CLI tool shows which information.

Tool outbounds inbounds empty
DependencyGraphToHTML
DependencyGraphToText
X X X
DependablesToHTML
DependablesToText
- X -
DependentsToHTML
DependentsToText
X - -
HideOutboundDependenciesToHTML
HideOutboundDependenciesToText
- X X
HideInboundDependenciesToHTML
HideInboundDependenciesToText
X - X

Transitive Closure

Dependencies are not automatically transitive, it depends on the implementation details within the code. Suppose you have:

    A --> B --> C

If you make a change in C, you may or may not have to modify A depending on how A and B are implemented.

A transitive closure is the set of nodes that can be reached by following downstream or upstream dependencies from a starting node. The nodes in this set might be impacted by a change, but they are not automatically. You get the upstream closure by following inbound dependencies. You get the downstream closure by following outbound dependencies. Each string of dependencies forms a path through the graph between two nodes. The number of dependencies that form the path give the path's length. The degree of separation between two nodes is the length of the shortest path between them.

Here is a further example:

    A --> B --> C --> D --> E

C is the starting point for the closure. The set of nodes whose distance to the source is 0 is [C]. The set of nodes whose distance to the source is 1 is [B, D]. The set of nodes whose distance to the source is 2 is [A, E].

distance closure
0 [C]
1 [B, C, D]
no constraint [A, B, C, D, E]
upstream [A, B, C]
downstream [C, D, E]

Here is the general algorithm:

  1. Initial set is the source itself.
  2. At each iteration, add nodes that are directly reachable and not not already in the set.
  3. Do as many iterations as needed, or until you don't add anything anymore.

A transitive closure only keeps the shortest path between two nodes.

Dependency Metrics

This is a simple tally of how many dependencies there are in a graph. It can give you a rough idea of the complexity of a given codebase, but it is not as powerful as the metrics computed by OOMetrics.


OO Metrics

Object-oriented software metrics help assess the quality of a piece of software. You can use them to verify that your architecture evolves in the right direction, from version to version. You can also use them to spot deviations from your quality guidelines, such as methods that have too many parameters or are too long.

Dependency Finder can use the information it extracts from class files to compute software quality metrics. Metrics measure certain aspects of software and provide a quantitative assessment of its quality.

Dependency Finder recognizes four levels of metrics:

Each metrics is composed of a number of measurements. What measurements make up a given metrics level is defined in a configuration file written in XML.

Explanation

A set of metrics is a group of measurements pertaining to a given chunk of software. Dependency Finder recognizes four sizes of chunks:

Project
These metrics apply to the project as a whole.
Group
These metrics apply to groups of classes. A Java package is a de facto group, but you can specify arbitrary groups using Perl regular expressions.
Class
These metrics apply to a single class.
Method
These metrics apply to individual methods within a class.

It does not track metrics about class attributes because these are not really all that interesting. The only measurements worth something deal with dependencies, and I rolled those into class metrics.

The configuration file defines what measurements apply to which level of scope (package, group, class, or method). It also defines the rules for creating arbitrary groups. You can customize the content of your metrics report by using different configuration files.

Each measurement has a name and a class that has the semantics of the measurement. There are classes for counters, lists, simple mathematical expressions, etc. Each measurement can be expressed as a numeric value and the configuration file can defines the recommanded range for that measurement.

The current types of measurement are as follow:

AccumulatorMeasurement
Aggregates lists in submetrics on the measurement's context. It uses Perl regular expressions to filter list elements.
CounterMeasurement
A simple counter, it tallies the values that are put in it. If you try to add a non-number, it simply adds 1.
NameListMeasurement
Accumulates a set of values. Its numerical value is the cardinality (i.e., size) of the set. OOMetrics uses it to keep track of dependencies.
NbSubMetricsMeasurement
Counts how many submetrics there are in the measurement's context. It can use selection criteria while counting. These are akin to "WHERE C > 10".
RatioMeasurement
Divides one measurement (base) by another (divider). Both must be in the same context.
StatisticalMeasurement
Computes the statistical properties of a given measurement across the submetrics of the measurement's context. Given a measurement name, it explores the tree of metrics rooted at the context and finds the numerical value of these named measurements in the tree. For these measurements, it computes:
  • minimum value
  • median value
  • average value
  • standard deviation
  • maximum value
  • sum
  • number of data points
SumMeasurement
Adds up numerical values. Use it with just one term to alias other measurements.

Whenever a measurement references another measurement, there is a chance that that measurement is in fact a StatisticalMeasurement standing in for a collection of others at the levels below. The measurment making the reference can specify explicitly which subvalue (i.e., minimum, median, etc.) of the StatisticalMeasurement it wants to use for its own computation. Or, it can rely on the StatisticalMeasurement's self disposition setting. A StatisticalMeasurement can specify explicitly which of its subvalues it wants to use for its numerical value. By default, this is the average value.

These measurements can potentially refer to other measurements and fall under this disposition feature:

Right now, measurement processing is fixed. In the future, you will be able to define your own types of measurements and plug them into the framework.

OOMetrics and OOMetricsGUI create one project-level metrics for the whole project. This level has measurements that span the entire analyzed codebase. For each .class file, they create one class-level metrics for that class, and one method-level metrics for each of its methods.

The tools add Class-level metrics to relevant group-level metrics. One such group, which the tools create automatically for each class, is named after the package that this class belongs to. You can define additional groups in the configuration file by providing a name for the group and one or more Perl regular expressions. If the class name matches any of the regular expressions, the class will be added to that group. This allows you to group together classes for a component or module that might span multiple Java packages.

E.g.,

    <group-definitions>
        <group-definition>
            <name>Servlet API</name>
            <pattern>/^javax.servlet/</pattern>
        </group-definition>
        <group-definition>
            <name>Test</name>
            <pattern>/test/i</pattern>
        </group-definition>
    </group-definitions>

Extracting

You use OOMetrics and OOMetricsGUI to extract .class files and compute metrics. The list of metrics is taken from a configuration file written in XML. You set the file with the -configuration switch to either command. There is also a -default-configuration switch that kicks if you do not provide a -configuration switch. This is how the launching scripts can point to the standard configuration in %DEPENDENCYFINDER_HOME%/etc/MetricsConfig.xml even if you don't specify a configuration on the command-line.

There are two configurations that come bundled with Dependency Finder.

%DEPENDENCYFINDER_HOME%/etc/MetricsConfig.xml
It has a series of measurements and statistical compilations at various levels. It can assist you in evaluating the size and complexity of your software. You can use it to find large classes or methods, or to find pieces with a disproportionate number of dependencies.
%DEPENDENCYFINDER_HOME%/etc/MartinConfig.xml
Computes I, A, and D', as per Robert C. Martin's article.

You can start from these to define your own configurations where you adjust the output to the metrics you care about.

If you don't need a given measurement, put visible="no" in its <measurement> tag. You could remove the tag altogether, but other measurements might depend on it. They would compute faulty values it you were to remove it, whereas marking it invisible will only remove it from the final output.

You can add new measurements that are functions of ones that already exist.

AccumulatorMeasurement
For merging and filtering name lists
RatioMeasurement
For comparing values to one another
StatisticalMeasurement
Get distribution information for another measurement
SumMeasurement
Add measurments together or provide aliases to existing ones

Look at %DEPENDENCYFINDER_HOME%/etc/MartinConfig.xml for an exemple of how you can combine these types of measurements to compute complex values.

You can also add your own group definitions to report on related classes that might not all be in the same Java package. For example, all classes with "Test" in the name, or all classes under a given top-level package, such as javax.servlet.

You can customize the valid range of measurements to the particular conditions of your project. Each <measurement> tag can contain optional <lower-threshold> and <upper-threshold> tags. Reports can highlight measurement values that fall outside of the valid range you specified.

specify valid range is
none all values are valid
lower threshold only all values greater than or equal to the threshold are valid
upper threshold only all values lesser than or equal to the threshold are valid
both lower and upper threshold only all values in the range between the lower and upper threshold, including them, are valid

Note that if all these ranges include the threshold themselves. If a value is equal to either the lower or the upper threshold, it is still considered valid. Also, if the upper threshold is less than the lower threshold, then no value will ever be considered valid. At this time, you cannot specify an exlusive range, where a measurement would be valid unless it fell within the designated range.

A future version of Dependency Finder will let you write custom measurement classes so you can compute your own metrics. Right now, the basic set is hard-coded and the configuration allows you mainly to customize the report.

Statistical measurements can print out as:

    self_dispose [minimum median/average standard_deviation maximum sum (size_of_data_set)]

The first value is the numeric value of the measurement. Which one of the values this is depends on the self disposition used in the measurement's <init> tag. By default, it is the average of the data set.

Extracting With the GUI


Extract Dialog

Select File | Extract; this will popup a file dialog. Select the files and/or directories to extract from and click Extract. You can repeat this command as often as needed; each time, the extracted metrics will be added to the current set of metrics in memory.


Sample Result

You can limit the number of rows on display with the filtering mechanism at the bottom of the user interface. You enter a Perl regular expression in the text field and click the button marked Filter:.

You can sort entries by the value of any measurement by clicking on the header of each column. Each mouse click alternates between ascending and descending sorting order. This also works for the name column.

Extracting With the CLI

You use OOMetrics to compute metrics and produce a report. Simply string out the files and directories on the command-line. The content of the report depends on the switches on the command-line.

E.g.,

    C:\>OOMetrics -all -txt classes %JAVA_HOME%\JRE\lib\rt.jar

You must use at least one of the following switches that determine which groups of metrics are included in the report. You can combine them if you need to.

-project
Include project-related metrics.
-groups
Include group- and package-related metrics.
-classes
Include class-related metrics.
-methods
Include method-related metrics.
-all
All of the above

You must also specify an output mode with one of these switches.

-csv
Writes each section in its own CSV file, perfect for loading in Microsoft Excel and charting.
-txt
Writes a single text report with all requested sections.
-xml
Writes a single XML report with all requested sections.

You can specify an output file with the -out switch. But in this case, the switch only provides a prefix for the output files. OOMetrics will add the appropriate suffix, depending on the output mode. For text and XML output, it writes only one file with the total report. For CSV output, however, it writes one file per selected section; each filename starts with the prefix you specify, followed by either "_project", "_groups", "_classes", or "_methods" according to the section, and ends with the ".csv" suffix.

    C:\>OOMetrics -all -txt %DEPENDENCYFINDER_HOME%\lib\DependencyFinder.jar -out df

Extracting With Ant

You use the com.jeantessier.dependencyfinder.ant.OOMetrics task to compute metrics and produce a report. You need to associate the class to a tag using Ant's <taskdef> task. You also need to tell Ant where to find the Dependency Finder classes. There is a special help page on how to do this.

    <oometrics allmetrics="yes"
               txt="yes"
               configuration="${dependencyfinder.home}/etc/MetricsConfig.xml"
               destprefix="metrics">
        <path>
            <pathelement location="."/>
        </path>
    </oometrics>

API Differences

If you are developing libraries or frameworks, that is, software that will be used and/or extended by others, you need to know what changed from version to version. Your clients depend on your published set of APIs for their own development efforts. It becomes important, as you release new revisions of your software, to keep track of which parts of the published API changed, so that you can tell your clients how to upgrade their code. The JarJarDiff tool can compute differences automatically, so you can quickly know what you might have broken in your customer's code.

Even if this is not your case, it is still a good practive to have a look at what changed across your software since the latest baseline. This allows you to keep track of changes to the classes and methods and verify that they are evolving in the right direction, whichever way you decide that is.

Extracting

Extracting With the CLI

Typically, you compile Java source files to .class files and javadocs. You may even combine the .class files into one or more JAR files. From source code in .java files, you can use javadoc to generate HTML documentation or you can use javac/jar to generate executable code in JAR files.


Compilation Workflow
Key

Source files that you write.

JDK tools and files they generate.

Dependency Finder tools and files they generate, based on your code.

The tools in Dependency Finder work from the executable code, either in JAR files or in individual .class files; they do not work from either the source code or the generated javadoc.

You use JarJarDiff to extract API differences. The following command computes the API differences between versions Old and New of My Project. The differences are saved as an XML document in the file diff.xml.

    C:\>JarJarDiff -name "My Project"    \
                   -old-label Old        \
                   -old old.jar          \
                   -new-label New        \
	           -new new.jar          \
	           -out diff.xml

Extracting Differences

You use -old switches to tell it where the old, or base version is located. Each -old switch can point to either a JAR file, a Zip file, or a directory that will be searched for .class files. You can specify a label for the version with the -old-label switch. If you do not provide a label, the tool with create one by concatenating the parameters to -old switches.

You use -new switches to tell it where the new, or modified version is located. Each -new switch can point to either a JAR file, a Zip file, or a directory that will be searched for .class files. You can specify a label for the version with the -new-label switch. If you do not provide a label, the tool with create one by concatenating the parameters to -new switches.

By default, the raw results are written in XML to the standard output stream (i.e., System.out). You can write it to a file by using the -out switch.

Extracting With Ant

You can also do the extraction from your build script if you use Ant. You use the com.jeantessier.dependencyfinder.ant.JarJarDiff task to compute differences and produce a report. You need to associate the class to a tag using Ant's <taskdef> task. You also need to tell Ant where to find the Dependency Finder classes. There is a special help page on how to do this.

    <jarjardiff destfile="xerces.xml"
                name="My Project"
	        oldlabel="Old"
	        newlabel="New">
        <old>
            <pathelement location="old.jar"/>
        </old>
        <new>
            <pathelement location="new.jar"/>
        </new>
    </jarjardiff>
    <xslt style="${dependencyfinder.home}/etc/PublishedDiffToHTML.xsl"
          in="xerces.xml"
          out="xerces.html"/>

Reporting

Dependency Finder includes XSL stylesheets for representing differences using HTML. The stylesheets include:

You can run them using the script of the same name and the -in and -out switches. The scripts call XSLTProcess and specify the XSL stylesheet with the -xsl switch. You can do the same, if you want, with either our stylesheets or your own.

Full List

You use DiffToHTML to convert all API differences to HTML. The following command produces a full report from the output of the previous JarJarDiff command.

    C:\>DiffToHTML -in diff.xml          \
                   -out fullreport.html

Full List

Public List

You use PublishedDiffToHTML to convert only public API differences to HTML. The following command produces a full report from the output of the previous JarJarDiff command.

    C:\>PublishedDiffToHTML -in diff.xml                  \
                            -out publishedAPIreport.html

Public List

Filtering

ListDocumentedElements is a Java doclet that can list all packages, classes, interfaces, fields, constructors, and methods in a given set of Java source files. You can run this tool on the "old version" source files to get the list of everything in it (old list), and on the "new version" to get the list of everything in the "new version" (new list). All you have left to do is compare the two lists, either yourself or with the ListDiff tool. This will show you the names of all the additions and removals between the two versions.

You can also limit the reporting to specific programming elements that have been earmarked using special javadoc tags. You get the list of earmarked elements with ListDocumentedElements. The following commands run this doclet on the old as well as the new codebases, saving the results in individual text files.

In this first case, the command will only list elements that you marked as @level published.

    C:\>ListDocumentedElements -tag level         \
                               -valid published   \
                               -out old-list.txt  \
                               old.java
    C:\>ListDocumentedElements -tag level         \
                               -valid published   \
                               -out new-list.txt  \
                               new.java

If you add Dependency Finder's JAR file to your CLASSPATH, you can call javadoc directly instead:

    C:\javadoc -doclet com.jeantessier.diff.ListDocumentedElements  \
               -tag level                                           \
               -valid published                                     \
               -out old-list.txt                                    \
               old.java
    C:\javadoc -doclet com.jeantessier.diff.ListDocumentedElements  \
               -tag level                                           \
               -valid published                                     \
               -out new-list.txt                                    \
               new.java

Or, as an Ant task:

    <target name="listdocumentedelements" depends="init">
        <javadoc packagenames="${packageNames}">
	    <sourcepath path="src"/>
	    <classpath refid="libs"/>
	    <doclet name="com.jeantessier.diff.ListDocumentedElements"
		    path="${classesDir}">
		<param name="-tag" value="level"/>
		<param name="-invalid" value="developer"/>
		<param name="-out" value="df.txt"/>
	    </doclet>
	</javadoc>
    </target>

In this second case, the command will only list elements that you did marked not mark as @level developer.

    C:\>ListDocumentedElements -tag level          \
                               -invalid published  \
                               -out old-list.txt   \
                               old.java
    C:\>ListDocumentedElements -tag level          \
                               -invalid developer  \
                               -out new-list.txt   \
                               new.java

Extracting Documented Elements

You then feed the output from the doclet to JarJarDiff. In the diagram below, we show both .class and JAR files being fed to JarJarDiff, but you only need to do either, not both. Dependency Finder tools can read JAR files just as well as .class files.

    C:\>JarJarDiff -name "My Project"               \
                   -old-label Old                   \
                   -old old.jar                     \
	           -old-documentation old-list.txt  \
                   -new-label New                   \
	           -new new.jar                     \
	           -new-documentation new-list.txt  \
	           -out diff.xml

Extracting Differences on Documented Elements

The full list will include special sections that highlight changes in what is no more part of the documentation and what was newly added to the documentation.

    C:\>DiffToHTML -in diff.xml          \
                   -out fullreport.html

Full List Of Documented Elements

The list can also be fed to PublishedDiffToHTML to further limit the changes to the published public API.

    C:\>PublishedDiffToHTML -in diff.xml                         \
                            -param validation-list new-list.txt  \
                            -out publishedAPIreport.html

Public List Of Documented Elements

Listing Published Documentation Changes

You use ListDiff to list changes in what is considered part of the published API. It only needs the lists generated by the doclet. No source or bytecode required.

    C:\>ListDiff -name "My Project"  \
                 -old-label Old      \
                 -old old-list.txt   \
                 -new-label New      \
                 -new new-list.txt   \
                 -compress           \
                 -out listdiff.xml

Listing Differences in Documented Elements

ListDiff writes in XML and you can convert this XML into HTML with ListDiffToHTML, or to text with ListDiffToText.

    C:\>ListDiffToHTML -in listdiff.xml     \
                       -out docreport.html

Report on Differences in Documented Elements
NOTE:
You can also use ListDocumentationDiffToText and ListDocumentationDiffToHTML to extract similar information from the output of JarJarDiff. The list is written in HTML. But we recommend that you use ListDiff instead; it is faster and simpler.

Support

Look at the current bug list on SourceForge. There is also a help-oriented discussion forum.

You can also email me at jeantessier at users.sourceforge.net.