package org.atc.tools.ant;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.atc.tools.ant.format.*;
import org.atc.tools.ant.parsers.GenericItemParser;
import org.atc.tools.ant.parsers.ItemParser;
import org.atc.tools.ant.writers.CSVItemWriter;
import org.atc.tools.ant.writers.ItemWriter;
import org.atc.tools.ant.writers.PlainTextItemWriter;
import org.atc.tools.ant.writers.XMLItemWriter;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.lang.String.format;
/**
* A task that outputs TODOs to a file for all source files that contain them
*
* @author Alex Collins
*/
public class TODOTask extends MatchingTask {
private PrintStream outStream;
private boolean shouldWriteToProperty;
/**
* The delimited string containing the origin and content of TODOs/FIXMEs.
* e.g. "My.java: do something,Another.java: code some more"
*/
private StringBuffer optBuffer;
private List<FormattedItem> itemsList;
private ItemParser itemParser;
private Map<Format, ItemFormatter> itemFormattersMap;
private Map<Format, ItemWriter> itemWriterMap;
public void init() {
super.init();
}
public void execute() throws BuildException {
super.execute();
initRequiredFields();
parseFiles();
outputFindings();
}
/**
* For all files in baseDir call processFile(f)
*/
private void parseFiles() {
File baseDir = Configuration.baseDir;
for (String fName : getDirectoryScanner(baseDir).getIncludedFiles()) {
File f = new File(format("%s/%s", baseDir.getAbsolutePath(), fName));
processFile(f);
}
}
private void outputFindings() {
vlog(format("itemsList before output is %s", itemsList));
if (shouldWriteToProperty) {
getProject().setNewProperty(Configuration.property, optBuffer.toString());
}
ItemWriter itemWriter = itemWriterMap.get(Configuration.optFormat);
vlog(format("Writing using itemWriter '%s'", itemWriter));
if (itemWriter == null) {
itemWriter = itemWriterMap.get(Format.DEFAULT);
log(format("Warning: using DEFAULT output format because I couldn't find a writer for '%s'", Configuration.optFormat));
}
itemWriter.write(outStream, itemsList);
outStream.flush();
outStream.close();
}
/**
* Scan the file and parse any and all TODO/FIXMEs into the <code>itemsList</code> field.
*
* @param f
*/
private void processFile(File f) {
try {
vlog(format("Processing %s", f.getName()));
final BufferedReader br = new BufferedReader(new FileReader(f));
String line;
int lineCounter = 0;
while ((line = br.readLine()) != null) {
line = line.trim();
lineCounter++;
if (itemParser.containsItem(line)) {
Item item = newItem(f, line);
item.setLineNumber(lineCounter);
FormattedItem fmtItem = formatItem(item);
vlog(format("Line number %s", lineCounter));
if (Configuration.regex != null) {
String replace = Configuration.replace == null ? "" : Configuration.replace;
vlog(format("Running pattern %s with replace '%s' against '%s'",
Configuration.regex, replace, fmtItem.getValue()));
fmtItem.setValue(fmtItem.getValue().replaceAll(Configuration.regex, replace));
vlog(format("Pattern produced '%s'", fmtItem.getValue()));
}
itemsList.add(fmtItem);
vlog(format("Added item to itemsList: %s", fmtItem));
//TODO: write only to the list; remove this to the output phase
if (shouldWriteToProperty) {
// buffer the val until later
// Part of a two-stage refactor; output to file is done later; buffering for property is still done here. We'll merge the two concepts later.
bufferValueForProperty(item);
vlog("Item buffered");
}
// }
}
}
} catch (IOException e) {
log(format("Error! I couldn't write a TODO to the output file: %s", e.getMessage()));
}
}
private FormattedItem formatItem(Item item) {
//FIXME: do we risk a null pointer if the optFormat isn't recognisable?
return itemFormattersMap.get(Configuration.optFormat).format(Configuration.optFormat, item);
}
private Item newItem(File f, String line) {
Item item = itemParser.parse(line);
item.setOrigin(f.getName());
return item;
}
private void bufferValueForProperty(Item item) {
String todoStr = item.toString();
optBuffer.append(todoStr).append(Configuration.delim);
vlog(format("Buffered %s to property %s with delim %s",
todoStr, Configuration.property, Configuration.delim));
}
/**
* Check if we should be writing to a property instead of an output file.
*
* @return true if the property field is set
*/
private boolean shouldWriteToProperty() {
return Configuration.property != null && !"".equals(Configuration.property);
}
private void initRequiredFields() throws BuildException {
if (Configuration.baseDir == null) {
log("You didn't specify a directory to recurse, so I'll use the current directory by default. This might not be what you want...");
Configuration.baseDir = new File(System.getProperty("user.dir"));
}
if (Configuration.outFile == null) {
outStream = System.out;
log("No output file specified; writing to stdout");
} else {
try {
outStream = new PrintStream(Configuration.outFile);
log(format("Using file '%s'", Configuration.outFile.getAbsoluteFile()));
} catch (FileNotFoundException e) {
log("Error! Couldn't find output file!");
throw new BuildException(e);
}
}
if (Configuration.regex != null) {
if (Configuration.replace == null) {
log("No replace provided for regex; replacing all occurrences with nothing");
}
}
shouldWriteToProperty = shouldWriteToProperty();
optBuffer = new StringBuffer();
itemsList = new ArrayList<FormattedItem>();
itemParser = new GenericItemParser();
itemFormattersMap = new HashMap<Format, ItemFormatter>();
//TODO only add those we're generating? Or leave and add support for multiple formats?
itemFormattersMap.put(Format.DEFAULT, new PlainTextItemFormatter());
itemFormattersMap.put(Format.CSV, new CSVItemFormatter());
itemFormattersMap.put(Format.XML, new XMLItemFormatter());
//TODO move this to a register pattern and code for subclassing?
itemWriterMap = new HashMap<Format, ItemWriter>();
itemWriterMap.put(Format.DEFAULT, new PlainTextItemWriter());
itemWriterMap.put(Format.CSV, new CSVItemWriter());
itemWriterMap.put(Format.XML, new XMLItemWriter());
}
/**
* Log <code>msg</code> if this.verbose is true
*/
protected final void vlog(String msg) {
if (Configuration.verbose) log(msg);
}
/**
* Set the base directory for the source files
* Required!
*/
public void setDir(File dir) {
Configuration.baseDir = dir;
}
/**
* The file to write the TODOs to, or stdout otherwise
*/
public void setOutFile(File file) {
Configuration.outFile = file;
}
public void setVerbose(boolean verbose) {
Configuration.verbose = verbose;
}
/**
* What format to write the TODOs as. One of CSV, XML or the default (blank):
* SourceFilename:TODO_CONTENT
*/
public void setFormat(String format) {
Configuration.optFormat = Format.fromString(format);
}
/**
* Store the TODOs found in the property identified by <code>property</code>.
*
* @param property
*/
public void setProperty(String property) {
Configuration.property = property;
}
/**
* Use the given String as the delimiter for each TODO item.
* By default this is set to the system's line separator.
*/
public void setDelim(String delim) {
Configuration.delim = delim;
}
public void setPattern(String regex) {
Configuration.regex = regex;
}
public void setReplace(String replace) {
Configuration.replace = replace;
}
}