The program ckjm calculates Chidamber and Kemerer object-oriented metrics by processing the bytecode of compiled Java files. The program calculates for each class the following six metrics, and displays them on its standard output, following the class's name:
I wrote this program out of frustration over the lack of reliable programs to calculate the Chidamber and Kemerer object-oriented metrics. The programs I found on the web were either incomplete (they calculated only some of the metrics), or unreliable (they calculated results that were obviously wrong), or extremely inefficient (they required GBs of RAM and hours of processing). Ckjm is mean and lean, following the Unix tradition of doing one thing well. It will not automatically recurse directories looking for the files you want measured, it does not offer a GUI and fancy diagrams (or even an XML output facility), and it calculates only few metrics other than the six ones specified by Chidamber and Kemerer. However, it does this job thoroughly, and efficiently: on a 1.6GHz Pentium-M machine version 1.1 of the tool processed the 33MB of the Eclipse 3.0 jar files (19717 classes) in 95 seconds.
To run the program you simply specify the class files (or pairs of jar/class files) on its command line or standard input. The program will produce on its standard output a line for each class containing the complete name of the class and the values of the corresponding class metrics. This operation model allows the tool to be easilly extended using textual pre and post processors.
-jar
flag, providing as its argument the location of the file
ckjm.jar
.
Next, you can specify as arguments the Java class files you want
to analyze.
java -jar /usr/local/lib/ckjm-1.5.jar build/classes/gr/spinellis/ckjm/*.class
(Replace the sequence /usr/local/lib/ckjm-1.5.jar
with the actual path and filename of the ckjm version you are using.)
The command's output will be a list of class names (prefixed by the
package they are defined in), followed by the corresponding
metrics for that class: WMC, DIT, NOC, CBO, RFC, LCOM, Ce, and NPM.
gr.spinellis.ckjm.ClassMetricsContainer 3 1 0 3 18 0 2 2 gr.spinellis.ckjm.MethodVisitor 11 1 0 21 40 0 1 8 gr.spinellis.ckjm.CkjmOutputHandler 1 1 0 1 1 0 3 1 gr.spinellis.ckjm.ClassMetrics 24 1 0 0 33 196 6 23 gr.spinellis.ckjm.MetricsFilter 7 1 0 6 30 11 2 5 gr.spinellis.ckjm.ClassVisitor 13 1 0 14 71 34 2 9 gr.spinellis.ckjm.ClassMap 3 1 0 1 21 0 0 2 gr.spinellis.ckjm.PrintPlainResults 2 1 0 2 8 0 1 2If the classes are located in a jar archive, you can specify as a single argument the name of the archive, followed by a space, followed by the name of the class in the archive.
java -jar /usr/local/lib/ckjm.jar 'ant-jai.jar org/apache/tools/ant/types/optional/image/Text.class'
Finally, instead of specifying the classes to be analyszed as the
command's arguments, you pass them (as class files, or as jar file, class
file pairs) on the command's standard input.
The following example will process all class files located in the
build directory.
find build -name '*.class' -print | java -jar /usr/local/lib/ckjm.jar
The program, by default, will not take into account classes that belong
to the Java SDK packages.
The command-line option switch -s
, can be used to enable this
processing.
find build -name '*.class' -mtime -7 -print | java -jar /usr/local/lib/ckjm.jar
find build -name '*.class' | fgrep -v '$' | java -jar /usr/local/lib/ckjm.jar
find build lib -name '*.class' -print | java -jar /usr/local/lib/ckjm.jar
jar tf ant.jar |
sed -n '/\.class$/s/^/ant.jar /p' |
java -jar /usr/local/lib/ckjm.jar
for i in lib/*.jar
do
jar tf $i |
sed -n "/\.class$/s,^,$i ,p"
done |
java -jar /usr/java/ckjm-1.3/build/ckjm-1.3.jar
(Replace the sequence /usr/java/ckjm-1.3/build/ckjm-1.3.jar
with the actual path and filename of the ckjm version you are using.)
#!/bin/sed -f
1i\
<?xml version="1.0" ?>\
<ckjm>
s/^/<metric><classname>/
s/ /<\/classname><WMC>/
s/ /<\/WMC><DIT>/
s/ /<\/DIT><NOC>/
s/ /<\/NOC><CBO>/
s/ /<\/CBO><RFC>/
s/ /<\/RFC><LCOM>/
s/ /<\/LCOM><Ca>/
s/ /<\/Ca><NPM>/
s/$/<\/NPM><\/metric>/
$a\
</ckjm>
If you name the script ckjm2xml and make it executable,
you can generate XML output and save it into a file as follows.
java -jar /usr/local/lib/ckjm.jar *.class | ckjm2xml >metrics.xml
(Replace the sequence /usr/local/lib/ckjm.jar
with the actual path and filename of the ckjm version you are using.)
<?xml version="1.0" ?>
<ckjm>
<metric><classname>gr.spinellis.ckjm.ClassMetricsContainer</classname><WMC>3</WMC><DIT>1</DIT><NOC>0</NOC><CBO>3</CBO><RFC>18</RFC><LCOM>0</LCOM><Ca>2</Ca><NPM>2</NPM></metric>
<metric><classname>gr.spinellis.ckjm.MethodVisitor</classname><WMC>11</WMC><DIT>1</DIT><NOC>0</NOC><CBO>21</CBO><RFC>40</RFC><LCOM>0</LCOM><Ca>1</Ca><NPM>8</NPM></metric>
<metric><classname>gr.spinellis.ckjm.CkjmOutputHandler</classname><WMC>1</WMC><DIT>1</DIT><NOC>0</NOC><CBO>1</CBO><RFC>1</RFC><LCOM>0</LCOM><Ca>3</Ca><NPM>1</NPM></metric>
<metric><classname>gr.spinellis.ckjm.ClassMetrics</classname><WMC>24</WMC><DIT>1</DIT><NOC>0</NOC><CBO>0</CBO><RFC>33</RFC><LCOM>196</LCOM><Ca>6</Ca><NPM>23</NPM></metric>
<metric><classname>gr.spinellis.ckjm.MetricsFilter</classname><WMC>7</WMC><DIT>1</DIT><NOC>0</NOC><CBO>6</CBO><RFC>30</RFC><LCOM>11</LCOM><Ca>2</Ca><NPM>5</NPM></metric>
<metric><classname>gr.spinellis.ckjm.ClassVisitor</classname><WMC>13</WMC><DIT>1</DIT><NOC>0</NOC><CBO>14</CBO><RFC>71</RFC><LCOM>34</LCOM><Ca>2</Ca><NPM>9</NPM></metric>
<metric><classname>gr.spinellis.ckjm.ClassMap</classname><WMC>3</WMC><DIT>1</DIT><NOC>0</NOC><CBO>1</CBO><RFC>21</RFC><LCOM>0</LCOM><Ca>0</Ca><NPM>2</NPM></metric>
<metric><classname>gr.spinellis.ckjm.PrintPlainResults</classname><WMC>2</WMC><DIT>1</DIT><NOC>0</NOC><CBO>2</CBO><RFC>8</RFC><LCOM>0</LCOM><Ca>1</Ca><NPM>2</NPM></metric>
</ckjm>
To copy the ckjm's output to the Microsoft Windows clipboard
to later paste the results into an MS-Word table, simply
pipe the output of ckjm to the winclip command of the
Outwit tool suite.
You can also plot the results in various formats by using
gnuplot (http://www.gnuplot.info).
Here is a diagram depicting the distribution of the CBO metric within
the classes of Eclipse (http://www.eclipse.org).
/* Measuring decision: couple interfaces */
-s
) is available for including
the Java SDK classes into the calculation.build.xml
file.
The ckjm jar file should be in the classpath.
<taskdef name="ckjm" classname="gr.spinellis.ckjm.ant.CkjmTask">
<classpath>
<pathelement location="path/to/ckjm1-2.jar"/>
</classpath>
</taskdef>
Now you can make use of the ckjm task.
The attributes of the ckjm task are the following:
<ckjm outputfile="ckjm.xml" format="xml" classdir="build/classes">
<include name="**/*.class" />
<exclude name="**/*Test.class" />
<extdirs path="lib" />
</ckjm>
You can use an XSL stylesheet to generate an HTML report from the XML
output file.
Example:
<style in="ckjm.xml" style="path/to/ckjm.xsl" out="ckjm.html" />
The distribution contains in the xsl
directory
two sample XSL files.
Here is a complete example of a build.xml file.
<project name="myproject" default="ckjm">
<target name="compile">
<!-- your compile instructions -->
</target>
<target name="ckjm" depends="compile">
<taskdef name="ckjm" classname="gr.spinellis.ckjm.ant.CkjmTask">
<classpath>
<pathelement location="path/to/ckjm1-2.jar"/>
</classpath>
</taskdef>
<ckjm outputfile="ckjm.xml" format="xml" classdir="build/classes">
<include name="**/*.class" />
<exclude name="**/*Test.class" />
</ckjm>
<style in="ckjm.xml" style="path/to/ckjm.xsl" out="ckjm.html" />
</target>
If the analyzed files form part of a class hierarchy of other class files that
are not part of the analysis, then the extdirs
path-like structure (http://ant.apache.org/manual/using.html#path)
of the ckjm task must be set to point to the directory containing
the corresponding jar files.
This will internally set the java.ext.dirs property so that
ckjm can locate the jar files containing those classes.
This product includes software developed by the Apache Software Foundation (http://www.apache.org/).
java.lang.NoSuchMethodError: org.apache.bcel.classfile.JavaClass.getSuperClasses()
.
java -Djava.ext.dirs=lib -jar ckjm-1.8.jar *.class
In the corresponding ant task, the new extdirs
path-like structure (http://ant.apache.org/manual/using.html#path)
of the ckjm task will accomplish the same function.
Example:
<ckjm outputfile="hsqldb.xml" format="xml" classdir="/app/hsqldb/classes">
<extdirs path="/app/hsqldb/lib" />
<include name="**/*.class" />
</ckjm>
(This issue was discovered by 최재영(Choi Jae Young).)
static final
fields, and these do not
take part in the calculations.
You may want to consult the disassembled code
(using a command like javap -c -private
to see
what elements ckjm takes into account.
java.lang.ClassNotFoundException: Exception while looking for class
javax.servlet.http.HttpServlet: java.io.IOException:
Couldn't find: javax.servlet.http.HttpServlet.class
To solve this problem you must explicitly setup the
java.ext.dirs
property pointing to a directory containing
the jar files where ckjm can locate those classes.
Example:
java -Djava.ext.dirs=lib -jar ckjm-1.8.jar *.class
Diomidis Spinellis. Tool writing: A forgotten art? (http://www.dmst.aueb.gr/dds/pubs/jrnl/2005-IEEESW-TotT/html/v22n4.html). IEEE Software, 22(4):9-11, July/August 2005. (doi:10.1109/MS.2005.111 (http://dx.doi.org/10.1109/MS.2005.111))
ln -s '/path/to/directory with spaces/' /tmp/shortname