For Dependency Finder version 1.1.0.
by Jean Tessier
PATH
PATH
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.
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.
Dependency Finder comes as a ZIP file that includes everything you need
to run it. The file is named DependencyFinder-<version>.bin.zip
.
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.4.2 in C:\jdk1.4.2
:
SET JAVA_HOME=C:\jdk1.4.2
You can either add this line to your AUTOEXEC.BAT
file, or on Windows
NT/XP, use Start | Settings | Control Panel | System and select the
Environment tab to create and edit variables.
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\
.
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 Windows NT/XP
use Start | Settings | Control Panel | System and select the Environment
tab to create and edit variables.
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%
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.
If the any of the tools run 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.
Dependency Finder comes as one of two compressed TAR file that includes
everything you need to run it. The files are named
DependencyFinder-<version>.bin.tar.gz
and
DependencyFinder-<version>.bin.tar.bz2
. Their contents are identical
except of the compression algorithm used.
Tar files in the distribution contain long file names, and may require gnu tar to do the extraction.
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. There are
distributions of Java for most operating systems, including MacOS X.
Suppose you installed JDK 1.3 in /usr/java
.
For sh:
JAVA_HOME=/usr/java export JAVA_HOME
For bash:
export JAVA_HOME=/usr/java
You can put these lines in your .profile
file.
For csh or tcsh:
setenv JAVA_HOME /usr/java
You can put this line in your .cshrc
file.
Extract the TAR file to some directory you like. The extraction will create a subdirectory named "DependencyFinder" that is the root of the package.
$ tar xvzf DependencyFinder-<version&t;.tar.gz
or
$ bunzip2 DependencyFinder-<version&t;.tar.bz2 | tar xvf -
For example, if you extract it to $HOME
, you will endup with a
directory called $HOME/DependencyFinder/
.
Create an environment variable named DEPENDENCYFINDER_HOME
and have it
point to the directory where you installed Dependency Finder.
If the any of the tools run out of memory, try modifying the
DEPENDENCYFINDER_OPTS
environment variable to allocate more memory to the JVM.
Suppose you extracted the TAR file in $HOME
.
For sh:
DEPENDENCYFINDER_HOME=$HOME/DependencyFinder export DEPENDENCYFINDER_HOME DEPENDENCYFINDER_OPTS=-Xmx128m export DEPENDENCYFINDER_OPTS
For bash:
export DEPENDENCYFINDER_HOME=$HOME/DependencyFinder export DEPENDENCYFINDER_OPTS=-Xmx128m
You can put these lines in your .profile
file.
For csh or tcsh:
setenv DEPENDENCYFINDER_HOME $HOME/DependencyFinder setenv DEPENDENCYFINDER_OPTS -Xmx128m
You can put these lines in your .cshrc
file.
PATH
Add the directory where you put the Unix scripts to your PATH
variable.
Suppose you extracted the TAR file in $HOME
.
For sh:
PATH=$DEPENDENCYFINDER_HOME\bin;$PATH export PATH
For bash:
export PATH=$DEPENDENCYFINDER_HOME\bin;$PATH
You can put these lines in your .profile
file.
For csh or tcsh:
set path = ($DEPENDENCYFINDER_HOME\bin $path)
You can put this line in your .cshrc
file.
Dependency Finder also comes as a web application that you can easily deploy in just about any J2EE servlet container. Most containers have their own way to deploy and manage web applications, so you will need to adjust these instructions to your particular circumstances.
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 copying the WAR file to a specific directory.
For instance, using Tomcat on Windows, you can do the following:
C:\>%CATALINA_HOME%\bin\shutdown
C:\>copy DependencyFinder.war %CATALINA_HOME%\webapps
C:\>%CATALINA_HOME%\bin\startup
web.xml
file
Next, you need to tell the wep application where to find the dependency graph
on which it will operate. This is done via the application's context
parameters. Some application servers let you set them using some form of
application management tool. But for others, such as Tomcat, you need to edit
them manually using a text editor. You can find them in theWEB-INF\web.xml
file from the WAR file.
You will need to decide how you feed information into the web application. You can either extract the dependency graph from compiled Java code or read in a previously extracted graph. Extraction is faster, but requires that you place a copy of the compiled Java code on the server running the web application. This is often impractical, so you can generate the graph on your build machine instead and transfer only the resulting XML document.
There are four context parameters that you can 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 from 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.file
Use this variable to tell Dependency Finder where to locate files which contain extracted dependency graphs. You can separate multiple locations by using commas. Each location should contain an XML document, such as the ones produced by DependencyExtractor
directory that will be recursively.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. Other application servers may allow you to simply restart the web application itself.
C:\>%CATALINA_HOME%\bin\shutdown
C:\>notepad %CATALINA_HOME%\webapps\DependencyFinder\WEB-INF\web.xml
C:\>%CATALINA_HOME%\bin\startup
Or you can edit the WAR file directly and then use the deployment tool in Tomcat Manager to redeploy the web application.
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.
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 |
ListSymbols
|
<listsymbols>
|
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
|
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.
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.
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.
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.
Dependency Finder uses specialized classes to parse .class
files. It also
includes some tools for looking at the contents of these files.
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.
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 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.
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:
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.
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.
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.
Dependency Finder does not keep track of dependencies on self. Dependency analysis is useful for two things:
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.
Very often, Dependency Finder cannot track dependencies on final
fields whose
type is either a primitive type or String
.
In Java, you can define constants in classes and interfaces by marking them
as final
. No one can change the value because it is final, not even
subclasses. 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. Even when the client
is the class itself, the compiler can inline the value directly and save the
field lookup.
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.
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.
Dependency Finder does not track the line numbers where dependencies occur.
Dependency Finder works on compiled code and the compiler leaves very little
data as far as line numbers are concerned. The only information available is
the LineNumberTable
attribute, part of the Code
attribute, which is itself
part of the Method_info
structure inside the .class
file. Now, the
LineNumberTable
attribute is optional. "javac
" won't include it if you use
the -g:none
option. And even when it does, it is not always very helpful.
You can run a quick test with:
public class A {
public void f1() {
// Do nothing
}
}
And get an entry for line 1 for an implicit constructor (this happens to be the
location of the class declaration) and another entry for line 4 for f1()
(this is the closing '}
' that ends the method). In both cases, they are not
obvious locations.
Also, the .class
file has absolutely no line number information whatsoever
about:
Code
attribute)Given all this, Dependency Finder does not do anything with line numbers, given that the information is sketchy and what little is there is hard to work with.
And there is an additional limitation. Dependency graphs only record a
specific dependency once, regardless how many times it is present in the code.
If a method a()
calls another method b()
more than once, there is only one
edge in the graph. Also, there is no object to represent the dependency so the
graph cannot record dependency-specific details, such as how many times it
occured or on which line it occured.
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.
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.
C:\>DependencyExtractor classes
%JAVA_HOME%\JRE\lib\rt.jar
You can specify an output file with the -out
switch:
C:\>DependencyExtractor -out df.txt
%DEPENDENCYFINDER_HOME%\lib\DependencyFinder.jar
You can save the graph to XML with the -xml
switch:
C:\>DependencyExtractor -xml -out df.xml
%DEPENDENCYFINDER_HOME%\lib\DependencyFinder.jar
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 -show-all
-scope-includes /test/
test.xml
C:\>c2c -show-all
-class-scope-includes /test/
test.xml
C:\>DependencyReporter -c2c
-show-all
-class-scope-includes /test/
test.xml
C:\>DependencyReporter -show-all
-class-scope
-class-scope-includes /test/
-class-filter
test.xml
For fun, I entered manually this graph description into Poseidon for UML.
We can narrow the graph further to class-to-package dependencies. Again, all these commands are equivalent.
C:\>c2p -show-all
-scope-includes /test/
test.xml
C:\>c2p -show-all
-class-scope-includes /test/
test.xml
C:\>DependencyReporter -c2p
-show-all
-scope-includes /test/
test.xml
C:\>DependencyReporter -show-all
-class-scope
-class-scope-includes /test/
-package-filter
test.xml
test --> java.lang --> java.util
Finally, a package-to-package dependency graph. All these commands are equivalent.
C:\>p2p -show-all
test.xml
C:\>DependencyReporter -p2p
-show-all
test.xml
C:\>DependencyReporter -show-all
-package-scope
-package-filter
test.xml
--> java.lang --> java.util java.lang <-- java.util <--
The web application can either extract a dependency graph from compiled Java code, or read one that was previously extracted from one or more XML document. The graph will stay in memory for the lifetime of the process serving the application, or until you load it anew. This graph can get pretty big, so make sure you allocate enough memory to the application server.
You use extract.jsp
to generate a dependency graph from compiled Java code.
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 long 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>
You use load.jsp
to read a dependency graph from an XML document such as the
ones generated by DependencyExtractor
. If you just call load.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 read it, and a timestamp of when it was
extracted.
If you click on the Launch button, or call load.jsp?launch=Launch
, it
will start to read a new dependency graph. The previous one will remain in use
for other callers until the loading terminates successfully. The JSP prints
the name of classes as it reads them. And at the end, it prints how many files
it read and how it took to read them.
You can use the URL "load.jsp?launch=Launch
" to automatically update the
graph after automated compilation runs, such as during nightly builds.
<target name="load" depends="init">
<get src="${WEB_APP_URL}/load.jsp?launch=Launch"
dest="log.html"/>
</target>
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>
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:
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 {}
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() {}
}
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:
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:
A.a() --> B
A --> B.b()
A --> B
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.
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.
The redundant dependencies are shown in bold. We can remove them by minimizing the graph.
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.
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 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.
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.
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 -show-all
-package-scope
-class-scope
-feature-scope
C:\>DependencyReporter -show-all
-all
C:\>DependencyReporter -show-all
-f2f
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 -show-all
-scope-includes //
-scope-excludes /Test/
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
.
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
.
C:\>DependencyReporter -show-all
-package-scope
-class-scope
-feature-scope
C:\>DependencyReporter -show-all -all
C:\>DependencyReporter -show-all -f2f
C:\>DependencyReporter -show-all -scope-includes //
C:\>DependencyReporter -show-all
-scope-includes //
-scope-excludes /Test/
-feature-scope-includes /someMethod/
-feature-scope-includes /anotherMethod/
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, DependencyReporter
has switches
matching the checkboxes from the GUI and web application. You can use it with
-show-inbounds
, with or without -show-empty-nodes
. The Ant task also has
matching attribtues.
Another option with 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:
DependablesToText
DependablesToHTML
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:
HideOutboundDependenciesToText
HideOutboundDependenciesToHTML
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[])
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, DependencyReporter
has switches
matching the checkboxes from the GUI and web application. You can use it with
-show-outbounds
, with or without -show-empty-nodes
. The Ant task also has
matching attribtues.
Another option with 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:
DependentsToText
DependentsToHTML
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:
HideInboundDependenciesToText
HideInboundDependenciesToHTML
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
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, DependencyReporter
has switches
matching the checkboxes from the GUI and web application. You can use it with
both -show-inbounds
and -show-outbounds
, with or without
-show-empty-nodes
. If you want to use all three, you can save some typing by
using -show-all
instead. The Ant task also has matching attribtues.
Another option with 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:
DependencyGraphToText
DependencyGraphToHTML
The HTML version weaves cross-references between all the elements for easy navigation.
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[])
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
Sometimes the codebase can be too large for Dependency Finder to deal with it in one pass. The dependency graph can become so large that it uses up all available memory. This can be the case eiher on a large project or when you try to build a graph across many applications. You can ease this situation by:
If you have multiple JAR files, you can simply process them individualy.
If you have one large JAR file, you can extract it to a temporary location and put the files in smaller groups, such as related packages rooted at some directory.
A quick and dirty way of splitting the JAR: extract it to a folder somewhere and run DependencyReporter on the subfolders.
For example, using DependencyFinder.jar
:
% jar xf DependencyFinder.jar com
You can analyze the major package groups as follow:
% DependencyExtractor -xml -out classreader.xml
com/jeantessier/classreader
% DependencyExtractor -xml -out dependency.xml
com/jeantessier/dependency
% DependencyExtractor -xml -out dependencyfinder.xml
com/jeantessier/dependencyfinder
% DependencyExtractor -xml -out diff.xml
com/jeantessier/diff
% DependencyExtractor -xml -out metrics.xml
com/jeantessier/metrics
To save on memory, DependencyExtractor
uses a TransientClassfileLoader
: it
reads classes in one at a time and discards them before moving on to the next.
So the real problem is the size of the resulting graph. If you do only steps
1, 2, and 4 above, without the reduction stop, DependencyReporter
will
aggregate multiple graphs when you give it all the XML files but the resulting
graph will still be as big as if you had just run DependencyExtractor
once on
the original codebase.
Here's a variation on this idea. Run DependencyReporter
with -c2c
on the
intermediary graphs to generate small class-to-class graphs.
% DependencyReporter -xml -out classreader.c2c.xml
-show-all
-c2c
classreader.xml
% DependencyReporter -xml -out dependency.c2c.xml
-show-all
-c2c
dependency.xml
% DependencyReporter -xml -out dependencyfinder.c2c.xml
-show-all
-c2c
dependencyfinder.xml
% DependencyReporter -xml -out diff.c2c.xml
-show-all
-c2c
diff.xml
% DependencyReporter -xml -out metrics.c2c.xml
-show-all
-c2c
metrics.xml
Then combine all these small graphs with a last pass of DependencyReporter
to
aggregate all the little ones into an all-encompassing class-to-class graph.
% DependencyReporter -xml -out c2c.xml
-c2c
classreader.c2c.xml
dependency.c2c.xml
dependencyfinder.c2c.xml
diff.c2c.xml
metrics.c2c.xml
This approach shows an 80%-90% reduction in number of nodes and about 80% in the number of edges (dependencies). Taken together, that's a 98% reduction in complexity. This should help with both memory footprint and processing time.
Graph | Nodes | Dependencies | Nodes x Dependencies | ||||||
Packages | Classes | Features | Total | ||||||
classreader.xml |
8 | 112 | 1,078 | 1,198 | 4,042 | 4,842,316 | |||
classreader.c2c.xml |
8 | 112 | 0 | 120 | (-90%) | 809 | (-80%) | 97,080 | (-98%) |
commandline.xml |
3 | 34 | 204 | 241 | 545 | 131,345 | |||
commandline.c2c.xml |
3 | 34 | 0 | 37 | (-85%) | 115 | (-79%) | 4,255 | (-97%) |
dependency.xml |
11 | 102 | 903 | 1,016 | 3,429 | 3,483,864 | |||
dependency.c2c.xml |
11 | 102 | 0 | 113 | (-89%) | 395 | (-88%) | 44,635 | (-99%) |
dependencyfinder.xml |
27 | 252 | 1,744 | 2,023 | 6,947 | 14,053,781 | |||
dependencyfinder.c2c.xml |
27 | 252 | 0 | 279 | (-86%) | 1,429 | (-79%) | 398,691 | (-97%) |
diff.xml |
9 | 75 | 504 | 588 | 1,580 | 929,040 | |||
diff.c2c.xml |
9 | 75 | 0 | 84 | (-86%) | 235 | (-85%) | 19,740 | (-98%) |
metrics.xml |
13 | 112 | 820 | 945 | 2,873 | 2,714,985 | |||
metrics.c2c.xml |
13 | 112 | 0 | 125 | (-87%) | 418 | (-85%) | 52,250 | (-98%) |
text.xml |
5 | 11 | 20 | 36 | 66 | 2,376 | |||
text.c2c.xml |
5 | 11 | 0 | 16 | (-56%) | 10 | (-85%) | 160 | (-93%) |
all.xml |
35 | 420 | 4,571 | 5,026 | 17,846 | 89,693,996 | |||
all.c2c.xml |
35 | 420 | 0 | 455 | (-91%) | 3,411 | (-81%) | 1,552,005 | (-98%) |
These numbers were taken from version 1.1.0.
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:
A transitive closure only keeps the shortest path between two nodes.
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
.
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.
You can specify a custom configuration file or let the tool use the default
one in %DEPENDENCYFINDER_HOME%\etc\MetricsConfig.xml
.
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 indicates behavior associated with
the measurement. The semantics of each measurement are currently hardcoded in
the MetricsGatherer
. It decides which basic values are computed. You can
make minor adjustments by specifying the class used for 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.
Here is a brief overview of some of the measurements, grouped by chunk. The
definitive list is in %DEPENDENCYFINDER_HOME%\etc\MetricsConfig.xml
.
The following basic measurements are computed for each method.
The following are simple numerical measurements taken against the method.
- Parameters (PARAM)
- Number of parameters for the method. Very long signatures are often seen as a sign of poor design.
- Single Line of Code (SLOC)
- Number of code lines in the method. Comments and empty lines are not counted. Large methods are usually a sign of poor design.
- Local Variables (LVAR)
- Number of local variables use by the method.
The following measurements list classes, methods, and fields that have a dependency to or from this method.
- Inbound Intra-Class Method Dependencies (IICM)
- Other methods within the same class, that depend on this method.
- Outbound Intra-Class Feature Dependencies (OICF)
- Methods and fields within the same class that this method depends on.
- Inbound Intra-Package Method Dependencies (IIPM)
- Methods in other classes of the same package that depend on this method.
- Outbound Intra-Package Feature Dependencies (OIPF)
- Methods and fields within the classes of the same package that this method depends on.
- Inbound Extra-Package Method Dependencies (IEPM)
- Methods in other packages that depend on this method.
- Outbound Extra-Package Feature Dependencies(OEPF)
- Methods and fields in other packages that this method depends on.
- Outbound Intra-Package Class Dependencies (OIPC)
- Classes within the same package that this method depends on.
- Outbound Extra-Package Class Dependencies (OEPC)
- Classes in other packages that this method depends on.
The following basic measurements are computed for each class.
The following are simple numerical measurements taken against the class.
- Single Line of Code (SLOC)
- Number of code lines in the class. Comments and empty lines are not counted. Large classes are usually a sign of poor design.
- Subclasses (SUB)
- Number of direct subclasses of this class.
- Depth of Inheritance (DOI)
- How many hops there are between the top of the inheritance hierarchy and this class.
- Attributes (A)
- Number of attributes in this class.
- Methods (M)
- Number of methods in this class
- Inner Classes (IC)
- Number of inner classes in this class.
The following measurements list classes, methods, and fields that have a dependency to or from this method.
- Inbound Intra-Package Dependencies (IIP)
- Classes of the same package that depend on this class.
- Inbound Extra-Package Dependencies (IEP)
- Classes in other packages that depend on this class.
- Outbound Intra-Package Dependencies (OIP)
- Classes of the same package that this class depends on.
- Outbound Extra-Package Dependencies (OEP)
- Classes of other packages that this class depends on.
- Inbound Intra-Package Method Dependencies(IIPM)
- Methods and fields in other classes of the same package that depend on this class.
- Inbound Extra-Package Method Dependencies (IEPM)
- Methods in other packages that depend on this class.
The following basic measurements are computed for each group. They are simple numerical measurements taken against the group.
Each package is automatically a group. The configuration file can also define additional arbitrary groups that will be collected in addition to package-related groups.
- Classes (C)
- Number of classes in this group.
- Interfaces (I)
- Number of interfaces in this group
The following basic measurements are computed for the whole project. They are simple numerical measurements taken against the project.
- Groups (G)
- Number of groups in the project.
- Packages (P)
- Number of packages in the project
All the dependency-related measurements can accumulate the explicit
dependencies that make them. All you need to do is make their class
com.jeantessier.metrics.NameListMeasurement
in the configuration file. You
can list those dependencies as part of your report by giving the -expand
switch to OOMetrics
.
The current types of measurement are as follow:
ContextAccumulatorMeasurement
- Aggregates lists from other measurements in the same 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
SubMetricsAccumulatorMeasurement
- Aggregates lists from measurements in submetrics of the current context. It uses Perl regular expressions to filter list elements.
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 measurement 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, it uses the average value.
These measurements can potentially refer to other measurements and make use of
the disposition feature when dealing with StatisticalMeasurement
:
ContextAccumulatorMeasurement
NbSubMetricsMeasurement
RatioMeasurement
StatisticalMeasurement
SubMetricsAccumulatorMeasurement
SumMeasurement
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.
<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>
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
, andD'
, 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.
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.
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.
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.
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 -out df
-all
-txt
%DEPENDENCYFINDER_HOME%\lib\DependencyFinder.jar
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>
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.
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.
![]() |
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 -out diff.xml
-name "My Project"
-old-label Old
-old old.jar
-new-label New
-new new.jar
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.
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"/>
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.
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
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
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
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 -out diff.xml
-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
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
The list can also be fed to PublishedDiffToHTML
to further
limit the changes to the published public API.
C:\>PublishedDiffToHTML -in diff.xml
-out publishedAPIreport.html
-param validation-list new-list.txt
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 -out listdiff.xml
-name "My Project"
-old-label Old
-old old-list.txt
-new-label New
-new new-list.txt
-compress
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
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.Here are some use cases that are more elaborate. They combine functionality from multiple tools.
This use case deals with having a large codebase and tracking usage of
deprecated classes and methods. As your codebase grows and your third-party
libraries get updated, it is inevitable that it will end up using deprecated
APIs. javac
can list violations, but only up to the first 100 and the output
is a simple flat list of violations.
Dependency Finder can track all uses of deprecated APIs and group them by
various criteria. You can use either DependencyReporter
or OOMetrics
,
depending on how you want to structure the results.
But regardless of which way you go about it, you must first list the deprecated
symbols you are looking for. Run ListDeprecatedElements
on all the JARs that
might contain deprecated symbos. Don't forget to include rt.jar
. Save the
output to a file that will list each deprecated element, one per line.
C:\>ListDeprecatedElements -out deprecated.txt
%JAVA_HOME%\jre\lib\rt.jar
lib
classes
Note: This may take a while.
You only need to run this command when a JAR changes. In the example above,
we look at third-party libraries in lib
and the project's own codebase in
classes
. The codebase is likely to change very often, but you don't have to
wait for the tool to analyze large libraries, such as rt.jar
, every time.
You can split the call and generate two lists: one list for all third-party
libraries (fairly stable, doesn't change often) and one list for our own
codebase (generate new list with every build to take into account the latest
changes).
C:\>ListDeprecatedElements -out deprecated.libs.txt
%JAVA_HOME%\jre\lib\rt.jar
lib
C:\>ListDeprecatedElements -out deprecated.code.txt
classes
The examples in this section use the CLI, but there are equivalent Ant tasks so you can put this in your nightly build very easily.
Let's use the following simple class as an example to illustrate the various reports. It has one deprecated method and another method calling it, therefore creating a dependency on a deprecated symbol.
public class DeprecatedExample {
/** @deprecated */
public void deprecatedMethod() {}
public void callingMethod() {
deprecatedMethod();
}
}
You can extract a list of deprecated symbols from it.
C:\>ListDeprecatedElements -out deprecated.txt DeprecatedExample.class
DeprecatedExample.deprecatedMethod()
Now that you have a list of deprecated symbols, you can analyze the codebase to determine where they are being used. You can use either with a dependency graph or with dependency-related metrics.
Generate a dependency graph to work from:
C:\>DependencyExtractor -xml -out graph.xml DeprecatedExample.class
DeprecatedExample --> java.lang.Object DeprecatedExample() --> java.lang.Object.Object() callingMethod() --> DeprecatedExample.deprecatedMethod() deprecatedMethod() <-- DeprecatedExample.callingMethod() java.lang Object <-- DeprecatedExample Object() <-- DeprecatedExample.DeprecatedExample()
Then, run the DependencyReporter
to show dependencies related to the
deprecated API. The tool can either group them by deprecated element, showing
where they are called, or by caller, showing which deprecated element they
call.
It is useful to organize the report by deprecated element to find all the
places where a given deprecated symbol is referenced. In the example, it will
show where deprecatedMethod()
is called from.
For a text report organized by deprecated element:
C:\>DependencyReporter -out report.txt
-show-inbounds
-scope-includes-list deprecated.txt
graph.xml
DeprecatedExample deprecatedMethod() <-- DeprecatedExample.callingMethod()
For an HTML report organized by deprecated element:
C:\>DependencyReporter -xml -out report.xml
-show-inbounds
-scope-includes-list deprecated.txt
graph.xml
C:\>DependencyGraphToHTML -in report.xml -out report.html
It is useful to organize the report by caller to find all usage of deprecated
symbols in a given section of your codebase. In the example, it will show what
deprecated methods are used by callingMethod()
.
For a text report organized by caller:
C:\>DependencyReporter -out report.txt
-show-outbounds
-filter-includes-list deprecated.txt
graph.xml
DeprecatedExample callingMethod() --> DeprecatedExample.deprecatedMethod()
For an HTML report organized by caller:
C:\>DependencyReporter -xml -out report.xml
-show-outbounds
-filter-includes-list deprecated.txt
graph.xml
C:\>DependencyGraphToHTML -in report.xml -out report.html
You can use multiple -scope-includes-list
and -filter-includes-list
switches if your deprecated API list consists of more than one file.
C:\>DependencyReporter -out report.txt
-show-inbounds
-scope-includes-list deprecated.libs.txt
-scope-includes-list deprecated.code.txt
graph.xml
C:\>DependencyReporter -out report.txt
-show-outbounds
-filter-includes-list deprecated.libs.txt
-filter-includes-list deprecated.code.txt
graph.xml
DependencyReporter
can also take -scope-excludes-list
and
-filter-excludes-list
switches for deprecated items that you may want to skip
over. There are times when reliance on a deprecated API is inevitable or too
expensive to correct. Yet, you don't want it to appear again and again in
reports and distract from other use of deprecated material that needs to be
fixed. You can keep track manually of the cases to ignore. When you pass them
to DependencyReporter
, it will be remove them from the includes list.
C:\>DependencyReporter -out report.txt
-show-inbounds
-scope-includes-list deprecated.libs.txt
-scope-includes-list deprecated.code.txt
-scope-excludes-list deprecated.skip.txt
graph.xml
DependencyReporter
provides a simple format where a lot of data is presented
succinctly side-by-side. But you may want to group this data in larger chunks.
For this, you can use OOMetrics
instead.
You can use OOMetrics
to write either an HTML report or one of two text
report formats.
To generate reports organized by deprecated elements, use the configuration
file in etc\InboundDependencyConfig.xml
.
In
counts all calls, including duplicates. DistIn
removes duplicates and
can list its entries when OOMetrics
is used with -expand
. At project
level, they account for the whole project. At group level, they account for an
entire group or package. At the class level, they account for a single class.
At the method, they account for an individual method. Inside
is used to show
dependencies on the class itself that do not come from its methods.
For a text report organized by deprecated elements, with all methods listed together:
C:\>OOMetrics -out report
-configuration %DEPENDENCYFINDER_HOME%\etc\InboundDependencyConfig.xml
-filter-includes-list deprecated.txt
-all
-txt
-expand
DeprecatedExample.class
Project metrics --------------- Project Used by APIs (In): 1 [1 1/1 0 1 1 (1)] Used by distinct APIs (DistIn): 1 (100%) [1 1/1 0 1 1 (1)]
Group metrics -------------
Used by APIs (In): 1 [1 1/1 0 1 1 (1)] Used by distinct APIs (DistIn): 1 (100%) [1 1/1 0 1 1 (1)]
Class metrics ------------- DeprecatedExample Used by APIs (In): 1 Used by distinct APIs (DistIn): 1 (100%) Used by additional APIs directly (Inside): 0 (0%)
Method metrics -------------- DeprecatedExample.deprecatedMethod() Used by APIs (In): 1 Used by distinct APIs (DistIn): 1 (100%) DeprecatedExample.callingMethod()
For a text report organized by deprecated element, with methods grouped by class:
C:\>OOMetrics -out report
-configuration %DEPENDENCYFINDER_HOME%\etc\InboundDependencyConfig.xml
-filter-includes-list deprecated.txt
-all
-xml
-expand
DeprecatedExample.class
C:\>MetricsToText -in report.xml -out report.txt
Project Used by APIs: 1.0 [1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1] Used by distinct APIs: 1.0 [1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1] Used by distinct APIs ratio: 1.0
Used by APIs: 1.0 [1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1] Used by distinct APIs: 1.0 [1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1] Used by distinct APIs ratio: 1.0
DeprecatedExample Used by APIs: 1.0 Used by distinct APIs: 1.0 Used by distinct APIs ratio: 1.0 Used by additional APIs directly: 0 Used by additional APIs directly ratio: 0.0
DeprecatedExample.deprecatedMethod() Used by APIs: 1.0 Used by distinct APIs: 1 DeprecatedExample.callingMethod() Used by distinct APIs ratio: 1.0
For an HTML version of this report, use MetricsToHTML
instead in the final
step.
To generate reports organized by caller, use the configuration file in
etc\OutboundDependencyConfig.xml
.
Out
counts all calls, including duplicates. DistOut
removes duplicates and
can list its entries when OOMetrics
is used with -expand
. At project
level, they account for the whole project. At group level, they account for an
entire group or package. At the class level, they account for a single class.
At the method, they account for an individual method. Outside
is used to
show dependencies from the class itself that do not go through its methods.
For a text report organized by caller, with all methods listed together:
C:\>OOMetrics -out report
-configuration %DEPENDENCYFINDER_HOME%\etc\OutboundDependencyConfig.xml
-filter-includes-list deprecated.txt
-all
-txt
-expand
DeprecatedExample.class
Project metrics --------------- Project Uses APIs (Out): 1 [1 1/1 0 1 1 (1)] Uses distinct APIs (DistOut): 1 (100%) [1 1/1 0 1 1 (1)]
Group metrics -------------
Uses APIs (Out): 1 [1 1/1 0 1 1 (1)] Uses distinct APIs (DistOut): 1 (100%) [1 1/1 0 1 1 (1)]
Class metrics ------------- DeprecatedExample Uses APIs (Out): 1 Uses distinct APIs (DistOut): 1 (100%) Uses additional APIs in fields and class structure (outside of methods) (Outside): 0 (0%)
Method metrics -------------- DeprecatedExample.callingMethod() Uses APIs (Out): 1 Uses distinct APIs (DistOut): 1 (100%) DeprecatedExample.deprecatedMethod()
For a text report organized by caller, with methods grouped by class:
C:\>OOMetrics -out report
-configuration %DEPENDENCYFINDER_HOME%\etc\OutboundDependencyConfig.xml
-filter-includes-list deprecated.txt
-all
-xml
-expand
DeprecatedExample.class
C:\>MetricsToText -in report.xml -out report.txt
Project Uses APIs: 1.0 [1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1] Uses distinct APIs: 1.0 [1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1] Uses distinct APIs ratio: 1.0
Uses APIs: 1.0 [1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1] Uses distinct APIs: 1.0 [1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 1] Uses distinct APIs ratio: 1.0
DeprecatedExample Uses APIs: 1.0 Uses distinct APIs: 1.0 Uses distinct APIs ratio: 1.0 Uses additional APIs in fields and class structure (outside of methods): 0 Uses additional APIs in fields and class structure (outside of methods) ratio: 0.0
DeprecatedExample.callingMethod() Uses APIs: 1.0 Uses distinct APIs: 1 DeprecatedExample.deprecatedMethod() Uses distinct APIs ratio: 1.0
For an HTML version of this report, use MetricsToHTML
instead in the final
step.
You often rely on third-party libraries for speciic functions in your own software. For example, Dependency Finder uses Log4J for logging and Jakarta-ORO for matching regular expressions. When these third-party libraries release new versions, you need to figure out the impact that upgrading to the newer version will have on your code.
This is where JarJarDiff
can help. But it reports all changes, many of
which may be internal only or in areas that you are not using. You can run the
output through PublishedDiffToHTML
to limit it to externally visible elements
(public and protected), but that may not be enough. The third-party library
provider may not give you a list of their published API to further restrict
PublishedDiffToHTML
, but you can build your own using Dependency Finder,
customized to your own usage of the library.
For example, to find Dependency Finder's usage of Log4J, you first extract Dependency Finder's dependency graph and reduce it to the part that involves Log4J elements only.
C:\>DependencyExtractor -xml -out df.xml
DependencyFinder.jar
C:\>DependencyReporter -xml -out df-log4j.xml
-show-all
-scope-includes /log4j/
df.xml
C:\>DependencyGraphToText -in df-log4j.xml
-out df-log4j.txt
If you use multiple third-party libraries, you don't have to to repeat all the
steps for each one. You can supply multiple -scope-includes
switches to
DependencyReporter
to get a single text file that has all the symbols across
all third-party libraries.
Now, df-log4j.txt
has the list of all Log4J elements used in Dependency
Finder. You can pass it to JarJarDiff
and PublishedDiffToHTML
to limit the
report to these elements only.
C:\>JarJarDiff -out log4j-1.2.8-1.2.9.xml
-name Log4J
-old-label 1.2.8
-old jakarta-log4j-1.2.8\dist\lib\log4j-1.2.8.jar
-new-label 1.2.9
-new jakarta-log4j-1.2.0\dist\lib\log4j-1.2.9.jar
C:\>PublishedDiffToHTML -in log4j-1.2.8-1.2.9.xml
-out log4j-1.2.8-1.2.9.html
-param validation-list df-log4j.txt
If you run DiffToHTML
, you will get a full report on what changed in the
library. If you run PublishedDiffToHTML
without a validation list, you will
get a smaller report that focuses on the public view of the library. The
example shown above, with the validation list, focuses exclusively on those
parts of the library that you are actually using.
You can use OOMetrics
to computer Robert C. Martin's D metric. Dependency
Finder comes with a special configuration that computes a normalized variation,
called D', in the configuration file etc\MartinConfig.xml
.
D
, A
, and I
work at the group or package level.
The formula for I
is:
I = Ce / (Ca + Ce)
Ca
is the number of classes outside this package that depend on classes
inside this package (i.e., inbound dependencies). Ce
is the number of
classes outside this package that classes in the package depend on (i.e.,
outbound dependencies). Feature names are converted to class names with Perl
regular expressions. All class names are collected with
SubMetricsAccumulatorMeasurement
to remove duplicates. With Ca
and Ce
,
the tool can calculate the value of I
for the package.
The formula for A
is:
A = Na / Nc
OOMetrics
already computes C
and AC
that correspond to Nc
and Na
respectively. The configuration uses SumMeasurement
to give them aliases to
relate them to the computation of A
.
The formula for D'
is:
D' = A + I - 1
It is computed for each group. At the project level, the configuration gives a
statistical analysis of D'
, A
, and I
across the project's codebase.
For a text report straight from the tool:
C:\>OOMetrics -out report
-configuration %DEPENDENCYFINDER_HOME%\etc\MartinConfig.xml
-all
-txt
classes
For a text report from the XML output:
C:\>OOMetrics -out report
-configuration %DEPENDENCYFINDER_HOME%\etc\MartinConfig.xml
-all
-xml
classes
C:\>MetricsToText -in report.xml -out report.txt
For an HTML version of this report, use MetricsToHTML
instead in the last
step.
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.