package hudson.plugins.tasks.parser;
import hudson.AbortException;
import hudson.plugins.analysis.util.model.Priority;
import hudson.plugins.tasks.Messages;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.LineIterator;
import org.apache.commons.lang.StringUtils;
/**
* Scans a given input stream for open tasks.
*
* @author Ulli Hafner
*/
public class TaskScanner {
private static final String WORD_BOUNDARY = "\\b";
/** The regular expression patterns to be used to scan the files. One pattern per priority. */
private final Map<Priority, Pattern> patterns = new HashMap<Priority, Pattern>();
private boolean isInvalidPattern;
private final StringBuilder errorMessage = new StringBuilder();
/**
* Creates a new instance of {@link TaskScanner}.
*/
public TaskScanner() {
this("FIXME", "TODO", "@deprecated", false);
}
/**
* Creates a new instance of {@link TaskScanner}.
*
* @param high
* tag identifiers indicating high priority
* @param normal
* tag identifiers indicating normal priority
* @param low
* tag identifiers indicating low priority
* @param ignoreCase
* if case should be ignored during matching
*/
public TaskScanner(final String high, final String normal, final String low, final boolean ignoreCase) {
if (StringUtils.isNotBlank(high)) {
patterns.put(Priority.HIGH, compile(high, ignoreCase));
}
if (StringUtils.isNotBlank(normal)) {
patterns.put(Priority.NORMAL, compile(normal, ignoreCase));
}
if (StringUtils.isNotBlank(low)) {
patterns.put(Priority.LOW, compile(low, ignoreCase));
}
}
/**
* Compiles a regular expression pattern to scan for tag identifiers.
*
* @param tagIdentifiers
* the identifiers to scan for
* @param ignoreCase
* specifies if case should be ignored
* @return the compiled pattern
*/
private Pattern compile(final String tagIdentifiers, final boolean ignoreCase) {
try {
String[] tags;
if (tagIdentifiers.indexOf(',') == -1) {
tags = new String[] {tagIdentifiers};
}
else {
tags = StringUtils.split(tagIdentifiers, ",");
}
List<String> regexps = new ArrayList<String>();
for (int i = 0; i < tags.length; i++) {
String tag = tags[i].trim();
if (StringUtils.isNotBlank(tag)) {
if (Character.isLetterOrDigit(tag.charAt(0))) {
regexps.add(WORD_BOUNDARY + tag + WORD_BOUNDARY);
}
else {
regexps.add(tag + WORD_BOUNDARY);
}
}
}
int flags;
if (ignoreCase) {
flags = Pattern.CASE_INSENSITIVE;
}
else {
flags = 0;
}
return Pattern.compile("^.*(" + StringUtils.join(regexps.iterator(), "|") + ")(.*)$", flags);
}
catch (PatternSyntaxException exception) {
isInvalidPattern = true;
errorMessage.append(Messages.Tasks_PatternError(tagIdentifiers, exception.getMessage()));
errorMessage.append("\n");
return null;
}
}
/**
* Scans the specified input stream for open tasks.
*
* @param reader
* the file to scan
* @return the result stored as java project
* @throws IOException
* if we can't read the file
*/
public Collection<Task> scan(final Reader reader) throws IOException {
try {
if (isInvalidPattern) {
throw new AbortException(errorMessage.toString());
}
LineIterator lineIterator = IOUtils.lineIterator(reader);
List<Task> tasks = new ArrayList<Task>();
for (int lineNumber = 1; lineIterator.hasNext(); lineNumber++) {
String line = (String)lineIterator.next();
for (Priority priority : Priority.values()) {
if (patterns.containsKey(priority)) {
Matcher matcher = patterns.get(priority).matcher(line);
if (matcher.matches() && matcher.groupCount() == 2) {
String message = matcher.group(2).trim();
tasks.add(new Task(priority, lineNumber, matcher.group(1), StringUtils.remove(message, ":").trim()));
}
}
}
}
return tasks;
}
finally {
reader.close();
}
}
}