More‎ > ‎

How to Transform the Results from Findbugs, Checkstyle and PMD to HTML Report with XSLT 2.0 and Java?

BY MARKUS SPRUNCK


This article describes how to Transform the Results from Findbugs, Checkstyle and PMD into a single HTML Report with XSLT 2.0 and Java. For some tasks pure Java is not my first choice - this is especially true for the manipulation of XML files. The example code creates a single HTML report out of three XML result files from Findbugs, Checkstyle and PMD. For this task a combination of Java and XSLT is easier to code and from my point of view better to maintain than pure Java.

Requirements

The results of static code analysis with the tools Findbugs [1], Checkstyle [2] and PMD [3] can be collected and merged with tools like Sonar [4] or XRadar [5]. Unfortunately, in some cases these tools are not available and/or applicable, e.g. when you need a simple report or the user can't have HTTP access to a distinct environment with a Sonar server. Yes, this happens in some industries.

If you are not familiar with XSL Transformations, you may start reading Hands-on XSL (IBM; about 1h for the tutorial) and then XSL Frequently Asked Questions (by Dave Pawson) for more details.

So, the task in this article is to create a nice single HTML report (see Figure 1) with some statistics, hyperlinks to rule descriptions, a unified classification and details collected for each class.

 

HTML-Sample-Report
Figure 1: Sample HTML report

Program to do the XSL Transformation

The following Java class (File #1) shows how to calls the XSL Transformation and cleans up the temporary files. In the program some error handling code has been removed for better readability. The main method of the class contains the following 6 steps:
  1. Create intermediate xml-file for Findbugs 
  2. Create intermediate xml-file for Checkstyle 
  3. Create intermediate xml-file for PMD 
  4. Merge first two files and create firstMergeResult file 
  5. Merge result file with third file and create secondMergeResult file 
  6. Create html report out of secondMergeResult 
To compile this program you need two additional libraries: (i) log4j-1.2.13.jar for logging and (ii) saxon9.jar/saxonb9.jar for XSLT 1.0/2.0. 

I recommend for the beginning Saxon-B ("The last release of Saxon-B was version 9.1.0.8. This was a complete and conformant implementation of the XSLT 2.0, XQuery 1.0, and XPath 2.0 Recommendations published on 23 January 2007 by W3C." [6]) . This is a free available version, but there are also commercial product versions with more functions.

File #1: ScaReportUtility.java

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package com.sprunck.sample.sca.report;
 
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
 
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
 
import org.apache.log4j.Logger;
 
class ScaReportUtility {
 
    private static final String EMPTY = "";
 
    private static final Logger LOGGER = Logger.getLogger(ScaReportUtility.class);
 
    private static void run(final String xslt, 
            final String input, final String output, 
            final String param, final String value) {
 
        FileOutputStream outputStream = null;
        try {
            if (LOGGER.isInfoEnabled()) {
                LOGGER.info(input + "  > " + xslt 
                + " " + param + " " + value + " >  " + output);
            }
 
            // Process the Source into a Transformer Object
            System.setProperty("javax.xml.transform.TransformerFactory", 
                    "net.sf.saxon.TransformerFactoryImpl");
            final TransformerFactory transformerFactory = TransformerFactory.newInstance();
            final InputStream inputStream = ScaReportUtility.class.getResourceAsStream("/xsl/" + xslt);
            final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            final StreamSource source = new StreamSource(reader);
            final Transformer transformer = transformerFactory.newTransformer(source);
 
            // Add a parameter for the transformation 
            if (!param.isEmpty()) {
                transformer.setParameter(param, value);
            }
 
            outputStream = new FileOutputStream(output);
            final StreamResult outputTarget = new StreamResult(outputStream);
            final StreamSource xmlSource = new StreamSource(input);
 
            // Transform the XML Source to a Result 
            transformer.transform(xmlSource, outputTarget);
 
        } catch (final TransformerConfigurationException e) {
            LOGGER.error(e.getMessage());
        } catch (final TransformerException e) {
            LOGGER.error(e.getMessage());
        } catch (final FileNotFoundException e) {
            LOGGER.error(e.getMessage());
        } finally {
            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (final IOException e) {
                    LOGGER.error(e.getMessage());
                }
            }
        }
    }
 
    static void deletefile(final String pathName) {
        final File file = new File(pathName);
        if (!file.delete()) {
            LOGGER.error("Unable to delete file " + pathName);
        }
    }
 
    public static void main(final String[] args) {
 
        // Prepare userDirectory and tempDirectoryPrefix
        final String currentDirectory = System.getProperty("user.dir");
        final String userDirectory = currentDirectory.replace('\\', '/') + '/';
        final String timeStamp = Integer.toHexString((int) System.nanoTime());
        final String tempDirectory = System.getProperty("java.io.tmpdir");
        final String tempDirectoryPrefix = tempDirectory.replace('\\', '/') + timeStamp;
 
        // 1. Create intermediate xml-file for Findbugs
        final String inputFileFindbugs = userDirectory + "findbugs.xml";
        final String findbugsTempFile = tempDirectoryPrefix + "_PostFB.xml";
        run("prepare_findbugs.xslt", inputFileFindbugs, findbugsTempFile, EMPTY, EMPTY);
 
        // 2. Create intermediate xml-file for Checkstyle
        final String inputFileCheckstyle = userDirectory + "checkstyle.xml";
        final String checkstyleTempFile = tempDirectoryPrefix + "_PostCS.xml";
        run("prepare_checkstyle.xslt", inputFileCheckstyle, checkstyleTempFile, EMPTY, EMPTY);
 
        // 3. Create intermediate xml-file for PMD
        final String inputFilePMD = userDirectory + "pmd.xml";
        final String pmdTempFile = tempDirectoryPrefix + "_PostPM.xml";
        run("prepare_pmd.xslt", inputFilePMD, pmdTempFile, EMPTY, EMPTY);
 
        // 4. Merge first two files and create firstMergeResult file
        final String firstMergeResult = tempDirectoryPrefix + "_FirstMerge.xml";
        run("merge.xslt", checkstyleTempFile, firstMergeResult, "with", findbugsTempFile);
 
        // 5. Merge result file with third file and create secondMergeResult file
        final String secondMergeResult = tempDirectoryPrefix + "_SecondMerge.xml";
        run("merge.xslt", firstMergeResult, secondMergeResult, "with", pmdTempFile);
 
        // 6. Create html report out of secondMergeResult 
        final String htmlOutputFileName = userDirectory + "result.html";
        run("create_html.xslt", secondMergeResult, htmlOutputFileName, EMPTY, EMPTY);
 
        // Delete all temporary files
        deletefile(findbugsTempFile);
        deletefile(checkstyleTempFile);
        deletefile(pmdTempFile);
        deletefile(firstMergeResult);
        deletefile(secondMergeResult);
    }
}   

The output of this program may look like this:

1
2
3
4
5
6
0   [main] INFO - C:/eclipse/ScaReport/test/findbugs.xml > prepare_findbugs.xslt > c:/temp/8c30437c_PostFB.xml
518 [main] INFO - C:/eclipse/ScaReport/test/checkstyle.xml > prepare_checkstyle.xslt > c:/temp/8c30437c_PostCS.xml
578 [main] INFO - C:/eclipse/ScaReport/test/pmd.xml > prepare_pmd.xslt > c:/temp/8c30437c_PostPM.xml
625 [main] INFO - c:/temp/8c30437c_PostCS.xml > merge.xslt with c:/temp/8c30437c_PostFB.xml > c:/temp/8c30437c_FirstMerge.xml
734 [main] INFO - c:/temp/8c30437c_FirstMerge.xml > merge.xslt with c:/temp/8c30437c_PostPM.xml > c:/temp/8c30437c_SecondMerge.xml
798 [main] INFO - c:/temp/8c30437c_SecondMerge.xml > create_html.xslt > C:/eclipse/ScaReport/test/result.html

Figure 2 depicts the flow of the transformations. In the beginning the 3 input files findbugs.xml, checkstyle.xml and pmd.xml are transformed into intermediate, temporary files. These three files are then merged in two steps into one single file. This file is then transformed into the final html report.
Figure 2: The 6 Transformation Steps of the Java Program in file #1 

Before we start to discuss the XSLT files, we should have a short look at the original input files. 

XML from Findbugs, Checkstyle and PMD 

Findbugs, Checkstyle and PMD have complete different formats. The next three files (#3, #4 and #5) are small samples and show how different the content is.

File #2: pmd.xml with two <violation> entries

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<pmd timestamp="2010-07-23T20:34:59.300" version="4.2.5">
    <file name="C:\Workspace\default_ant\src\org\sprunck\bee\Bee.java">
        <violation begincolumn="13" beginline="19" class="Bee" endcolumn="20" endline="19" externalinfourl="http://pmd.sourceforge.net/rules/basic.html#UselessOperationOnImmutable" method="toString" package="org.sprunck.bee" priority="3" rule="UselessOperationOnImmutable" ruleset="Basic Rules">
        An operation on an Immutable object (String, BigDecimal or BigInteger) won't change the object itself
        </violation>
    </file>
    <file name="C:\Workspace\default_ant\src\org\sprunck\foo\Foo.java">
        <violation begincolumn="13" beginline="36" class="Foo" endcolumn="17" endline="36" externalinfourl="http://pmd.sourceforge.net/rules/basic.html#UnconditionalIfStatement" method="Went" package="org.sprunck.foo" priority="3" rule="UnconditionalIfStatement" ruleset="Basic Rules">
        Do not use if statements that are always true or always false
        </violation>
    </file>
</pmd>

File #3: checkstyle.xml with tree <errors> entries

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<checkstyle version="5.0">
    <file name="C:\Workspace\default_ant\src\org\sprunck\bee\Bee.java">
    </file>
    <file name="C:\Workspace\default_ant\src\org\sprunck\bee\package-info.java">
    </file>
    <file name="C:\Workspace\default_ant\src\org\sprunck\foo\Foo.java">
        <error line="10" message="Line has trailing spaces." severity="error" source="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck"></error>        
        <error line="20" message="Line has trailing spaces." severity="error" source="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineCheck"></error>            
        <error column="26" line="30" message="'Went' entspricht nicht dem Muster '^[a-z][a-zA-Z0-9]*$'." severity="error" source="com.puppycrawl.tools.checkstyle.checks.naming.MethodNameCheck"></error>
    </file>
    <file name="C:\Workspace\default_ant\src\org\sprunck\foo\package-info.java">
    </file>
    <file name="C:\Workspace\default_ant\src\org\sprunck\tests\BeeTest.java">
    </file>
    <file name="C:\Workspace\default_ant\src\org\sprunck\tests\FooTest.java">
    </file>
    <file name="C:\Workspace\default_ant\src\org\sprunck\tests\package-info.java">
    </file>
</checkstyle>

File #4: findbugs.xml with four <BugInstance> entries

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
<bugcollection analysistimestamp="1279910106071" release="" sequence="0" timestamp="1279910105010" version="1.3.9-rc2">
  <project projectname="">
    <jar>C:\Workspace\default_ant\bin</jar>
    <auxclasspathentry>C:\Workspace\default_ant\lib\junit-4.5.jar</auxclasspathentry>
  </project>  
 
  <buginstance abbrev="CN" category="BAD_PRACTICE" instancehash="57afbdc4c8b3eaf65667ad2375c54fbf" instanceoccurrencemax="0" instanceoccurrencenum="0" priority="2" type="CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE">
    <shortmessage>Class defines clone() but doesn't implement Cloneable</shortmessage>
    <longmessage>org.sprunck.bee.Bee defines clone() but doesn't 
        implement Cloneable</longmessage>
    <class classname="org.sprunck.bee.Bee" primary="true">
      <sourceline classname="org.sprunck.bee.Bee" end="31" start="6">
        <message>At <Unknown>:[lines 6-31]</message>
      </sourceline>
      <message>In class org.sprunck.bee.Bee</message>
    </class>    
    <method classname="org.sprunck.bee.Bee" isstatic="false" name="clone" primary="true" signature="()Ljava/lang/Object;">
    <sourceline classname="org.sprunck.bee.Bee" end="31" endbytecode="25" start="31" startbytecode="0"></sourceline>
      <message>In method org.sprunck.bee.Bee.clone()</message>
    </method>
    <sourceline classname="org.sprunck.bee.Bee" end="31" endbytecode="25" start="31" startbytecode="0" synthetic="true">
      <message>At <Unknown>:[line 31]</message>
    </sourceline>
  </buginstance>
   
  <buginstance abbrev="NP" category="BAD_PRACTICE" instancehash="83c0778b087a767c144d8d9f2eb1e2fb" instanceoccurrencemax="0" instanceoccurrencenum="0" priority="2" type="NP_CLONE_COULD_RETURN_NULL">
    <shortmessage>Clone method may return null</shortmessage>
    <longmessage>org.sprunck.bee.Bee.clone() may return null</longmessage>
    <class classname="org.sprunck.bee.Bee" primary="true">
      <sourceline classname="org.sprunck.bee.Bee" end="31" start="6">
        <message>At <Unknown>:[lines 6-31]</message>
      </sourceline>
      <message>In class org.sprunck.bee.Bee</message>
    </class>
    <method classname="org.sprunck.bee.Bee" isstatic="false" name="clone" primary="true" signature="()Ljava/lang/Object;">
      <sourceline classname="org.sprunck.bee.Bee" end="31" endbytecode="25" start="31" startbytecode="0"></sourceline>
      <message>In method org.sprunck.bee.Bee.clone()</message>
    </method>
    <sourceline classname="org.sprunck.bee.Bee" end="31" endbytecode="1" primary="true" start="31" startbytecode="1">
      <message>At <Unknown>:[line 31]</message>
    </sourceline>
  </buginstance>
   
  <buginstance abbrev="RV" category="CORRECTNESS" instancehash="e2731da5e9908130587c6e3c2f03efdd" instanceoccurrencemax="0" instanceoccurrencenum="0" priority="1" type="RV_RETURN_VALUE_IGNORED">
    <shortmessage>Method ignores return value</shortmessage>
    <longmessage>org.sprunck.bee.Bee.toString() ignores return value
         of String.concat(String)</longmessage>
    <class classname="org.sprunck.bee.Bee" primary="true">
      <sourceline classname="org.sprunck.bee.Bee" end="31" start="6">
        <message>At <Unknown>:[lines 6-31]</message>
      </sourceline>
      <message>In class org.sprunck.bee.Bee</message>
    </class>
    <method classname="org.sprunck.bee.Bee" isstatic="false" name="toString" primary="true" signature="()Ljava/lang/String;">
      <sourceline classname="org.sprunck.bee.Bee" end="22" endbytecode="73" start="18" startbytecode="0"></sourceline>
      <message>In method org.sprunck.bee.Bee.toString()</message>
    </method>
    <method classname="java.lang.String" isstatic="false" name="concat" role="METHOD_CALLED" signature="(Ljava/lang/String;)Ljava/lang/String;">
      <sourceline classname="java.lang.String" end="2003" endbytecode="105" sourcefile="String.java" sourcepath="java/lang/String.java" start="1996" startbytecode="0"></sourceline>
      <message>Called method String.concat(String)</message>
    </method>
    <sourceline classname="org.sprunck.bee.Bee" end="19" endbytecode="14" primary="true" start="19" startbytecode="14">
      <message>At <Unknown>:[line 19]</message>
    </sourceline>
  </buginstance>
   
  <buginstance abbrev="Nm" category="BAD_PRACTICE" instancehash="25df32f98cb531473711f6d820a5cb44" instanceoccurrencemax="0" instanceoccurrencenum="0" priority="2" type="NM_METHOD_NAMING_CONVENTION">
    <shortmessage>Method names should start with a lower case letter</shortmessage>
    <longmessage>The method name org.sprunck.foo.Foo.Went() doesn't start 
        with a lower case letter</longmessage>
    <class classname="org.sprunck.foo.Foo" primary="true">
      <sourceline classname="org.sprunck.foo.Foo" end="39" start="6">
        <message>At <Unknown>:[lines 6-39]</message>
      </sourceline>
      <message>In class org.sprunck.foo.Foo</message>
    </class>
    <method classname="org.sprunck.foo.Foo" isstatic="false" name="Went" primary="true" signature="()Z">
      <sourceline classname="org.sprunck.foo.Foo" end="39" endbytecode="37" start="35" startbytecode="0"></sourceline>
      <message>In method org.sprunck.foo.Foo.Went()</message>
    </method>
    <sourceline classname="org.sprunck.foo.Foo" end="39" endbytecode="37" start="35" startbytecode="0" synthetic="true">
      <message>At <Unknown>:[lines 35-39]</message>
    </sourceline>
  </buginstance>
   
  <bugcategory category="BAD_PRACTICE">
    <description>Bad practice</description>
  </bugcategory>
  <bugcategory category="CORRECTNESS">
    <description>Correctness</description>
  </bugcategory>
  <bugpattern abbrev="NP" category="BAD_PRACTICE" type="NP_CLONE_COULD_RETURN_NULL">
    <shortdescription>Clone method may return null</shortdescription>
    <details></details>
  </bugpattern>
  <bugpattern abbrev="Nm" category="BAD_PRACTICE" type="NM_METHOD_NAMING_CONVENTION">
    <shortdescription>Method names should start with a lower case letter</shortdescription>
    <details></details>
  </bugpattern>
  <bugpattern abbrev="RV" category="CORRECTNESS" type="RV_RETURN_VALUE_IGNORED">
    <shortdescription>Method ignores return value</shortdescription>
    <details></details>
  </bugpattern>
  <bugpattern abbrev="CN" category="BAD_PRACTICE" type="CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE">
    <shortdescription>Class defines clone() but doesn't implement Cloneable</shortdescription>
    <details></details>
  </bugpattern>
  <bugcode abbrev="Nm">
    <description>Confusing method name</description>
  </bugcode>
  <bugcode abbrev="RV" cweid="440">
    <description>Bad use of return value from method</description>
  </bugcode>
  <bugcode abbrev="NP" cweid="476">
    <description>Null pointer dereference</description>
  </bugcode>
  <bugcode abbrev="CN" cweid="580">
    <description>Bad implementation of cloneable idiom</description>
  </bugcode>
  <errors errors="0" missingclasses="0"></errors>
  <findbugssummary alloc_mbytes="63.56" clock_seconds="1.40" cpu_seconds="1.62" gc_seconds="0.26" num_packages="3" peak_mbytes="24.91" priority_1="1" priority_2="3" referenced_classes="18" timestamp="Fri, 23 Jul 2010 20:35:05 +0200" total_bugs="4" total_classes="4" total_size="45" vm_version="14.2-b01">
    <filestats bugcount="3" bughash="df1120c1c4b7708412d471df8e18b310" path="org/sprunck/bee/<Unknown>" size="11"></filestats>
    <filestats bugcount="1" bughash="2838bfeb77300e43a82da09454a1d353" path="org/sprunck/foo/<Unknown>" size="12"></filestats>
    <filestats bugcount="0" path="org/sprunck/tests/<Unknown>" size="22"></filestats>
    <packagestats package="org.sprunck.bee" priority_1="1" priority_2="2" total_bugs="3" total_size="11" total_types="1">
      <classstats bugs="3" class="org.sprunck.bee.Bee" interface="false" priority_1="1" priority_2="2" size="11" sourcefile="<Unknown>"></classstats>
    </packagestats>
    <packagestats package="org.sprunck.foo" priority_2="1" total_bugs="1" total_size="12" total_types="1">
      <classstats bugs="1" class="org.sprunck.foo.Foo" interface="false" priority_2="1" size="12" sourcefile="<Unknown>"></classstats>
    </packagestats>
    <packagestats package="org.sprunck.tests" total_bugs="0" total_size="22" total_types="2">
      <classstats bugs="0" class="org.sprunck.tests.BeeTest" interface="false" size="9" sourcefile="<Unknown>"></classstats>
      <classstats bugs="0" class="org.sprunck.tests.FooTest" interface="false" size="13" sourcefile="<Unknown>"></classstats>
    </packagestats>
    <findbugsprofile>
      <classprofile avgmicrosecondsperinvocation="611" invocations="18" maxmicrosecondsperinvocation="1786" name="edu.umd.cs.findbugs.detect.EqualsOperandShouldHaveClassCompatibleWithThis" standarddeviationmircosecondsperinvocation="635" totalmilliseconds="11"></classprofile>
      <classprofile avgmicrosecondsperinvocation="1009" invocations="12" maxmicrosecondsperinvocation="9060" name="edu.umd.cs.findbugs.classfile.engine.bcel.ConstantDataflowFactory" standarddeviationmircosecondsperinvocation="2440" totalmilliseconds="12"></classprofile>
      <classprofile avgmicrosecondsperinvocation="1191" invocations="12" maxmicrosecondsperinvocation="4932" name="edu.umd.cs.findbugs.classfile.engine.bcel.UnconditionalValueDerefDataflowFactory" standarddeviationmircosecondsperinvocation="1342" totalmilliseconds="14"></classprofile>
      <classprofile avgmicrosecondsperinvocation="1100" invocations="18" maxmicrosecondsperinvocation="5828" name="edu.umd.cs.findbugs.detect.BuildObligationPolicyDatabase" standarddeviationmircosecondsperinvocation="1514" totalmilliseconds="19"></classprofile>
      <classprofile avgmicrosecondsperinvocation="1835" invocations="12" maxmicrosecondsperinvocation="17810" name="edu.umd.cs.findbugs.classfile.engine.bcel.CFGFactory" standarddeviationmircosecondsperinvocation="4827" totalmilliseconds="22"></classprofile>
      <classprofile avgmicrosecondsperinvocation="2482" invocations="12" maxmicrosecondsperinvocation="16333" name="edu.umd.cs.findbugs.classfile.engine.bcel.IsNullValueDataflowFactory" standarddeviationmircosecondsperinvocation="4594" totalmilliseconds="29"></classprofile>
      <classprofile avgmicrosecondsperinvocation="1211" invocations="25" maxmicrosecondsperinvocation="13843" name="edu.umd.cs.findbugs.classfile.engine.bcel.JavaClassAnalysisEngine" standarddeviationmircosecondsperinvocation="2750" totalmilliseconds="30"></classprofile>
      <classprofile avgmicrosecondsperinvocation="2583" invocations="12" maxmicrosecondsperinvocation="24261" name="edu.umd.cs.findbugs.classfile.engine.bcel.TypeDataflowFactory" standarddeviationmircosecondsperinvocation="6559" totalmilliseconds="30"></classprofile>
      <classprofile avgmicrosecondsperinvocation="1491" invocations="21" maxmicrosecondsperinvocation="15508" name="edu.umd.cs.findbugs.classfile.engine.bcel.ValueNumberDataflowFactory" standarddeviationmircosecondsperinvocation="3259" totalmilliseconds="31"></classprofile>
      <classprofile avgmicrosecondsperinvocation="1770" invocations="18" maxmicrosecondsperinvocation="8428" name="edu.umd.cs.findbugs.detect.OverridingEqualsNotSymmetrical" standarddeviationmircosecondsperinvocation="2406" totalmilliseconds="31"></classprofile>
      <classprofile avgmicrosecondsperinvocation="2179" invocations="18" maxmicrosecondsperinvocation="9310" name="edu.umd.cs.findbugs.detect.NoteDirectlyRelevantTypeQualifiers" standarddeviationmircosecondsperinvocation="2427" totalmilliseconds="39"></classprofile>
      <classprofile avgmicrosecondsperinvocation="2804" invocations="15" maxmicrosecondsperinvocation="38444" name="edu.umd.cs.findbugs.classfile.impl.ZipCodeBaseFactory" standarddeviationmircosecondsperinvocation="9526" totalmilliseconds="42"></classprofile>
      <classprofile avgmicrosecondsperinvocation="152" invocations="287" maxmicrosecondsperinvocation="2219" name="edu.umd.cs.findbugs.classfile.engine.ClassDataAnalysisEngine" standarddeviationmircosecondsperinvocation="236" totalmilliseconds="43"></classprofile>
      <classprofile avgmicrosecondsperinvocation="2558" invocations="18" maxmicrosecondsperinvocation="7725" name="edu.umd.cs.findbugs.detect.FieldItemSummary" standarddeviationmircosecondsperinvocation="2943" totalmilliseconds="46"></classprofile>
      <classprofile avgmicrosecondsperinvocation="4046" invocations="12" maxmicrosecondsperinvocation="46704" name="edu.umd.cs.findbugs.classfile.engine.bcel.MethodGenFactory" standarddeviationmircosecondsperinvocation="12862" totalmilliseconds="48"></classprofile>
      <classprofile avgmicrosecondsperinvocation="98" invocations="545" maxmicrosecondsperinvocation="1898" name="edu.umd.cs.findbugs.OpcodeStack$JumpInfoFactory" standarddeviationmircosecondsperinvocation="181" totalmilliseconds="53"></classprofile>
      <classprofile avgmicrosecondsperinvocation="734" invocations="287" maxmicrosecondsperinvocation="67446" name="edu.umd.cs.findbugs.classfile.engine.ClassInfoAnalysisEngine" standarddeviationmircosecondsperinvocation="4201" totalmilliseconds="210"></classprofile>
    </findbugsprofile>
  </findbugssummary>
  <classfeatures></classfeatures>
  <history></history>
</bugcollection>

The shown files have a lot in common, but to create a unique report it is better to find a new common format.

Transformation to Common Format

In the new format contains everything needed for the HTML report. 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<sca xmlns:functx="http://www.functx.com">
    <file name="<package.class_name.java>">
        <message category="<rule_category>" line="<integer_number>" message="<short_message_string>" priority="<number>" rule="<rule_name>" rule_id="<rule_id>" tool="<tool_name>"></message>
        .
        .
        .
    </file>
    .
    .
    .
 
</sca>

The following three files (#5, #6 and #7) show the data from Findbugs, Checkstyle and PMD transformed into the new format.

File #5: prepare_findbugs.xslt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output encoding="ISO-8859-1" indent="yes" method="xml"></xsl:output>
 
    <xsl:template match="/">
        <sca>
            <xsl:for-each select="//BugCollection/BugInstance/Class">
                <xsl:call-template name="File"></xsl:call-template>
            </xsl:for-each>
        </sca>
    </xsl:template>
 
    <xsl:template name="File">
        <xsl:variable name="catkey" select="SourceLine/@classname"></xsl:variable>
        <file name="{$catkey}.java">
            <xsl:call-template name="Source_Line"></xsl:call-template>
        </file>
    </xsl:template>
 
    <xsl:template match="SourceLine" name="Source_Line">
        <xsl:variable name="type" select="../@type"></xsl:variable>
        <message>
            <xsl:attribute name="tool">findbugs</xsl:attribute>
            <xsl:attribute name="line"><xsl:value-of select="./SourceLine/@start"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="message"><xsl:value-of select="../ShortMessage"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="priority"><xsl:value-of select="../../BugInstance/@priority"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="rule"><xsl:value-of select="../ShortMessage"></xsl:value-of> (<xsl:value-of select="../../..//BugPattern[$type=@type]/@abbrev"></xsl:value-of>)</xsl:attribute>
            <xsl:variable name="category1" select="../../..//BugPattern[$type=@type]/@category"></xsl:variable>
            <xsl:variable name="smallCase" select="'abcdefghijklmnopqrstuvwxyz '"></xsl:variable>
            <xsl:variable name="upperCase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'"></xsl:variable>
            <xsl:attribute name="category"><xsl:value-of select="translate($category1,$upperCase,$smallCase)"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="rule_id"><xsl:value-of select="../../..//BugPattern[$type=@type]/@type"></xsl:value-of></xsl:attribute>
        </message>
    </xsl:template>
 
    <xsl:template match="ShortMessage"></xsl:template>
    <xsl:template match="Field"></xsl:template>
    <xsl:template match="LongMessage"></xsl:template>
 
</xsl:stylesheet>

File #6: prepare_pmd.xslt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<xsl:stylesheet version="2.0" xmlns:functx="http://www.functx.com" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
    <xsl:output encoding="ISO-8859-1" indent="yes" method="xml"></xsl:output>
 
    <xsl:template match="/">
        <sca>
            <xsl:for-each select="//pmd">
                <xsl:apply-templates></xsl:apply-templates>
            </xsl:for-each>
        </sca>
    </xsl:template>
 
     <xsl:template match="file">
         <xsl:variable name="package_tmp" select="./violation/@package"></xsl:variable>
         <xsl:variable name="package" select="distinct-values($package_tmp)"></xsl:variable>         
         <xsl:variable name="class_tmp" select="./violation/@class"></xsl:variable>
         <xsl:variable name="class" select="distinct-values($class_tmp)"></xsl:variable>
        <file name="{$package}.{$class}.java">
            <xsl:apply-templates select="node()"></xsl:apply-templates>
        </file>
    </xsl:template>
 
    <xsl:template match="violation">
        <message>
            <xsl:attribute name="tool">pmd</xsl:attribute>
            <xsl:attribute name="line"><xsl:value-of select="@beginline"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="message"><xsl:value-of select="@rule"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="priority"><xsl:value-of select="@priority"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="rule"><xsl:value-of select="@rule"></xsl:value-of></xsl:attribute>
 
            <xsl:variable name="category1" select="@ruleset"></xsl:variable>
            <xsl:variable name="smallCase" select="'abcdefghijklmnopqrstuvwxyz '"></xsl:variable>
            <xsl:variable name="upperCase" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ_'"></xsl:variable>
            <xsl:attribute name="category"><xsl:value-of select="translate($category1,$upperCase,$smallCase)"></xsl:value-of></xsl:attribute>
        </message>
    </xsl:template>
 
</xsl:stylesheet>

File #7: prepare_checkstlye.xslt

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output encoding="ISO-8859-1" indent="yes" method="xml"></xsl:output>
 
    <xsl:template match="/">
        <sca>
            <xsl:for-each select="//checkstyle">
                <xsl:apply-templates></xsl:apply-templates>
            </xsl:for-each>
        </sca>
    </xsl:template>
 
    <xsl:template match="file">
        <xsl:variable name="new_name" select="translate(@name, '/', '.')"></xsl:variable>
        <xsl:variable name="new_name" select="translate($new_name, '\', '.')"></xsl:variable>
        <xsl:variable name="new_name" select="concat(substring-after($new_name,'.src.'),  substring-after($new_name,'.source.'))"></xsl:variable>
 
        <xsl:variable name="msg" select="./error"></xsl:variable>
        <xsl:if test="($msg='')">
            <file name="{$new_name}">
                <xsl:apply-templates select="node()"></xsl:apply-templates>
            </file>
        </xsl:if>
    </xsl:template>
 
    <xsl:template match="error">
        <xsl:variable name="priority">
            <xsl:if test="@severity='error'">1</xsl:if>
            <xsl:if test="@severity='warning'">2</xsl:if>
            <xsl:if test="@severity='info'">3</xsl:if>
        </xsl:variable>
        <message>
            <xsl:attribute name="tool">checkstyle</xsl:attribute>
            <xsl:attribute name="line"><xsl:value-of select="@*"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="message"><xsl:value-of select="@severity"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="priority"><xsl:value-of select="+$priority"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="rule"><xsl:value-of select="@message"></xsl:value-of></xsl:attribute>
            <xsl:attribute name="category">style</xsl:attribute>
        </message>
    </xsl:template>
 
</xsl:stylesheet>

Now the three new three files will be merged into one single file (#8). For this the merge.xslt [7] from Oliver Becker, has been used. This is a simple XSLT 1.0 implementation to merge two files. 

File #8: <timestamp>_SecondMerge.xml with new format and merged results

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<sca xmlns:functx="http://www.functx.com">
 
    <file name="org.sprunck.bee.Bee.java">
        <message category="bad practice" line="6" message="Class defines clone() but doesn't implement Cloneable" priority="2" rule="Class defines clone() but doesn't implement Cloneable (CN)" rule_id="CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE" tool="findbugs"></message>
        <message category="basic rules" line="19" message="UselessOperationOnImmutable" priority="3" rule="UselessOperationOnImmutable" tool="pmd"></message>
    </file>
     
    <file name="org.sprunck.bee.Bee.java">
        <message category="bad practice" line="6" message="Clone method may return null" priority="2" rule="Clone method may return null (NP)" rule_id="NP_CLONE_COULD_RETURN_NULL" tool="findbugs"></message>
    </file>
     
    <file name="org.sprunck.bee.Bee.java">
        <message category="correctness" line="6" message="Method ignores return value" priority="2" rule="Method ignores return value (RV)" rule_id="RV_RETURN_VALUE_IGNORED" tool="findbugs"></message>
    </file>
     
    <file name="org.sprunck.foo.Foo.java">
        <message category="style" line="10" message="error" priority="1" rule="Line has trailing spaces." tool="checkstyle"></message>
        <message category="style" line="20" message="error" priority="1" rule="Line has trailing spaces." tool="checkstyle"></message>
        <message category="style" line="30" message="error" priority="1" rule="'Went' entspricht nicht dem Muster '^[a-z][a-zA-Z0-9]*$'." tool="checkstyle"></message>
        <message category="bad practice" line="6" message="Method names should start with a lower case letter" priority="2" rule="Method names should start with a lower case letter (Nm)" rule_id="NM_METHOD_NAMING_CONVENTION" tool="findbugs"></message>
        <message category="basic rules" line="36" message="UnconditionalIfStatement" priority="3" rule="UnconditionalIfStatement" tool="pmd"></message>
    </file>
 
</sca>

Creation of HTML Report

Now we are ready to create the HTML report. The following XSLT 2.0 file creates the report shown in figure 1.

File #9: create_html.xslt

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
<xsl:stylesheet version="2.0"><!--  -->
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" 
    <!--  -->
    xmlns:uml="http://www.eclipse.org/uml2/2.0.0/UML" xmlns:xmi="http://schema.omg.org/spec/XMI/2.1"
    <!--  -->
    xmlns:fn="http://www.w3.org/2005/xpath-functions">
    <!--  -->
    <xsl:output encoding="ISO-8859-1" indent="yes" method="html"></xsl:output>
 
    <xsl:template match="sca">
         
             
                <title>XSLT Sample</title>
                 
             
                <h2>Report
                    <xsl:call-template name="out_whitespace"></xsl:call-template>
                    (<xsl:value-of select="current-date()"></xsl:value-of>;
                    <xsl:call-template name="out_whitespace"></xsl:call-template>
                    <xsl:value-of select="current-time()"></xsl:value-of>)
                </h2>
                <h3>Summary Messages</h3>
                <xsl:for-each-group group-by="@category" select="//message">
                        <xsl:sort select="@category"></xsl:sort>
                        <xsl:variable name="category1" select="@category"></xsl:variable>
                        </xsl:for-each-group><xsl:for-each-group group-by="@rule" select="//message[$category1=@category]">
                            <xsl:sort order="ascending" select="@priority"></xsl:sort>
                            </xsl:for-each-group><table border="0" class="details" width="90%">
                    <tbody><tr>
                        <th align="left">category</th>
                        <th align="left">tool</th>
                        <th align="left">priority</th>
                        <th align="left">rule</th>
                        <th align="left">count</th>
                    </tr>
 
                    <tr>
                            <td colspan="5">
                                <xsl:value-of select="$category1"></xsl:value-of>
                            </td>
                        </tr>
 
                        <tr class="alternate">
                                <td></td>
                                <td>
                                    <xsl:value-of select="@tool"></xsl:value-of>
                                </td>
                                <td align="left">
                                    <xsl:value-of select="@priority"></xsl:value-of>
                                </td>
                                <td>
                                    <xsl:if test="'findbugs'=@tool">
                                        <xsl:call-template name="findbugs_link"></xsl:call-template>
                                    </xsl:if>
                                    <xsl:if test="'pmd'=@tool">
                                        <xsl:call-template name="pmd_link"></xsl:call-template>
                                    </xsl:if>
                                    <xsl:if test="'checkstyle'=@tool">
                                        <xsl:value-of select="@rule"></xsl:value-of>
                                    </xsl:if>
                                </td>
                                <td align="center">
                                    <xsl:value-of select="count( current-group() )"></xsl:value-of>
                                </td>
                            </tr>
                         
                     
                    <tr class="dark">
                        <td colspan="4"></td>
                        <td align="center">
                            <xsl:value-of select="count( .//message )"></xsl:value-of>
                        </td>
                    </tr>
                </tbody></table>
                <p>
                 
                </p><h3>Summary Files</h3>
                <xsl:for-each-group group-by="@name" select="file">
                        <xsl:sort order="ascending" select="@name"></xsl:sort>
                        <xsl:variable name="fileName" select="@name"></xsl:variable>
     
                        </xsl:for-each-group><table border="0" class="details" width="90%">
                    <tbody><tr>
                        <th align="left" width="90pt">class file</th>
                        <th align="center" width="90pt">high</th>
                        <th align="center" width="90pt">medium</th>
                        <th align="center" width="90pt">low</th>
                        <th align="center" width="90pt">total</th>
                    </tr>
                    <tr class="alternate">
                            <td>
                                <xsl:call-template name="out_element_link"></xsl:call-template>
                            </td>
                            <td align="center">
                                <div class="p1">
                                    <xsl:value-of select="count(//file[@name=$fileName]/message[@priority = 1])"></xsl:value-of>
                                </div>
                            </td>
                            <td align="center">
                                <div class="p2">
                                    <xsl:value-of select="count(//file[@name=$fileName]/message[@priority = 2])"></xsl:value-of>
                                </div>
                            </td>
                            <td align="center">
                                <div class="p3">
                                    <xsl:value-of select="count(//file[@name=$fileName]/message[@priority = 3])"></xsl:value-of>
                                </div>
                            </td>
                            <td align="center">
                                <div class="p3">
                                    <xsl:value-of select="count(//file[@name=$fileName]/message)"></xsl:value-of>
                                </div>
                            </td>
                        </tr>
                     
                    <tr class="dark">
                        <td></td>
                        <td align="center">
                            <div class="p1">
                                <xsl:value-of select="count(.//message[@priority = 1])"></xsl:value-of>
                            </div>
                        </td>
                        <td align="center">
                            <div class="p2">
                                <xsl:value-of select="count(.//message[@priority = 2])"></xsl:value-of>
                            </div>
                        </td>
                        <td align="center">
                            <div class="p3">
                                <xsl:value-of select="count(.//message[@priority = 3])"></xsl:value-of>
                            </div>
                        </td>
                        <td align="center">
                            <div class="p3">
                                <xsl:value-of select="count(.//message)"></xsl:value-of>
                            </div>
                        </td>
                    </tr>
                </tbody></table>
                <p>
                 
                </p><h3>Details by Class</h3>
                 <xsl:for-each-group group-by="@name" select="file"> 
                <xsl:sort order="ascending" select="@name"></xsl:sort>
                    <xsl:call-template name="file_detail"></xsl:call-template>
                </xsl:for-each-group> 
                                 
             
    </xsl:template>
 
    <xsl:template name="file_detail">
        <xsl:variable name="fileName" select="@name"></xsl:variable>
        <xsl:for-each select="//file[@name=$fileName]/message">
                <xsl:sort order="ascending" select="@priority"></xsl:sort>
                </xsl:for-each><table border="0" class="details" width="90%">
            <tbody><tr><th align="left" colspan="6">
                <xsl:call-template name="out_element_anker"></xsl:call-template>
            </th>
            </tr><tr>
                <th align="left" width="80pt">tool</th>
                <th align="left" width="50pt">priority</th>
                <th align="left" width="30pt">line</th>
                <th align="left" width="80pt">category</th>
                <th align="left" width="380pt">rule</th>
                <th align="left">message</th>
            </tr>            
                 
            <tr class="alternate">
                    <td>
                        <xsl:value-of select="@tool"></xsl:value-of>
                    </td>
                    <td>
                        <xsl:value-of select="@priority"></xsl:value-of>
                    </td>
                    <td>
                        <xsl:value-of select="@line"></xsl:value-of>
                    </td>
                    <td>
                        <xsl:value-of select="@category"></xsl:value-of>
                    </td>
                    <td>
                        <xsl:value-of select="@rule"></xsl:value-of>
                    </td>
                    <td>
                        <xsl:value-of select="@message"></xsl:value-of>
                    </td>
                </tr>
             
        </tbody></table>
         
 
    </xsl:template>
 
    <xsl:template name="out_whitespace">
        <xsl:text disable-output-escaping="no"> </xsl:text>
    </xsl:template>
 
    <xsl:template name="out_key">
        <xsl:value-of select="@name"></xsl:value-of>
    </xsl:template>
 
    <xsl:template name="out_element">
        <xsl:value-of select="@name"></xsl:value-of>
    </xsl:template>
 
    <xsl:template name="out_element_anker">
        <xsl:text disable-output-escaping="yes"><a name="</xsl:text>
        <xsl:call-template name="out_key"></xsl:call-template>
        <xsl:text disable-output-escaping="yes">"></xsl:text>
        <xsl:call-template name="out_element"></xsl:call-template>
        <xsl:text disable-output-escaping="yes"></a></xsl:text>
    </xsl:template>
 
    <xsl:template name="out_element_link">
        <xsl:text disable-output-escaping="yes"><a href="#</xsl:text>
        <xsl:call-template name="out_key"></xsl:call-template>
        <xsl:text disable-output-escaping="yes">" title="</xsl:text>
        <xsl:call-template name="out_element"></xsl:call-template>
        <xsl:text disable-output-escaping="yes">"></xsl:text>
        <xsl:variable name="name1">
            <xsl:value-of select="@name"></xsl:value-of>
        </xsl:variable>
        <xsl:if test="(''=$name1)">
            <xsl:call-template name="out_element"></xsl:call-template>
        </xsl:if>
        <xsl:value-of select="@name"></xsl:value-of>
        <xsl:text disable-output-escaping="yes"></a></xsl:text>
    </xsl:template>
 
    <xsl:template name="findbugs_link">
        <xsl:text disable-output-escaping="yes"> <a
            href="http://findbugs.sourceforge.net/bugDescriptions.html#</xsl:text>
        <xsl:value-of select="@rule_id"></xsl:value-of>
        <xsl:text disable-output-escaping="yes">"></xsl:text>
        <xsl:value-of select="@rule"></xsl:value-of>
        <xsl:text disable-output-escaping="yes"></a></xsl:text>
    </xsl:template>
 
    <xsl:template name="pmd_link">
        <xsl:text disable-output-escaping="yes"> <a
            href="http://pmd.sourceforge.net/rules/basic.html#</xsl:text>
        <xsl:value-of select="@rule"></xsl:value-of>
        <xsl:text disable-output-escaping="yes">"></xsl:text>
        <xsl:value-of select="@rule"></xsl:value-of>
        <xsl:text disable-output-escaping="yes"></a></xsl:text>
    </xsl:template>
</xsl:stylesheet>

Conclusion

As you can see XSL Transformations are not really difficult. The nice thing is, that this program can be changed and extended in a easy way. To implement the same function with pure Java would need more code or at least I would need more. 

References

[1]  FindBugs
Is a static code analysis tool that analyses Java bytecode and detects a wide range of problems.
[2]  Checkstyle
Is a development tool to help programmers write Java code that adheres to a coding standard.
[3]  PMD
Scans source code and looks for potential problems possible bugs, unused and sub-optimal code and over-complicated expressions. 
[4]  Sonar
Sonar is an open platform to manage code quality.
[5]  XRadar
XRadar is an open extensible code report tool currently supporting all Java based systems.
[6]   SAXON
The XSLT and XQuery Processor. 
[7]  XSLT Stylesheets
Useful things and other jokes.

Sponsored Link