package hudson.plugins.warnings.parser;
import hudson.plugins.analysis.util.model.Priority;
import java.util.regex.Matcher;
import org.apache.commons.lang.StringUtils;
import edu.umd.cs.findbugs.annotations.SuppressWarnings;
/**
* A parser for the Doxygen warnings.
*
* @author Frederic Chateau
* @author Bruno Matos
*/
public class DoxygenParser extends RegexpDocumentParser {
/** A Doxygen warning. */
static final String WARNING_CATEGORY = "Doxygen warning";
/** Warning type of this parser. */
static final String WARNING_TYPE = "doxygen";
/**
* Pattern of Doxygen warnings.
* Here are explanations of this fairly complex (yet efficient) pattern.
* The pattern has 2 main parts:
* - one for doxygen messages related to a file or a function
* - one for global doxygen messages
*
* Global messages match the following simple pattern: "(Notice|Warning|Error): (.+)"
* Local messages are more complicated:
* - if it is a file we assume doxygen always prints the absolute path
* (eg: /home/user/project/foo.cpp, C:\project\foo.cpp) so the
* expression (?:/|[A-Za-z]:) matches either a slash or a volume letter
* like C:. Then we match everything until the colon sign ':', which is
* followed by a line number (can be -1 in some cases, which explains
* why the group is (-?\\d+). Finally, the warning type is mandatory and
* can be either "Warning" or "Error"
* - if it is a function, the function name is displayed between angle
* brackets, and followed by a line number. Finally, the warning type
* is sometimes printed, but not always, which is why the expression
* is (?:: (Warning|Error))?
* In both cases, local warnings are followed by a multi-line message that
* can get quite complex.
* The message is made of the remaining of the current line and of
* an arbitrary long (and optional) sequence of lines which can take many
* shapes, but that never begins like an absolute path or a function.
* So we accept anything except '/' and '<' for the first character,
* anything except ':' (windows drive colon) for the second character,
* and anything except '/' (doxygen uses slash instead of backslash, after
* the drive colon) for the third character.
* For each of these 3 characters we also refuse newlines to avoid getting
* empty or incomplete lines (lines with less than 3 characters are
* suspicious).
* After these 3 characters, we accept anything until the end of the line.
* The whole multi-line message is matched by:
* (.+(?:\\n[^/<\\n][^:\\n][^\\\\\\n].+)*
* */
private static final String DOXYGEN_WARNING_PATTERN =
"^(?:(?:((?:/|[A-Za-z]:).+?):(-?\\d+):\\s*([Ww]arning|[Ee]rror)|<.+>:-?\\d+(?::\\s*([Ww]arning|[Ee]rror))?): (.+(?:\\n[^/<\\n][^:\\n][^/\\n].+)*)|([Nn]otice|[Ww]arning|[Ee]rror): (.+))$";
/** The index of the regexp group capturing the file name (when the warning occurs in a file). */
private static final int FILE_NAME_GROUP = 1;
/** The index of the regexp group capturing the line number (when the warning occurs in a file). */
private static final int FILE_LINE_GROUP = 2;
/** The index of the regexp group capturing the warning type (when occuring in a file). */
private static final int FILE_TYPE_GROUP = 3;
/** The index of the regexp group capturing the warning type (when occuring in a function). */
private static final int FUNC_TYPE_GROUP = 4;
/** The index of the regexp group capturing the warning message (when it occurs in a local context: file or function). */
private static final int LOCAL_MESSAGE_GROUP = 5;
/** The index of the regexp group capturing the warning type, when not attached to a local context. */
private static final int GLOBAL_TYPE_GROUP = 6;
/** The index of the regexp group capturing the warning message, when not attached to a local context. */
private static final int GLOBAL_MESSAGE_GROUP = 7;
/**
* Creates a new instance of <code>DoxygenParser</code>.
*/
public DoxygenParser() {
super(DOXYGEN_WARNING_PATTERN, true, "Doxygen");
}
/** {@inheritDoc} */
@Override
protected Warning createWarning(final Matcher matcher) {
String message;
String fileName = "";
int lineNumber = 0;
Priority priority;
if (StringUtils.isNotBlank(matcher.group(LOCAL_MESSAGE_GROUP))) {
// Warning message local to a file or a function
message = matcher.group(LOCAL_MESSAGE_GROUP);
if (StringUtils.isNotBlank(matcher.group(FILE_NAME_GROUP))) {
// File related warning
fileName = matcher.group(FILE_NAME_GROUP);
lineNumber = getLineNumber(matcher.group(FILE_LINE_GROUP));
priority = parsePriority(matcher.group(FILE_TYPE_GROUP));
}
else {
// Function related warning
priority = parsePriority(matcher.group(FUNC_TYPE_GROUP));
}
}
else if (StringUtils.isNotBlank(matcher.group(GLOBAL_MESSAGE_GROUP))) {
// Global warning message
message = matcher.group(GLOBAL_MESSAGE_GROUP);
priority = parsePriority(matcher.group(GLOBAL_TYPE_GROUP));
}
else {
message = "Unknown doxygen error.";
priority = Priority.HIGH;
// should never happen
}
return new Warning(fileName, lineNumber, WARNING_TYPE, WARNING_CATEGORY, message, priority);
}
/**
* Returns the priority ordinal matching the specified warning type string.
*
* @param warningTypeString
* a string containing the warning type returned by a regular
* expression group matching it in the warnings output.
* @return the priority
*/
@SuppressWarnings("DB")
private Priority parsePriority(final String warningTypeString) {
Priority priority;
if (StringUtils.equalsIgnoreCase(warningTypeString, "notice")) {
priority = Priority.LOW;
}
else if (StringUtils.equalsIgnoreCase(warningTypeString, "warning")) {
priority = Priority.NORMAL;
}
else if (StringUtils.equalsIgnoreCase(warningTypeString, "error")) {
priority = Priority.HIGH;
}
else {
// empty label or other unexpected input
priority = Priority.HIGH;
}
return priority;
}
}