All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.mojo.findbugs.FindbugsReportGenerator.groovy Maven / Gradle / Ivy

There is a newer version: 3.0.5
Show newest version
package org.codehaus.mojo.findbugs

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

import groovy.util.slurpersupport.GPathResult
import org.apache.maven.doxia.sink.Sink
import org.apache.maven.doxia.tools.SiteTool
import org.apache.maven.plugin.logging.Log
import org.codehaus.plexus.util.PathTool

/**
 * The reporter controls the generation of the FindBugs report. It contains call back methods which gets called by
 * FindBugs if a bug is found.
 *
 * @author Garvin LeClaire
 * @version $Id: FindbugsReportGenerator.groovy Z gleclaire $
 */
class FindbugsReportGenerator implements FindBugsInfo {


	/**
	 * The key to get the value if the line number is not available.
	 *
	 */
	static final String NOLINE_KEY = "report.findbugs.noline"

	/**
	 * The key to get the column title for the line.
	 *
	 */
	static final String COLUMN_LINE_KEY = "report.findbugs.column.line"

	/**
	 * The key to get the column title for the bug.
	 *
	 */
	static final String COLUMN_BUG_KEY = "report.findbugs.column.bug"

	/**
	 * The key to get the column title for the bugs.
	 *
	 */
	static final String COLUMN_BUGS_KEY = "report.findbugs.column.bugs"

	/**
	 * The key to get the column title for the category.
	 *
	 */
	static final String COLUMN_CATEGORY_KEY = "report.findbugs.column.category"

	/**
	 * The key to get the column title for the priority.
	 *
	 */
	static final String COLUMN_PRIORITY_KEY = "report.findbugs.column.priority"

	/**
	 * The key to get the column title for the details.
	 *
	 */
	static final String COLUMN_DETAILS_KEY = "report.findbugs.column.details"

	/**
	 * The key to get the report title of the Plug-In from the bundle.
	 *
	 */
	static final String REPORT_TITLE_KEY = "report.findbugs.reporttitle"

	/**
	 * The key to get the report link title of the Plug-In from the bundle.
	 *
	 */
	static final String LINKTITLE_KEY = "report.findbugs.linktitle"

	/**
	 * The key to get the report link of the Plug-In from the bundle.
	 *
	 */
	static final String LINK_KEY = "report.findbugs.link"

	/**
	 * The key to get the files title of the Plug-In from the bundle.
	 *
	 */
	static final String FILES_KEY = "report.findbugs.files"

	/**
	 * The key to get the threshold of the report from the bundle.
	 *
	 */
	static final String THRESHOLD_KEY = "report.findbugs.threshold"

	/**
	 * The key to get the effort of the report from the bundle.
	 *
	 */
	static final String EFFORT_KEY = "report.findbugs.effort"

	/**
	 * The key to get the link to FindBugs description page from the bundle.
	 *
	 */
	static final String DETAILSLINK_KEY = "report.findbugs.detailslink"

	/**
	 * The key to get the version title for FindBugs from the bundle.
	 *
	 */
	static final String VERSIONTITLE_KEY = "report.findbugs.versiontitle"

	/**
	 * The key to get the files title of the Plug-In from the bundle.
	 *
	 */
	static final String SUMMARY_KEY = "report.findbugs.summary"

	/**
	 * The key to column title for the Class.
	 *
	 */
	static final String COLUMN_CLASS_KEY = "report.findbugs.column.class"

	/**
	 * The key to column title for the Classes.
	 *
	 */
	static final String COLUMN_CLASSES_KEY = "report.findbugs.column.classes"

	/**
	 * The key to column title for the errors.
	 *
	 */
	static final String COLUMN_ERRORS_KEY = "report.findbugs.column.errors"

	/**
	 * The key to column title for the files.
	 *
	 */
	static final String COLUMN_FILES_KEY = "report.findbugs.column.files"

	/**
	 * The key to column title for the files.
	 *
	 */
	static final String COLUMN_MISSINGCLASSES_KEY = "report.findbugs.column.missingclasses"


	/**
	 * The sink to write the report to.
	 *
	 */
	Sink sink

	/**
	 * The bundle to get the messages from.
	 *
	 */
	ResourceBundle bundle

	/**
	 * The logger to write logs to.
	 *
	 */
	Log log


	/**
	 * The threshold of bugs severity.
	 *
	 */
	String threshold

	/**
	 * The used effort for searching bugs.
	 *
	 */
	String effort

	/**
	 * The name of the current class which is analysed by FindBugs.
	 *
	 */
	String currentClassName

	/**
	 * Signals if the report for the current class is opened.
	 *
	 */
	boolean mIsCurrentClassReportOpened = false

	/**
	 * Signals if the jxr report plugin is enabled.
	 *
	 */
	boolean isJXRReportEnabled = false

	/**
	 * The running total of bugs reported.
	 *
	 */
	int bugCount

	/**
	 * The running total of missing classes reported.
	 *
	 */
	int missingClassCount

	/**
	 * The running total of files analyzed.
	 *
	 */
	int fileCount

	/**
	 * The Set of missing classes names reported.
	 *
	 */
	Set missingClassSet = new HashSet()

	/**
	 * The running total of errors reported.
	 *
	 */
	int errorCount

	/**
	 * Location where generated html will be created.
	 *
	 */

	File outputDirectory

	/**
	 * Location of the Xrefs to link to.
	 *
	 */
	File xrefLocation

	/**
	 * Location of the Test Xrefs to link to.
	 *
	 */
	File xrefTestLocation

	/**
	 * The directories containing the sources to be compiled.
	 *
	 */
	List compileSourceRoots

	/**
	 * The directories containing the test-sources to be compiled.
	 *
	 */
	List testSourceRoots

	/**
	 * Run Findbugs on the tests.
	 *
	 */
	boolean includeTests

	/**
	 * "org.apache.maven.doxia.tools.SiteTool"
	 *
	 */
	SiteTool siteTool



	File basedir

	GPathResult findbugsResults



	List bugClasses

	/**
	 * Default constructor.
	 *
	 * @param sink
	 *            The sink to generate the report.
	 * @param bundle
	 *            The resource bundle to get the messages from.
	 * @param basedir
	 *            The project base directory.
	 * @param siteTool
	 *            Doxia SiteTool Handle.
	 */
	FindbugsReportGenerator(Sink sink, ResourceBundle bundle, File basedir, SiteTool siteTool) {


		assert sink
		assert bundle
		assert basedir
		assert siteTool

		this.sink = sink
		this.bundle = bundle
		this.basedir = basedir
		this.siteTool = siteTool


		this.bugClasses = []

		this.currentClassName = ""

		this.bugCount = 0
		this.missingClassCount = 0
		this.errorCount = 0
		this.fileCount = 0
	}


	/**
	 * @see edu.umd.cs.findbugs.BugReporter#finish()
	 */
	void printBody() {
		log.debug("Finished searching for bugs!...")
		log.debug("sink is " + sink)

		bugClasses.each() {bugClass ->
			log.debug("finish bugClass is ${bugClass}")

			printBug(bugClass)
		}

		// close the report, write it
		sink.body_()

	}

	/**
	 * Prints the top header sections of the report.
	 */
	private void doHeading() {
		sink.head()
		sink.title()
		sink.text(getReportTitle())
		sink.title_()
		sink.head_()

		sink.body()

		// the title of the report
		sink.section1()
		sink.sectionTitle1()
		sink.text(getReportTitle())
		sink.sectionTitle1_()

		// information about FindBugs
		sink.paragraph()
		sink.text(bundle.getString(LINKTITLE_KEY) + FindBugsInfo.BLANK)
		sink.link(bundle.getString(LINK_KEY))
		sink.text(bundle.getString(FindBugsInfo.NAME_KEY))
		sink.link_()
		sink.paragraph_()

		sink.paragraph()
		sink.text(bundle.getString(VERSIONTITLE_KEY) + FindBugsInfo.BLANK)
		sink.italic()
		sink.text(edu.umd.cs.findbugs.Version.RELEASE)
		sink.italic_()
		sink.paragraph_()

		sink.paragraph()
		sink.text(bundle.getString(THRESHOLD_KEY) + FindBugsInfo.BLANK)
		sink.italic()
		sink.text(FindBugsInfo.findbugsThresholds.get(threshold))
		sink.italic_()
		sink.paragraph_()

		sink.paragraph()
		sink.text(bundle.getString(EFFORT_KEY) + FindBugsInfo.BLANK)
		sink.italic()
		sink.text(FindBugsInfo.findbugsEfforts.get(effort))
		sink.italic_()
		sink.paragraph_()
		sink.section1_()

	}

	/**
	 * Print the bug collection to a line in the table
	 *
	 * @param bugInstance
	 *            the bug to print
	 */
	protected void printBug(String bugClass) {


		log.debug("printBug bugClass is ${bugClass}")

		openClassReportSection(bugClass)


		log.debug("printBug findbugsResults is ${findbugsResults}")

		findbugsResults.BugInstance.each() {bugInstance ->


			log.debug("bugInstance --->  ${bugInstance}")

			if ( bugInstance.Class[0][email protected]() == bugClass ) {

				def type = [email protected]()
				def category = [email protected]()
				def message = bugInstance.LongMessage.text()
				def priority = [email protected]()
				def line = bugInstance.SourceLine[0]
				log.debug("BugInstance message is ${message}")

				sink.tableRow()

				// bug
				sink.tableCell()
				sink.text(message)
				sink.tableCell_()

				// category
				sink.tableCell()
				sink.text(category)
				sink.tableCell_()

				// description link
				sink.tableCell()
				sink.link(bundle.getString(DETAILSLINK_KEY) + "#" + type)
				sink.text(type)
				sink.link_()
				sink.tableCell_()

				// line
				sink.tableCell()

				if ( isJXRReportEnabled ) {
					log.debug("isJXRReportEnabled is enabled")
					sink.rawText(assembleJxrHyperlink(line))
				} else {
					sink.text([email protected]())
				}

				sink.tableCell_()

				// priority
				sink.tableCell()
				sink.text(findbugsPriority[priority.toInteger()])
				sink.tableCell_()

				sink.tableRow_()
			}
		}

		sink.table_()

		sink.section2_()

	}

	/**
	 * Assembles the hyperlink to point to the source code.
	 *
	 * @param line
	 *            The line number object with the bug.
	 * @param lineNumber
	 *            The line number to show in the hyperlink.
	 * @return The hyperlink which points to the code.
	 *
	 */
	protected String assembleJxrHyperlink(GPathResult line) {
		String hyperlink
		String prefix


		log.debug("Inside assembleJxrHyperlink")
		log.debug("line is " + line.text())
		log.debug("outputDirectory is " + outputDirectory.getAbsolutePath())
		log.debug("xrefLocation is " + xrefLocation.getAbsolutePath())
		log.debug("xrefTestLocation is " + xrefTestLocation.getAbsolutePath())

		compileSourceRoots.each {compileSourceRoot ->
			if ( new File(compileSourceRoot + File.separator + [email protected]()).exists() ) {
				prefix = PathTool.getRelativePath(outputDirectory.getAbsolutePath(), xrefLocation.getAbsolutePath())

				prefix = prefix ? prefix + FindBugsInfo.URL_SEPARATOR + xrefLocation.getName() + FindBugsInfo.URL_SEPARATOR : FindBugsInfo.PERIOD
				return
			}
		}


		if ( includeTests && !prefix ) {
			testSourceRoots.each {testSourceRoot ->
				if ( new File(testSourceRoot + File.separator + [email protected]()).exists() ) {
					prefix = PathTool.getRelativePath(outputDirectory.getAbsolutePath(), xrefTestLocation.getAbsolutePath())

					prefix = prefix ? prefix + FindBugsInfo.URL_SEPARATOR + xrefTestLocation.getName() + FindBugsInfo.URL_SEPARATOR : FindBugsInfo.PERIOD
					return
				}
			}
		}

		def path = prefix + [email protected]().replaceAll("[.]", "/").replaceAll("[\$].*", "")
		String lineNumber = valueForLine(line)

		if ( lineNumber != bundle.getString(NOLINE_KEY) ) {
			hyperlink = "" + lineNumber + ""
		} else {
			hyperlink = lineNumber
		}

		return hyperlink
	}

	/**
	 * Gets the report title.
	 *
	 * @return The report title.
	 *
	 */
	protected String getReportTitle() {
		return bundle.getString(REPORT_TITLE_KEY)
	}

	/**
	 * Initialised a bug report section in the report for a particular class.
	 */
	protected void openClassReportSection(String bugClass) {
		String columnBugText = bundle.getString(COLUMN_BUG_KEY)
		String columnBugCategory = bundle.getString(COLUMN_CATEGORY_KEY)
		String columnDescriptionLink = bundle.getString(COLUMN_DETAILS_KEY)
		String columnLineText = bundle.getString(COLUMN_LINE_KEY)
		String priorityText = bundle.getString(COLUMN_PRIORITY_KEY)

		log.debug("openClassReportSection bugClass is ${bugClass}")

		log.debug("Opening Class Report Section")

		sink.anchor(bugClass)
		sink.anchor_()

		sink.section2()
		sink.sectionTitle2()
		sink.text(bugClass)
		sink.sectionTitle2_()
		sink.table()
		sink.tableRow()

		// bug
		sink.tableHeaderCell()
		sink.text(columnBugText)
		sink.tableHeaderCell_()

		// category
		sink.tableHeaderCell()
		sink.text(columnBugCategory)
		sink.tableHeaderCell_()

		// description link
		sink.tableHeaderCell()
		sink.text(columnDescriptionLink)
		sink.tableHeaderCell_()

		// line
		sink.tableHeaderCell()
		sink.text(columnLineText)
		sink.tableHeaderCell_()

		// priority
		sink.tableHeaderCell()
		sink.text(priorityText)
		sink.tableHeaderCell_()

		sink.tableRow_()
	}

	/**
	 * Print the Summary Section.
	 */
	protected void printSummary() {

		log.debug("Entering printSummary")

		sink.section1()

		// the summary section
		sink.sectionTitle1()
		sink.text(bundle.getString(SUMMARY_KEY))
		sink.sectionTitle1_()

		sink.table()
		sink.tableRow()

		// classes
		sink.tableHeaderCell()
		sink.text(bundle.getString(COLUMN_CLASSES_KEY))
		sink.tableHeaderCell_()

		// bugs
		sink.tableHeaderCell()
		sink.text(bundle.getString(COLUMN_BUGS_KEY))
		sink.tableHeaderCell_()

		// Errors
		sink.tableHeaderCell()
		sink.text(bundle.getString(COLUMN_ERRORS_KEY))
		sink.tableHeaderCell_()

		// Missing Classes
		sink.tableHeaderCell()
		sink.text(bundle.getString(COLUMN_MISSINGCLASSES_KEY))
		sink.tableHeaderCell_()

		sink.tableRow_()

		sink.tableRow()

		// files
		sink.tableCell()
		sink.text(findbugsResults.FindBugsSummary.@total_classes.text())
		sink.tableCell_()

		// bug
		sink.tableCell()
		sink.text(findbugsResults.FindBugsSummary.@total_bugs.text())
		sink.tableCell_()

		// Errors
		sink.tableCell()
		sink.text([email protected]())
		sink.tableCell_()

		// Missing Classes
		sink.tableCell()
		sink.text([email protected]())
		sink.tableCell_()

		sink.tableRow_()
		sink.table_()

		sink.section1_()

		log.debug("Exiting printSummary")
	}

	/**
	 * Print the File Summary Section.
	 */
	protected void printFilesSummary() {
		log.debug("Entering printFilesSummary")

		sink.section1()

		// the Files section
		sink.sectionTitle1()
		sink.text(bundle.getString(FILES_KEY))
		sink.sectionTitle1_()

		/**
		 * Class Summary
		 */

		sink.table()
		sink.tableRow()

		// files
		sink.tableHeaderCell()
		sink.text(bundle.getString(COLUMN_CLASS_KEY))
		sink.tableHeaderCell_()

		// bugs
		sink.tableHeaderCell()
		sink.text(bundle.getString(COLUMN_BUGS_KEY))
		sink.tableHeaderCell_()

		sink.tableRow_()

		findbugsResults.FindBugsSummary.PackageStats.ClassStats.each() {classStats ->

			def classStatsValue = classStats.'@class'.text()
			def classStatsBugCount = classStats.'@bugs'.text()

			if ( classStatsBugCount.toInteger() > 0 ) {
				sink.tableRow()

				// class name
				sink.tableCell()
				sink.link("#" + classStatsValue)
				sink.text(classStatsValue)
				sink.link_()
				sink.tableCell_()

				// class bug total count
				sink.tableCell()
				sink.text(classStatsBugCount)
				sink.tableCell_()

				sink.tableRow_()


				bugClasses << classStatsValue
			}
		}



		sink.table_()

		sink.section1_()

		log.debug("Exiting printFilesSummary")
	}

	public void generateReport() {

		log.debug("Reporter Locale is " + this.bundle.getLocale().getLanguage())

		doHeading()

		printSummary()

		printFilesSummary()

		printBody()

		log.debug("Closing up report....................")

		sink.flush()
		sink.close()
	}

	/**
	 * Return the value to display. If FindBugs does not provide a line number, a default message is returned. The line
	 * number otherwise.
	 *
	 * @param line
	 *            The line to get the value from.
	 * @return The line number the bug appears or a statement that there is no source line available.
	 *
	 */
	protected String valueForLine(GPathResult line)
	{
		String value

		if ( line ) {
			def startLine = [email protected]()
			def endLine = [email protected]()

			if ( startLine == endLine ) {
				if ( startLine ) {
					value = startLine
				} else {
					value = bundle.getString(NOLINE_KEY)
				}
			} else {
				value = startLine + "-" + endLine
			}
		} else {
			value = bundle.getString(NOLINE_KEY)
		}

		return value
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy