Added WriteTag check which outputs a JavaDoc tag as information (patch 902110). Thanks to Daniel Grenner (dgrenner) for contribution.

This commit is contained in:
Oleg Sukhodolsky 2005-12-26 07:27:59 +00:00
parent ea4085cd15
commit 96c668724b
10 changed files with 776 additions and 1 deletions

View File

@ -334,6 +334,7 @@
<mkdir dir="${target.dir}/style/noframes"/>
<mkdir dir="${target.dir}/style/simple"/>
<mkdir dir="${target.dir}/style/csv"/>
<mkdir dir="target/style/author"/>
<style basedir="${target.dir}"
destdir="${target.dir}/style/noframes"
includes="cs_errors.xml"
@ -349,6 +350,10 @@
<style out="${target.dir}/style/csv/report-csv.txt"
in="${target.dir}/cs_errors.xml"
style="contrib/checkstyle-csv.xsl"/>
<style basedir="target"
destdir="target/style/author"
includes="cs_errors.xml"
style="contrib/checkstyle-author.xsl"/>
</target>
<!-- Targets to verify that JUnit is in the classpath -->

View File

@ -0,0 +1,230 @@
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output method="html" indent="yes"/>
<xsl:decimal-format decimal-separator="." grouping-separator=","/>
<!--
Checkstyle XML Style Sheet by Daniel Grenner
<daniel DOT grenner AT enea DOT se>
This stylesheet groups the errors by author name, if used in combination
with the WriteTag check:
<module name="WriteTag">
<property name="tag" value="@author"/>
<property name="tagFormat" value="\S"/>
<property name="severity" value="ignore"/>
</module>
The output contains both error and warning messages.
Files without errors or warnings are not included.
This stylesheet is based on checkstyle-noframes.xsl.
-->
<xsl:key name="keyAuthorID" match="//file/error[contains(@message,'@author')]" use="@message" />
<xsl:key name="keySeverityID" match="//file/error" use="@severity" />
<xsl:template match="checkstyle">
<html>
<head>
<style type="text/css">
.bannercell {
border: 0px;
padding: 0px;
}
body {
margin-left: 10;
margin-right: 10;
font:normal 68% verdana,arial,helvetica;
background-color:#FFFFFF;
color:#000000;
}
.a td {
background: #efefef;
}
.b td {
background: #fff;
}
th, td {
text-align: left;
vertical-align: top;
}
th {
font-weight:bold;
background: #ccc;
color: black;
}
table, th, td {
font-size:100%;
border: none
}
table.log tr td, tr th {
}
h2 {
font-weight:bold;
font-size:140%;
margin-bottom: 5;
}
h3 {
font-size:100%;
font-weight:bold;
background: #525D76;
color: white;
text-decoration: none;
padding: 5px;
margin-right: 2px;
margin-left: 2px;
margin-bottom: 0;
}
</style>
</head>
<body>
<a name="#top"/>
<!-- jakarta logo -->
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td class="bannercell" rowspan="2">
<!--a href="http://jakarta.apache.org/">
<img src="http://jakarta.apache.org/images/jakarta-logo.gif" alt="http://jakarta.apache.org" align="left" border="0"/>
</a-->
</td>
<td class="text-align:right">
<h2>CheckStyle Audit</h2>
</td>
</tr>
<tr>
<td class="text-align:right">Designed for use with <a href="http://checkstyle.sourceforge.net/">CheckStyle</a> and <a href="http://jakarta.apache.org">Ant</a>.</td>
</tr>
</table>
<hr size="1"/>
<!-- Summary part -->
<xsl:apply-templates select="." mode="summary"/>
<hr size="1" width="100%" align="left"/>
<!-- Author List part -->
<xsl:apply-templates select="." mode="authorlist"/>
<hr size="1" width="100%" align="left"/>
<!-- For each package create its part -->
<xsl:for-each select="file">
<xsl:sort select="./error[contains(@message,'@author=')]/@message"/>
<xsl:apply-templates select="."/>
<p/>
<p/>
</xsl:for-each>
<hr size="1" width="100%" align="left"/>
</body>
</html>
</xsl:template>
<xsl:template match="checkstyle" mode="summary">
<h3>Summary</h3>
<xsl:variable name="fileCount" select="count(file)"/>
<xsl:variable name="errorCount" select="count(key('keySeverityID', 'error'))"/>
<xsl:variable name="warningCount" select="count(key('keySeverityID', 'warning'))"/>
<table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">
<tr>
<th>Files</th>
<th>Errors</th>
<th>Warnings</th>
</tr>
<tr>
<xsl:call-template name="alternated-row"/>
<td>
<xsl:value-of select="$fileCount"/>
</td>
<td>
<xsl:value-of select="$errorCount"/>
</td>
<td>
<xsl:value-of select="$warningCount"/>
</td>
</tr>
</table>
</xsl:template>
<xsl:template match="checkstyle" mode="authorlist">
<h3>Authors</h3>
<table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">
<tr>
<th>Name</th>
<th>Errors</th>
<th>Warnings</th>
</tr>
<!-- Process each Author -->
<xsl:for-each select="file/error[generate-id(.) = generate-id(key('keyAuthorID', @message)[1])]">
<xsl:sort select="@message" />
<xsl:variable name="authorFull" select="@message"/>
<xsl:variable name="author" select="substring-after($authorFull,'@author=')"/>
<xsl:variable name="errors" select="count(key('keyAuthorID', @message)/../error[@severity='error'])"/>
<xsl:variable name="warnings" select="count(key('keyAuthorID', @message)/../error[@severity='warning'])"/>
<xsl:if test = "not ($author='' or ($errors + $warnings = 0))">
<tr>
<xsl:call-template name="alternated-row"/>
<td>
<a href="#{$author}">
<xsl:value-of select="$author"/>
</a>
</td>
<td><xsl:value-of select="$errors"/></td>
<td><xsl:value-of select="$warnings"/></td>
</tr>
</xsl:if>
</xsl:for-each>
</table>
</xsl:template>
<xsl:template match="file">
<xsl:variable name="errorCount" select="count(error[@severity='error']) + count(error[@severity='warning'])"/>
<xsl:if test="not ($errorCount=0)">
<xsl:variable name="author" select="substring-after(./error[contains(@message,'@author')]/@message,'@author=')"/>
<a name="#{$author}"/>
<h3>File <xsl:value-of select="@name"/>
<br/>
Author <xsl:value-of select="$author"/>
</h3>
<table class="log" border="0" cellpadding="5" cellspacing="2" width="100%">
<tr>
<th>Error Description</th>
<th>Line</th>
</tr>
<xsl:for-each select="error[not(@severity='info')]">
<xsl:sort select="@line" data-type="number"/>
<tr>
<xsl:call-template name="alternated-row"/>
<td>
<xsl:value-of select="@message"/>
</td>
<td>
<xsl:value-of select="@line"/>
</td>
</tr>
</xsl:for-each>
</table>
<a href="#top">Back to top</a>
</xsl:if>
</xsl:template>
<xsl:template name="basename">
<xsl:param name="path"/>
<xsl:choose>
<xsl:when test="contains($path, '\')">
<xsl:call-template name="basename">
<xsl:with-param name="path">substring-after($path, '\')</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$path"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="alternated-row">
<xsl:attribute name="class"><xsl:if test="position() mod 2 = 1">a</xsl:if><xsl:if test="position() mod 2 = 0">b</xsl:if></xsl:attribute>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,230 @@
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2005 Oliver Burn
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.checks.javadoc;
import org.apache.commons.beanutils.ConversionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import com.puppycrawl.tools.checkstyle.api.Check;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.FileContents;
import com.puppycrawl.tools.checkstyle.api.Scope;
import com.puppycrawl.tools.checkstyle.api.ScopeUtils;
import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
import com.puppycrawl.tools.checkstyle.api.TextBlock;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.api.Utils;
/**
* <p>
* Outputs a JavaDoc tag as information. Can be used e.g. with the stylesheets
* that sort the report by author name.
* To define the format for a tag, set property tagFormat to a
* regular expression.
* This check uses two different severity levels. The normal one is used for
* reporting when the tag is missing. The additional one (tagSeverity) is used
* for the level of reporting when the tag exists. The default value for
* tagSeverity is info.
* </p>
* <p> An example of how to configure the check for printing author name is:
*</p>
* <pre>
* &lt;module name="WriteTag"&gt;
* &lt;property name="tag" value="@author"/&gt;
* &lt;property name="tagFormat" value="\S"/&gt;
* &lt;/module&gt;
* </pre>
* <p> An example of how to configure the check to print warnings if an
* "@incomplete" tag is found, and not print anything if it is not found:
*</p>
* <pre>
* &lt;module name="WriteTag"&gt;
* &lt;property name="tag" value="@incomplete"/&gt;
* &lt;property name="tagFormat" value="\S"/&gt;
* &lt;property name="severity" value="ignore"/&gt;
* &lt;property name="tagSeverity" value="warning"/&gt;
* &lt;/module&gt;
* </pre>
*
* @author Daniel Grenner
* @version 1.0
*/
public class WriteTagCheck
extends Check
{
/** compiled regexp to match tag **/
private Pattern mTagRE;
/** compiled regexp to match tag content **/
private Pattern mTagFormatRE;
/** regexp to match tag */
private String mTag;
/** regexp to match tag content */
private String mTagFormat;
/** the severity level of found tag reports */
private SeverityLevel mTagSeverityLevel = SeverityLevel.INFO;
/**
* Sets the tag to check.
* @param aTag tag to check
* @throws ConversionException If the tag is not a valid regular exception.
*/
public void setTag(String aTag)
throws ConversionException
{
try {
mTag = aTag;
mTagRE = Utils.getPattern(aTag + "\\s+(.*$)");
}
catch (PatternSyntaxException e) {
throw new ConversionException("unable to parse " + aTag, e);
}
}
/**
* Set the tag format.
* @param aFormat a <code>String</code> value
* @throws ConversionException unable to parse aFormat
*/
public void setTagFormat(String aFormat)
throws ConversionException
{
try {
mTagFormat = aFormat;
mTagFormatRE = Utils.getPattern(aFormat);
}
catch (PatternSyntaxException e) {
throw new ConversionException("unable to parse " + aFormat, e);
}
}
/**
* Sets the tag severity level. The string should be one of the names
* defined in the <code>SeverityLevel</code> class.
*
* @param aSeverity The new severity level
* @see SeverityLevel
*/
public final void setTagSeverity(String aSeverity)
{
mTagSeverityLevel = SeverityLevel.getInstance(aSeverity);
}
/** {@inheritDoc} */
public int[] getDefaultTokens()
{
return new int[] {TokenTypes.INTERFACE_DEF, TokenTypes.CLASS_DEF, };
}
/** {@inheritDoc} */
public int[] getAcceptableTokens()
{
return new int[] {TokenTypes.INTERFACE_DEF,
TokenTypes.CLASS_DEF,
TokenTypes.METHOD_DEF,
};
}
/** {@inheritDoc} */
public void visitToken(DetailAST aAST)
{
final DetailAST mods = aAST.findFirstToken(TokenTypes.MODIFIERS);
final Scope declaredScope = ScopeUtils.getScopeFromMods(mods);
final Scope typeScope =
ScopeUtils.inInterfaceBlock(aAST) ? Scope.PUBLIC : declaredScope;
final FileContents contents = getFileContents();
final int lineNo = aAST.getLineNo();
final TextBlock cmt =
contents.getJavadocBefore(lineNo);
if (cmt == null) {
log(lineNo, "type.missingTag", mTag);
}
else {
checkTag(lineNo, cmt.getText(), mTag, mTagRE, mTagFormatRE,
mTagFormat);
}
}
/**
* Verifies that a type definition has a required tag.
* @param aLineNo the line number for the type definition.
* @param aCmt the Javadoc comment for the type definition.
* @param aTag the required tag name.
* @param aTagRE regexp for the full tag.
* @param aFormatRE regexp for the tag value.
* @param aFormat pattern for the tag value.
*/
private void checkTag(
int aLineNo,
String[] aCmt,
String aTag,
Pattern aTagRE,
Pattern aFormatRE,
String aFormat)
{
if (aTagRE == null) {
return;
}
int tagCount = 0;
for (int i = 0; i < aCmt.length; i++) {
final String s = aCmt[i];
final Matcher matcher = aTagRE.matcher(s);
if (matcher.find()) {
tagCount += 1;
final int contentStart = matcher.start(1);
final String content = s.substring(contentStart);
if (aFormatRE != null && !aFormatRE.matcher(content).find()) {
log(aLineNo + i - aCmt.length, "type.tagFormat", aTag,
aFormat);
}
else {
logTag(aLineNo + i - aCmt.length, aTag, content);
}
}
}
if (tagCount == 0) {
Object[] args = {aTag};
log(aLineNo, "type.missingTag", aTag);
}
}
/**
* Log a message.
*
* @param aLine the line number where the error was found
* @param aTag the javdoc tag to be logged
* @param aTagValue the contents of the tag
*
* @see java.text.MessageFormat
*/
protected final void logTag(int aLine, String aTag, String aTagValue)
{
String originalSeverity = getSeverity();
setSeverity(mTagSeverityLevel.getName());
log(aLine, "javadoc.writeTag", aTag, aTagValue);
setSeverity(originalSeverity);
}
}

View File

@ -11,6 +11,7 @@ javadoc.unclosedhtml=Unclosed HTML tag found: {0}
javadoc.unusedTag=Unused {0} tag for ''{1}''.
javadoc.unusedTagGeneral=Unused Javadoc tag.
javadoc.empty=Javadoc has empty description section.
javadoc.writeTag={0}={1}
type.missingTag=Type Javadoc comment is missing an {0} tag.
type.tagFormat=Type Javadoc tag {0} must match pattern ''{1}''.

View File

@ -0,0 +1,27 @@
////////////////////////////////////////////////////////////////////////////////
// Test case file for checkstyle.
// Created: 2004
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle;
/**
* Testing tag writing
* @author Daniel Grenner
* @incomplete This class needs more code...
* @doubletag first text
* @doubletag second text
*/
class InputWriteTag
{
public void method()
{
}
/**
* @todo Add a comment
*/
public void anotherMethod()
{
}
}

View File

@ -32,7 +32,7 @@ public abstract class BaseCheckTestCase
public void fileStarted(AuditEvent evt) {}
}
private final ByteArrayOutputStream mBAOS = new ByteArrayOutputStream();
protected final ByteArrayOutputStream mBAOS = new ByteArrayOutputStream();
protected final PrintStream mStream = new PrintStream(mBAOS);
protected final Properties mProps = new Properties();

View File

@ -18,6 +18,7 @@ public class AllTests {
suite.addTest(new TestSuite(JavadocTypeCheckTest.class));
suite.addTest(new TestSuite(JavadocVariableCheckTest.class));
suite.addTest(new TestSuite(PackageHtmlCheckTest.class));
suite.addTest(new TestSuite(WriteTagCheckTest.class));
return suite;
}

View File

@ -0,0 +1,173 @@
package com.puppycrawl.tools.checkstyle.checks.javadoc;
import com.puppycrawl.tools.checkstyle.BaseCheckTestCase;
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
import com.puppycrawl.tools.checkstyle.api.Scope;
import com.puppycrawl.tools.checkstyle.Checker;
import java.io.File;
import java.io.ByteArrayInputStream;
import java.io.LineNumberReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
* @author Daniel Grenner
*/
public class WriteTagCheckTest extends BaseCheckTestCase
{
private DefaultConfiguration mCheckConfig;
public void setUp() {
mCheckConfig = createCheckConfig(WriteTagCheck.class);
}
public void testDefaultSettings() throws Exception
{
final String[] expected =
{
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testTag() throws Exception
{
mCheckConfig.addAttribute("tag", "@author");
mCheckConfig.addAttribute("tagFormat", "\\S");
final String[] expected =
{
"10: @author=Daniel Grenner",
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testMissingFormat() throws Exception
{
mCheckConfig.addAttribute("tag", "@author");
final String[] expected =
{
"10: @author=Daniel Grenner",
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testTagSeverity() throws Exception
{
mCheckConfig.addAttribute("tag", "@incomplete");
mCheckConfig.addAttribute("tagFormat", "\\S");
mCheckConfig.addAttribute("tagSeverity", "warning");
final String[] expected =
{
"11: warning: @incomplete=This class needs more code...",
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testDoubleTag() throws Exception
{
mCheckConfig.addAttribute("tag", "@doubletag");
mCheckConfig.addAttribute("tagFormat", "\\S");
final String[] expected =
{
"12: @doubletag=first text",
"13: @doubletag=second text",
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testMissingTag() throws Exception
{
mCheckConfig.addAttribute("tag", "@missingtag");
final String[] expected =
{
"15: Type Javadoc comment is missing an @missingtag tag.",
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testMethod() throws Exception
{
mCheckConfig.addAttribute("tag", "@todo");
mCheckConfig.addAttribute("tagFormat", "\\S");
mCheckConfig.addAttribute("tokens",
"INTERFACE_DEF, CLASS_DEF, METHOD_DEF");
mCheckConfig.addAttribute("severity", "ignore");
final String[] expected =
{
"22: @todo=Add a comment",
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testSeverity() throws Exception
{
mCheckConfig.addAttribute("tag", "@author");
mCheckConfig.addAttribute("tagFormat", "\\S");
mCheckConfig.addAttribute("severity", "ignore");
final String[] expected =
{
"10: @author=Daniel Grenner",
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testIgnoreMissing() throws Exception
{
mCheckConfig.addAttribute("tag", "@todo2");
mCheckConfig.addAttribute("tagFormat", "\\S");
mCheckConfig.addAttribute("severity", "ignore");
final String[] expected =
{
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testRegularEx()
throws Exception
{
mCheckConfig.addAttribute("tag", "@author");
mCheckConfig.addAttribute("tagFormat", "0*");
final String[] expected = {
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
public void testRegularExError()
throws Exception
{
mCheckConfig.addAttribute("tag", "@author");
mCheckConfig.addAttribute("tagFormat", "ABC");
final String[] expected = {
"10: Type Javadoc tag @author must match pattern 'ABC'.",
};
verify(mCheckConfig, getPath("InputWriteTag.java"), expected);
}
protected void verify(Checker aC,
File[] aProcessedFiles,
String aMessageFileName,
String[] aExpected)
throws Exception
{
mStream.flush();
final int errs = aC.process(aProcessedFiles);
// process each of the lines
final ByteArrayInputStream bais =
new ByteArrayInputStream(mBAOS.toByteArray());
final LineNumberReader lnr =
new LineNumberReader(new InputStreamReader(bais));
for (int i = 0; i < aExpected.length; i++) {
final String expected = aMessageFileName + ":" + aExpected[i];
final String actual = lnr.readLine();
assertEquals("error message " + i, expected, actual);
}
assertTrue("unexpected output: " + lnr.readLine(),
aExpected.length >= errs);
aC.destroy();
}
}

View File

@ -682,5 +682,109 @@ public int checkReturnTag(final int aTagIndex,
</p>
</subsection>
</section>
<section name="WriteTag">
<subsection name="Description">
<p>
Outputs a JavaDoc tag as information. Can be used e.g. with the stylesheets
that sort the report by author name.
To define the format for a tag, set property tagFormat to a regular expression.
This check uses two different severity levels. The normal one is used for
reporting when the tag is missing. The additional one (tagSeverity) is used
for the level of reporting when the tag exists.
</p>
</subsection>
<subsection name="Properties">
<table>
<tr>
<th>name</th>
<th>description</th>
<th>type</th>
<th>default value</th>
</tr>
<tr>
<td>tag</td>
<td>Name of tag</td>
<td><a href="property_types.html#string">String</a></td>
<td><span class="default">null</span></td>
</tr>
<tr>
<td>tagFormat</td>
<td>Format of tag</td>
<td><a href="property_types.html#regexp">regular expression</a></td>
<td><span class="default">null</span></td>
</tr>
<tr>
<td>tagSeverity</td>
<td>Severity level when tag is found and printed</td>
<td><a href="property_types.html#severity">severity</a></td>
<td><span class="default">info</span></td>
</tr>
</table>
</subsection>
<subsection name="Examples">
<p>
An example of how to configure the check for printing author name is:
</p>
<source>
&lt;module name="WriteTag"&gt;
&lt;property name="tag" value="@author"/&gt;
&lt;property name="tagFormat" value="\S"/&gt;
&lt;/module&gt;
</source>
<p>
An example of how to configure the check to print warnings if an
"@incomplete" tag is found, and not print anything if it is not found:
</p>
<source>
&lt;module name="WriteTag"&gt;
&lt;property name="tag" value="@incomplete"/&gt;
&lt;property name="tagFormat" value="\S"/&gt;
&lt;property name="severity" value="ignore"/&gt;
&lt;property name="tagSeverity" value="warning"/&gt;
&lt;/module&gt;
</source>
<p>
To configure the check for javadoc which is in <span
class="default">private</span>, but not in <span
class="default">package</span> scope:
</p>
<source>
&lt;module name="JavadocStyle"&gt;
&lt;property name="scope" value="private"/&gt;
&lt;property name="excludeScope" value="package"/&gt;
&lt;/module&gt;
</source>
<p>
To configure the check to turn off first sentence checking:
</p>
<source>
&lt;module name="JavadocStyle"&gt;
&lt;property name="checkFirstSentence" value="false"/&gt;
&lt;/module&gt;
</source>
</subsection>
<subsection name="Package">
<p>
com.puppycrawl.tools.checkstyle.checks
</p>
</subsection>
<subsection name="Parent Module">
<p>
<a href="config.html#treewalker">TreeWalker</a>
</p>
</subsection>
</section>
</body>
</document>

View File

@ -26,6 +26,10 @@
TrailingComment now can be configured to accept some trailing comments
(such as NOI18N) (property legalComment, rfe 1385344).
</li>
<li>
Added WriteTag check which outputs a JavaDoc tag as information (patch 902110)
Thanks to Daniel Grenner (dgrenner) for contribution.
</li>
</ul>
<p>Fixed Bugs:</p>