--- /dev/null
+package net.jaekl.cfb.analyze;
+
+public class Analysis {
+
+}
--- /dev/null
+package net.jaekl.cfb.analyze;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.MessageFormat;
+import java.util.Locale;
+import java.util.Locale.Category;
+
+import net.jaekl.cfb.CfbBundle;
+import net.jaekl.cfb.util.Command;
+
+public class Analyzer {
+ File m_findbugsDir;
+
+ public Analyzer(File findbugsDir) {
+ m_findbugsDir = findbugsDir;
+ }
+
+ public Analysis analyze(PrintWriter pw, File workDir, File fbp) throws IOException {
+ Analysis result = new Analysis();
+
+ File fbOutput = outputWorkFile(workDir, fbp);
+
+ String cmdLine = buildCommandLine(workDir, fbp, fbOutput);
+ pw.println(cmdLine);
+ pw.flush();
+ Command.Result fbResult = new Command().exec(cmdLine);
+ if (0 != fbResult.getRetCode()) {
+ // Our attempt to execute FindBugs failed.
+ // Report the error and return null.
+ String cannotExecFormat = trans(CfbBundle.CANNOT_EXEC);
+ String cannotExecMsg = MessageFormat.format(cannotExecFormat, cmdLine, fbResult.getRetCode());
+ pw.println(cannotExecMsg);
+ pw.println(trans(CfbBundle.STDOUT_WAS));
+ pw.println(fbResult.getStdout());
+ pw.println(trans(CfbBundle.STDERR_WAS));
+ pw.println(fbResult.getStderr());
+ return null;
+ }
+
+ result = parseFbOutput(fbOutput);
+
+ return result;
+ }
+
+ String trans(String key) {
+ return CfbBundle.getInst(Locale.getDefault(Category.DISPLAY)).get(key);
+ }
+
+ String buildCommandLine(File workDir, File fbp, File fbOutput)
+ {
+ assert(null != workDir);
+ assert(null != fbp);
+ assert(null != fbOutput);
+
+ StringBuilder sb = new StringBuilder();
+
+ sb.append(m_findbugsDir.getAbsolutePath())
+ .append(File.separator)
+ .append("bin")
+ .append(File.separator)
+ .append("findbugs -textui -xml -output ")
+ .append(fbOutput.getAbsolutePath())
+ .append(" -project ")
+ .append(fbp.getAbsolutePath());
+
+ return sb.toString();
+ }
+
+ // Come up with an appropriate name for the XML output file.
+ // workDir: place where the file should be created
+ // fbp: FindBugsProject file
+ File outputWorkFile(File workDir, File fbp)
+ {
+ assert(null != workDir);
+ assert(null != fbp);
+
+ String workPath = workDir.getAbsolutePath();
+
+ String projName = fbp.getName();
+ int len = projName.length();
+ if (len > 4) {
+ String extension = projName.substring(len - 4, len).toLowerCase(Locale.CANADA);
+ if (extension.equals(".fbp")) {
+ projName = projName.substring(0, len - 4);
+ }
+ }
+
+ return new File(workPath + File.separator + projName + ".xml");
+ }
+
+ // Parse the output.xml that resulted from a FindBugs run,
+ // and store its findings into an Analysis object.
+ Analysis parseFbOutput(File fbOutput)
+ {
+ return null;
+ }
+}
--- /dev/null
+package net.jaekl.cfb.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
+public class Command {
+ public static class Result
+ {
+ private int m_retCode;
+ private String m_stdout;
+ private String m_stderr;
+
+ Result(int retCode, String stdout, String stderr) {
+ m_retCode = retCode;
+ m_stdout = stdout;
+ m_stderr = stderr;
+ }
+
+ public int getRetCode() { return m_retCode; }
+ public String getStdout() { return m_stdout; }
+ public String getStderr() { return m_stderr; }
+ }
+
+ private static class StreamGobbler extends Thread {
+
+ private StringBuilder m_sb;
+ private BufferedReader m_br;
+
+ public StreamGobbler(InputStream is) {
+ m_sb = new StringBuilder();
+ m_br = new BufferedReader(new InputStreamReader(is));
+ }
+
+ @Override
+ public void run() {
+ String line;
+ try {
+ line = m_br.readLine();
+ while (null != line) {
+ m_sb.append(line).append(System.lineSeparator());
+ line = m_br.readLine();
+ }
+ } catch (IOException exc) {
+ m_sb.append(Util.stringify(exc));
+ }
+ finally {
+ try {
+ m_br.close();
+ } catch (IOException exc) {
+ m_sb.append(Util.stringify(exc));
+ }
+ }
+ }
+
+ public String getOutput() { return m_sb.toString(); }
+ }
+
+ public Command()
+ {
+ // no-op
+ }
+
+ public Result exec(String cmd) throws IOException
+ {
+ int retCode = 0;
+ String stdout = "";
+ String stderr = "";
+
+ Process proc = doRuntimeExec(cmd);
+ assert( null != proc );
+
+ StreamGobbler stdoutGobbler = new StreamGobbler(proc.getInputStream());
+ StreamGobbler stderrGobbler = new StreamGobbler(proc.getErrorStream());
+
+ stdoutGobbler.start();
+ stderrGobbler.start();
+
+ try {
+ retCode = proc.waitFor();
+ stdoutGobbler.join();
+ stderrGobbler.join();
+
+ stdout = stdoutGobbler.getOutput();
+ stderr = stdoutGobbler.getOutput();
+
+ } catch (InterruptedException exc) {
+ stderr += Util.stringify(exc);
+ }
+
+ return new Result(retCode, stdout, stderr);
+ }
+
+ Process doRuntimeExec(String cmd) throws IOException {
+ return Runtime.getRuntime().exec(cmd);
+ }
+}
--- /dev/null
+package net.jaekl.cfb.util;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public class Util {
+ public static String stringify(Throwable thr) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ thr.printStackTrace(pw);
+ return sw.toString();
+ }
+}
--- /dev/null
+package net.jaekl.cfb.xml;
+
+import org.xml.sax.Attributes;
+
+import net.jaekl.qd.xml.ParseResult;
+import net.jaekl.qd.xml.XmlParseException;
+
+public class BugClass extends ParseResult {
+
+ static final String ROOT_TAG = "Class";
+ static final String[] INTERNAL = { };
+ static final Object[][] EXTERNAL = { { SourceLine.ROOT_TAG, SourceLine.class} };
+
+ public BugClass() {
+ super(ROOT_TAG, INTERNAL, EXTERNAL);
+ }
+
+ @Override
+ public void endContents(String uri, String localName, String qName,
+ String chars, Attributes attr) throws XmlParseException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException {
+ // TODO Auto-generated method stub
+
+ }
+
+}
--- /dev/null
+package net.jaekl.cfb.xml;
+
+import org.xml.sax.Attributes;
+
+import net.jaekl.qd.xml.ParseResult;
+import net.jaekl.qd.xml.XmlParseException;
+
+public class BugCollection extends ParseResult {
+
+ static final String ROOT_TAG = "BugCollection";
+ static final String[] INTERNAL = { };
+ static final Object[][] EXTERNAL = { { BugInstance.ROOT_TAG, BugInstance.class} };
+
+ public BugCollection() {
+ super(ROOT_TAG, INTERNAL, EXTERNAL);
+ }
+
+ @Override
+ public void endContents(String uri, String localName, String qName,
+ String chars, Attributes attr) throws XmlParseException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException {
+ // TODO Auto-generated method stub
+
+ }
+
+}
--- /dev/null
+package net.jaekl.cfb.xml;
+
+import org.xml.sax.Attributes;
+
+import net.jaekl.qd.xml.ParseResult;
+import net.jaekl.qd.xml.XmlParseException;
+
+public class BugInstance extends ParseResult {
+
+ static final String ROOT_TAG = "BugInstance";
+ static final String[] INTERNAL = { };
+ static final Object[][] EXTERNAL = { { BugClass.ROOT_TAG, BugClass.class},
+ { BugMethod.ROOT_TAG, BugMethod.class},
+ { SourceLine.ROOT_TAG, SourceLine.class} };
+
+ public BugInstance() {
+ super(ROOT_TAG, INTERNAL, EXTERNAL);
+ }
+
+ @Override
+ public void endContents(String uri, String localName, String qName,
+ String chars, Attributes attr) throws XmlParseException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException {
+ // TODO Auto-generated method stub
+
+ }
+
+}
--- /dev/null
+package net.jaekl.cfb.xml;
+
+import org.xml.sax.Attributes;
+
+import net.jaekl.qd.xml.ParseResult;
+import net.jaekl.qd.xml.XmlParseException;
+
+public class BugMethod extends ParseResult {
+
+ static final String ROOT_TAG = "Method";
+ static final String[] INTERNAL = { };
+ static final Object[][] EXTERNAL = { { SourceLine.ROOT_TAG, SourceLine.class} };
+
+ public BugMethod() {
+ super(ROOT_TAG, INTERNAL, EXTERNAL);
+ }
+
+ @Override
+ public void endContents(String uri, String localName, String qName,
+ String chars, Attributes attr) throws XmlParseException {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException {
+ // TODO Auto-generated method stub
+
+ }
+
+}
--- /dev/null
+package net.jaekl.cfb.xml;
+
+import org.xml.sax.Attributes;
+
+import net.jaekl.qd.xml.MissingAttributeException;
+import net.jaekl.qd.xml.ParseResult;
+import net.jaekl.qd.xml.XmlParseException;
+
+public class SourceLine extends ParseResult {
+
+ static final String ROOT_TAG = "SourceLine";
+ static final String[] INTERNAL = { };
+ static final Object[][] EXTERNAL = { };
+
+ static final String ATTR_CLASS_NAME = "classname";
+ static final String ATTR_START = "start";
+ static final String ATTR_END = "end";
+
+ String m_className;
+ int m_start;
+ int m_end;
+
+ public SourceLine() {
+ super(ROOT_TAG, INTERNAL, EXTERNAL);
+ m_className = null;
+ m_start = m_end = (-1);
+ }
+
+ @Override
+ public void endContents(String uri, String localName, String qName, String chars, Attributes attr)
+ throws XmlParseException
+ {
+ String scratch;
+
+ m_className = getRequiredAttr(localName, attr, ATTR_CLASS_NAME);
+
+ scratch = getRequiredAttr(localName, attr, ATTR_START);
+ m_start = Integer.parseInt(scratch);
+
+ scratch = getRequiredAttr(localName, attr, ATTR_END);
+ m_end = Integer.parseInt(scratch);
+ }
+
+ String getRequiredAttr(String tagName, Attributes attr, String attrName)
+ throws MissingAttributeException
+ {
+ String result = attr.getValue(attrName);
+ if (null == result) {
+ throw new MissingAttributeException(tagName, attrName);
+ }
+ return result;
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException
+ {
+ // no-op
+ }
+
+}
--- /dev/null
+package net.jaekl.qd;
+
+public class QDException extends Exception
+{
+ private static final long serialVersionUID = 1L;
+
+ public QDException() {
+ super();
+ }
+
+ public QDException(Throwable t) {
+ super(t);
+ }
+}
--- /dev/null
+// Copyright (C) 2004, 2014 Christian Jaekl
+
+package net.jaekl.qd.xml;
+
+
+public class MismatchedTagsException extends XmlParseException
+{
+ private static final long serialVersionUID = 1L;
+
+ String m_expected;
+ String m_actual;
+
+ public MismatchedTagsException(String expected, String actual) {
+ super();
+ m_expected = expected;
+ m_actual = actual;
+ }
+
+ public String getExpected() { return m_expected; }
+ public String getActual() { return m_actual; }
+}
--- /dev/null
+package net.jaekl.qd.xml;
+
+public class MissingAttributeException extends XmlParseException {
+ private static final long serialVersionUID = 1L;
+
+ String m_tagName;
+ String m_attributeName;
+
+ public MissingAttributeException(String tagName, String attributeName)
+ {
+ m_tagName = tagName;
+ m_attributeName = attributeName;
+ }
+}
--- /dev/null
+// Copyright (C) 2004, 2014 Christian Jaekl
+
+package net.jaekl.qd.xml;
+
+import java.util.ArrayList;
+
+public class MissingInfoException extends XmlParseException
+{
+ private static final long serialVersionUID = 1L;
+
+ String m_tagName;
+ ArrayList<String> m_missingAttributes;
+ ArrayList<String> m_missingChildTags;
+
+ public MissingInfoException(String tagName) {
+ super();
+ m_tagName = tagName;
+ m_missingAttributes = new ArrayList<String>();
+ m_missingChildTags = new ArrayList<String>();
+ }
+
+ public void addMissingAttribute(String name) {
+ m_missingAttributes.add(name);
+ }
+
+ public void addMissingChild(String name) {
+ m_missingChildTags.add(name);
+ }
+
+ public String getTagName() { return m_tagName; }
+
+ @Override
+ public String getMessage() {
+ StringBuilder sb = new StringBuilder();
+
+ sb.append("Tag: \"" + getTagName() + "\"");
+
+ for (String attr : m_missingAttributes) {
+ sb.append("\n Attribute: \"" + attr + "\"");
+ }
+
+ for (String child : m_missingChildTags) {
+ sb.append("\n Child tag: \"" + child + "\"");
+ }
+ return sb.toString();
+ }
+}
--- /dev/null
+// Copyright (C) 2014 Christian Jaekl
+
+// Simple SAX parse error handler.
+// Necessary to avoid printing [Fatal Error] messages to stdout when something goes wrong.
+
+package net.jaekl.qd.xml;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+public class ParseErrorHandler implements ErrorHandler {
+
+ @Override
+ public void error(SAXParseException saxpe) throws SAXException {
+ throw saxpe;
+ }
+
+ @Override
+ public void fatalError(SAXParseException saxpe) throws SAXException {
+ throw saxpe;
+ }
+
+ @Override
+ public void warning(SAXParseException saxpe) throws SAXException {
+ throw saxpe;
+ }
+
+}
--- /dev/null
+// Copyright (C) 2004, 2015 Christian Jaekl
+
+package net.jaekl.qd.xml;
+
+import java.util.Stack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+public class ParseHandler implements ContentHandler
+{
+ Stack<ParseResult> m_stack;
+
+ public ParseHandler(ParseResult root) {
+ m_stack = new Stack<ParseResult>();
+ m_stack.push(root);
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException
+ {
+ if (m_stack.isEmpty()) {
+ return;
+ }
+
+ try {
+ m_stack.peek().characters(ch, start, length);
+ }
+ catch (XmlParseException xpe) {
+ throw new SAXException(xpe);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException
+ {
+ try {
+ if (m_stack.isEmpty()) {
+ return;
+ }
+
+ boolean pop = m_stack.peek().endElement(uri, localName, qName);
+ if (pop) {
+ m_stack.pop();
+
+ if (m_stack.isEmpty()) {
+ return;
+ }
+
+ m_stack.peek().endExternal(uri, localName, qName);
+ }
+ }
+ catch (XmlParseException xpe) {
+ throw new SAXException(xpe);
+ }
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
+ {
+ try {
+ ParseResult current = m_stack.peek();
+ ParseResult next = current.startElement(uri, localName, qName, attributes);
+ if (next != current) {
+ m_stack.push(next);
+ }
+ }
+ catch (XmlParseException xpe) {
+ throw new SAXException(xpe);
+ }
+ }
+
+ @Override
+ public void endDocument() throws SAXException {
+ if (! m_stack.isEmpty()) {
+ String missingTag = m_stack.peek().getTagName();
+ throw new SAXException(new MissingInfoException(missingTag));
+ }
+ }
+
+ @Override
+ public void endPrefixMapping(String prefix) throws SAXException {
+ // no-op
+ }
+
+ @Override
+ public void ignorableWhitespace(char[] ch, int start, int length)
+ throws SAXException
+ {
+ // no-op
+ }
+
+ @Override
+ public void processingInstruction(String target, String data)
+ throws SAXException
+ {
+ // no-op
+ }
+
+ @Override
+ public void setDocumentLocator(Locator locator) {
+ // no-op
+ }
+
+ @Override
+ public void skippedEntity(String name) throws SAXException {
+ // no-op
+ }
+
+ @Override
+ public void startDocument() throws SAXException {
+ // no-op
+ }
+
+ @Override
+ public void startPrefixMapping(String prefix, String uri)
+ throws SAXException
+ {
+ // no-op
+ }
+}
--- /dev/null
+// Copyright (C) 2004, 2015 Christian Jaekl
+
+// Abstract class representing the result of parsing an XML Element.
+// A class derived from this one will know how to parse a subtree inside an XML file, and
+// will contain the result of that parse within itself when the parse has completed.
+//
+// Note that this code will need to be augmented and fixed if XML namespace support is desired.
+
+package net.jaekl.qd.xml;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Stack;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.helpers.AttributesImpl;
+
+public abstract class ParseResult
+{
+ Stack<CurrentInfo> m_current; // Name of the element that we're currently inside
+ StringBuilder m_chars; // character content of m_current.peek()
+ ArrayList<ParseResult> m_childParsers; // Set of all child parsers
+ boolean m_haveSeenMyTag; // Have I encountered my own (root) tag yet?
+
+ String m_tagName; // Name of the (root) element tag that I'm parsing
+ HashSet<String> m_internal; // Tags that we will store as members of this class instance
+ HashMap<String,Class<? extends ParseResult>> m_external; // Tags that we will store as child ParseResult-derived objects
+
+ // Information about the "current" tag, which we'll keep on the m_current stack.
+ static class CurrentInfo {
+ String m_tagName;
+ Attributes m_attr;
+
+ public CurrentInfo(String tagName, Attributes attr) {
+ m_tagName = tagName;
+ m_attr = new AttributesImpl(attr);
+ }
+
+ public String getTagName() { return m_tagName; }
+ public final Attributes getAttributes() { return m_attr; }
+ }
+
+ @SuppressWarnings("unchecked")
+ public ParseResult(String tagName, String[] internalMemberTags, Object[][] externalParserTags)
+ {
+ m_current = new Stack<CurrentInfo>();
+ m_chars = new StringBuilder();
+ m_childParsers = new ArrayList<ParseResult>();
+ m_haveSeenMyTag = false;
+
+ m_tagName = tagName;
+ m_internal = new HashSet<String>();
+ m_external = new HashMap<String, Class<? extends ParseResult>>();
+
+ for (String internalTag : internalMemberTags) {
+ m_internal.add(internalTag);
+ }
+
+ for (int idx = 0; idx < externalParserTags.length; ++idx) {
+ String externalTag = (String)externalParserTags[idx][0];
+ Class<? extends ParseResult> parserClass = (Class<? extends ParseResult>)externalParserTags[idx][1];
+ m_external.put(externalTag, parserClass);
+ }
+ }
+
+ public abstract void endContents(String uri, String localName, String qName, String chars, Attributes attr) throws XmlParseException;
+ public abstract void endExternal(String uri, String localName, String qName) throws XmlParseException;
+
+ public String getTagName() { return m_tagName; }
+ public boolean haveSeenMyTag() { return m_haveSeenMyTag; }
+
+ public void characters(char[] ch, int start, int length) throws XmlParseException
+ {
+ m_chars.append(ch, start, length);
+ }
+
+ protected ParseResult[] collectParsedChildren(Class<? extends ParseResult> cls) {
+ ArrayList<ParseResult> collection = new ArrayList<ParseResult>();
+ Iterator<ParseResult> iter = m_childParsers.iterator();
+ while (iter.hasNext()) {
+ ParseResult pr = iter.next();
+ if (pr.getClass().isAssignableFrom(cls)) {
+ collection.add(pr);
+ iter.remove();
+ }
+ }
+
+ ParseResult[] result = new ParseResult[collection.size()];
+ return collection.toArray(result);
+ }
+
+ // returns true if this ParseResult's context has ended with this endElement() call
+ public boolean endElement(String uri, String localName, String qName) throws XmlParseException
+ {
+ assert (null != localName);
+
+ boolean isInternal = m_internal.contains(localName);
+
+ if (! m_haveSeenMyTag) {
+ // We're in some unrecognised prologue. Ignore it and move on.
+ return false;
+ }
+
+ if (m_tagName.equals(localName)) {
+ validate();
+ return true;
+ }
+
+ if (!isInternal) {
+ // Unrecognized tag. Ignore it.
+ return false;
+ }
+
+ CurrentInfo info = m_current.pop();
+ String tag = info.getTagName();
+ if ( ! tag.equals(localName) ) {
+ throw new MismatchedTagsException(tag, localName);
+ }
+
+ String chars = m_chars.toString();
+ endContents(uri, localName, qName, chars, info.getAttributes());
+
+ return false;
+ }
+
+ // returns either itself, or a new ParseResult-derived object, whichever should handle parsing the inside of this element
+ public ParseResult startElement(String uri, String localName, String qName, Attributes attributes)
+ throws XmlParseException
+ {
+ assert (null != localName);
+
+ m_chars.setLength(0);
+
+ if (! m_haveSeenMyTag) {
+ // Have we opened our own (root) tag yet?
+
+ if (m_tagName.equals(localName)) {
+ m_haveSeenMyTag = true;
+ return this;
+ }
+ else {
+ // One of two things has happened here.
+ // Either (a) we've got some sort of wrapper here, and have not yet reach our own tag,
+ // or (b) we're parsing XML that doesn't match expectations.
+ // In either case, we're going to ignore this tag, and scan forward looking for our own root.
+ return this;
+ }
+ }
+
+ if (m_internal.contains(localName)) {
+ CurrentInfo info = new CurrentInfo(localName, attributes);
+ m_current.push(info);
+ return this;
+ }
+
+ Class<? extends ParseResult> parserClass = m_external.get(localName);
+ if (null != parserClass) {
+ try {
+ ParseResult childParser = (ParseResult) parserClass.newInstance();
+ m_childParsers.add(childParser);
+ return childParser.startElement(uri, localName, qName, attributes);
+ }
+ catch (IllegalAccessException iae) {
+ throw new XmlParseException(iae);
+ }
+ catch (InstantiationException ie) {
+ throw new XmlParseException(ie);
+ }
+ }
+
+ // Not a recognized tag. Ignore it, rather than complaining.
+ return this;
+ }
+
+ public void validate() throws XmlParseException
+ {
+ // Default implementation is a no-op.
+ // Override if you want to validate on endElement()
+ }
+}
+
--- /dev/null
+package net.jaekl.qd.xml;
+
+import net.jaekl.qd.QDException;
+
+public class XmlParseException extends QDException
+{
+ private static final long serialVersionUID = 1L;
+
+ public XmlParseException() {
+ // no-op
+ }
+
+ public XmlParseException(Throwable t) {
+ super(t);
+ }
+
+}
--- /dev/null
+package net.jaekl.cfb.analyze;
+
+import static org.junit.Assert.*;
+
+import java.io.File;
+
+import org.junit.Test;
+
+public class AnalyzerTest {
+
+ @Test
+ public void testOutputWorkFile() {
+ final String[][] DATA = {
+ // findBugsDir, workDir, FBP, expectedOutputXml
+ { "foo", "bar", "baz.fbp", "bar" + File.separator + "baz.xml" },
+ {
+ File.separator + "findbugs-3.01",
+ "." + File.separator + "work",
+ "project.fbp",
+ "." + File.separator + "work" + File.separator + "project.xml"
+ }
+ };
+
+ for (String[] datum : DATA) {
+ File findBugsDir = new File(datum[0]);
+ File workDir = new File(datum[1]);
+ File fbp = new File(datum[2]);
+ File expected = new File(datum[3]);
+
+ Analyzer analyzer = new Analyzer(findBugsDir);
+ File actual = analyzer.outputWorkFile(workDir, fbp);
+ assertEquals(expected.getAbsolutePath(), actual.getAbsolutePath());
+ }
+ }
+
+}
--- /dev/null
+package net.jaekl.qd.xml;
+
+import org.junit.Assert;
+
+import org.junit.Test;
+
+public class MissingInfoExceptionTest {
+
+ @Test
+ public void test_getMessage_withSimpleTag() {
+ final String TAG = "TagNameGoesHere";
+ MissingInfoException mie = new MissingInfoException(TAG);
+ String expected = "Tag: \"" + TAG + "\"";
+ String actual = mie.getMessage();
+ Assert.assertTrue(actual.contains(expected));
+ }
+
+ @Test
+ public void test_getMessage_withAttributesAndChildren() {
+ final String AUGUSTUS = "Augustus";
+ final String NOMEN = "nomen";
+ final String COGNOMEN = "cognomen";
+ final String TIBERIUS = "Tiberius";
+ final String JULIA = "Julia";
+
+ MissingInfoException mie = new MissingInfoException(AUGUSTUS);
+ mie.addMissingAttribute(NOMEN);
+ mie.addMissingAttribute(COGNOMEN);
+ mie.addMissingChild(TIBERIUS);
+ mie.addMissingChild(JULIA);
+
+ String actual = mie.getMessage();
+ Assert.assertTrue(actual.contains("Tag: \"" + AUGUSTUS + "\""));
+ Assert.assertTrue(actual.contains("Attribute: \"" + NOMEN + "\""));
+ Assert.assertTrue(actual.contains("Attribute: \"" + COGNOMEN + "\""));
+ Assert.assertTrue(actual.contains("Child tag: \"" + TIBERIUS + "\""));
+ Assert.assertTrue(actual.contains("Child tag: \"" + JULIA + "\""));
+ }
+
+}
--- /dev/null
+// Copyright (C) 2004, 2015 Christian Jaekl
+
+package net.jaekl.qd.xml;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+
+import org.junit.Assert;
+
+import org.junit.Test;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+public class ParseResultTest {
+ // Some samples of XML that we're going to (try to) parse\
+ private static final String MINIMAL_XML =
+ "<Root/>";
+ private static final String MINIMAL_XML_WITH_PROLOGUE =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root/>";
+ private static final String XML_WITH_MINOR_CONTENT =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root><One/></Root>";
+ private static final String ROOT_INSIDE_SECONDARY_ELEMENT =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Secondary><Root><One/></Root></Secondary>";
+ private static final String PROLOGUE_AND_SECONDARY_ELEMENT =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Secondary/>";
+ private static final String SIMPLE_INTERNAL_TAGS =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root><One/><Two>content of two</Two><Three>3</Three></Root>";
+ private static final String ROUTE_SUMMARY_FOR_STOP =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
+ + "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n"
+ + "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
+ + " <soap:Body>\n"
+ + " <GetRouteSummaryForStopResponse xmlns=\"http://octranspo.com\">\n"
+ + " <GetRouteSummaryForStopResult>\n"
+ + " <StopNo xmlns=\"http://tempuri.org/\">1234</StopNo>\n"
+ + " <StopDescription"
+ + " xmlns=\"http://tempuri.org/\">ONE-TWO-THREE-FOUR</StopDescription>\n"
+ + " <Error xmlns=\"http://tempuri.org/\"/>\n"
+ + " <Routes xmlns=\"http://tempuri.org/\">\n"
+ + " <Route>\n"
+ + " <RouteNo>123</RouteNo>\n"
+ + " <DirectionID>0</DirectionID>\n"
+ + " <Direction>NORTH</Direction>\n"
+ + " <RouteHeading>First Mall</RouteHeading>\n"
+ + " </Route>\n"
+ + " <Route>\n"
+ + " <RouteNo>123</RouteNo>\n"
+ + " <DirectionID>1</DirectionID>\n"
+ + " <Direction>SOUTH</Direction>\n"
+ + " <RouteHeading>Second Mall</RouteHeading>\n"
+ + " </Route>\n"
+ + " </Routes>\n"
+ + " </GetRouteSummaryForStopResult>\n"
+ + " </GetRouteSummaryForStopResponse>\n"
+ + " </soap:Body>\n"
+ + "</soap:Envelope>\n";
+ private static final String NEXT_TRIPS_FOR_STOP =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ + "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">"
+ + "<soap:Body><GetRouteSummaryForStopResponse xmlns=\"http://octranspo.com\">"
+ + "<GetRouteSummaryForStopResult>"
+ + "<StopNo xmlns=\"http://tempuri.org/\">2438</StopNo>"
+ + "<StopDescription xmlns=\"http://tempuri.org/\">BRONSON SUNNYSIDE</StopDescription>"
+ + "<Error xmlns=\"http://tempuri.org/\"/><Routes xmlns=\"http://tempuri.org/\">"
+ + "<Route><RouteNo>4</RouteNo><DirectionID>1</DirectionID><Direction>Northbound</Direction>"
+ + "<RouteHeading>Rideau C / Ctr Rideau</RouteHeading>"
+ + "<Trips>"
+ + "<Trip><TripDestination>Rideau Centre / Centre Rideau</TripDestination><TripStartTime>19:00</TripStartTime>"
+ + "<AdjustedScheduleTime>16</AdjustedScheduleTime><AdjustmentAge>0.45</AdjustmentAge><LastTripOfSchedule/>"
+ + "<BusType>4LB - IN</BusType><Latitude>45.408957</Latitude><Longitude>-75.664125</Longitude>"
+ + "<GPSSpeed>66.4</GPSSpeed></Trip>"
+ + "<Trip><TripDestination>Rideau Centre / Centre Rideau</TripDestination>"
+ + "<TripStartTime>19:30</TripStartTime><AdjustedScheduleTime>40</AdjustedScheduleTime><AdjustmentAge>-1</AdjustmentAge>"
+ + "<LastTripOfSchedule/><BusType>4LB - IN</BusType><Latitude/><Longitude/><GPSSpeed/></Trip>"
+ + "<Trip><TripDestination>Rideau Centre / Centre Rideau</TripDestination><TripStartTime>20:00</TripStartTime>"
+ + "<AdjustedScheduleTime>70</AdjustedScheduleTime><AdjustmentAge>-1</AdjustmentAge><LastTripOfSchedule/>"
+ + "<BusType>4LB - IN</BusType><Latitude/><Longitude/><GPSSpeed/></Trip>"
+ + "</Trips></Route></Routes></GetRouteSummaryForStopResult></GetRouteSummaryForStopResponse>"
+ + "</soap:Body></soap:Envelope>";
+
+ // Do the least possible parsing: check for the <Root/> element only.
+ public static class MinimalParse extends ParseResult {
+ private static final String[] INTERNAL = {};
+ private static final Object[][] EXTERNAL = {} ;
+
+ public MinimalParse() {
+ super("Root", INTERNAL, EXTERNAL);
+ }
+
+ @Override
+ public void endContents(String uri, String localName, String qName,
+ String chars, Attributes attr) throws XmlParseException
+ {
+ Assert.fail("Should not have any contents to end.");
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException
+ {
+ Assert.fail("Should not have any external tags to end.");
+ }
+ }
+
+ // Check that we can parse a minimal document without errors.
+ // Because there's no content being parsed (beyond the root element), there is
+ // no "correct" behaviour to assert. The test is to confirm that we
+ // don't do anything incorrect--no calls to endContent() nor endExternal(),
+ // and no exceptions thrown along the way.
+ @Test
+ public void test_withMinimalParse() throws IOException, SAXException {
+ MinimalParse mp = new MinimalParse();
+ ByteArrayInputStream bais = null;
+
+ String[] data = {
+ MINIMAL_XML,
+ MINIMAL_XML_WITH_PROLOGUE,
+ XML_WITH_MINOR_CONTENT,
+ ROOT_INSIDE_SECONDARY_ELEMENT
+ };
+
+ for (String datum : data) {
+ try {
+ bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
+ XMLReader reader = XMLReaderFactory.createXMLReader();
+ ParseHandler ph = new ParseHandler(mp);
+ reader.setContentHandler(ph);
+ reader.parse(new InputSource(bais));
+ }
+ finally {
+ if (null != bais) {
+ bais.close();
+ }
+ }
+ }
+ }
+
+ // If we parse something that doesn't have the expected root element, we should generate an exception
+ @Test
+ public void test_minimalParseWithMismatchedRootElement() throws IOException {
+ MinimalParse mp = new MinimalParse();
+ ByteArrayInputStream bais = null;
+
+ String[] data = { PROLOGUE_AND_SECONDARY_ELEMENT };
+
+ for (String datum : data) {
+ try {
+ bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
+ XMLReader reader = XMLReaderFactory.createXMLReader();
+ ParseHandler ph = new ParseHandler(mp);
+ reader.setContentHandler(ph);
+ reader.parse(new InputSource(bais));
+ Assert.fail("Should have thrown an exception.");
+ }
+ catch ( SAXException se ) {
+ Throwable cause = se.getCause();
+ Assert.assertNotNull(cause);
+ Assert.assertTrue(cause instanceof MissingInfoException);
+ MissingInfoException mie = (MissingInfoException) cause;
+ Assert.assertEquals("Root", mie.getTagName());
+ }
+ finally {
+ if (null != bais) {
+ bais.close();
+ }
+ }
+ }
+ }
+
+ // Do the some simple parsing: <Root/> and some subtags that are processed internally
+ public static class SimpleParse extends ParseResult {
+ private static final String ONE = "One";
+ private static final String TWO = "Two";
+ private static final String THREE = "Three";
+
+ private static final String[] INTERNAL = {ONE, TWO, THREE};
+ private static final Object[][] EXTERNAL = {} ;
+
+ String m_one;
+ String m_two;
+ String m_three;
+
+ public SimpleParse() {
+ super("Root", INTERNAL, EXTERNAL);
+
+ m_one = m_two = m_three = null;
+ }
+
+ public String getOne() { return m_one; }
+ public String getTwo() { return m_two; }
+ public String getThree() { return m_three; }
+
+ @Override
+ public void endContents(String uri, String localName, String qName,
+ String chars, Attributes attr) throws XmlParseException
+ {
+ if (localName.equals(ONE)) {
+ m_one = chars;
+ }
+ else if (localName.equals(TWO)) {
+ m_two = chars;
+ }
+ else if (localName.equals(THREE)) {
+ m_three = chars;
+ }
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException
+ {
+ Assert.fail("Should not have any external tags to end.");
+ }
+ }
+
+ // Parse some XML containing subtags that are handled internally by SimpleParse
+ @Test
+ public void test_parseWithInternalSubtags() throws IOException, SAXException
+ {
+ SimpleParse sp = new SimpleParse();
+ ByteArrayInputStream bais = null;
+
+ String[] data = {
+ SIMPLE_INTERNAL_TAGS
+ };
+
+ for (String datum : data) {
+ try {
+ bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
+ XMLReader reader = XMLReaderFactory.createXMLReader();
+ ParseHandler ph = new ParseHandler(sp);
+ reader.setContentHandler(ph);
+ reader.parse(new InputSource(bais));
+
+ Assert.assertEquals("", sp.getOne());
+ Assert.assertEquals("content of two", sp.getTwo());
+ Assert.assertEquals("3", sp.getThree());
+ }
+ finally {
+ if (null != bais) {
+ bais.close();
+ }
+ }
+ }
+ }
+
+ // Parse sub-tags, handling some internally and some externally
+ public static class RouteSummaryParse extends ParseResult {
+ private static final String STOP_NO = "StopNo";
+ private static final String STOP_DESCR = "StopDescription";
+ private static final String ERROR = "Error";
+ private static final String ROUTES = "Routes";
+ private static final String ROUTE = "Route";
+
+ private static final String[] INTERNAL = {STOP_NO, STOP_DESCR, ERROR, ROUTES};
+ private static final Object[][] EXTERNAL = { {ROUTE, RouteParse.class} };
+
+ // Data gleaned from parsing
+ int m_stopNo;
+ String m_stopDescr;
+ String m_error;
+ ArrayList<RouteParse> m_routes;
+
+ public RouteSummaryParse() {
+ super("GetRouteSummaryForStopResult", INTERNAL, EXTERNAL);
+
+ m_stopNo = 0;
+ m_stopDescr = m_error = null;
+ m_routes = new ArrayList<RouteParse>();
+ }
+
+ public int getStopNo() { return m_stopNo; }
+ public String getStopDescription() { return m_stopDescr; }
+ public String getError() { return m_error; }
+ public int getNumRoutes() { return m_routes.size(); }
+ public RouteParse getRoute(int idx) { return m_routes.get(idx); }
+
+ @Override
+ public void endContents(String uri, String localName, String qName,
+ String chars, Attributes attr) throws XmlParseException
+ {
+ if (localName.equals(STOP_NO)) {
+ m_stopNo = Integer.parseInt(chars);
+ }
+ else if (localName.equals(STOP_DESCR)) {
+ m_stopDescr = chars;
+ }
+ else if (localName.equals(ERROR)) {
+ m_error = chars;
+ }
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException
+ {
+ if (localName.equals(ROUTE)) {
+ ParseResult[] collected = collectParsedChildren(RouteParse.class);
+ for (ParseResult pr : collected) {
+ Assert.assertTrue(pr instanceof RouteParse);
+ m_routes.add((RouteParse)pr);
+ }
+ }
+ }
+ }
+ public static class RouteParse extends ParseResult {
+ private static final String ROUTE = "Route";
+ private static final String ROUTE_NO = "RouteNo";
+ private static final String DIR_ID = "DirectionID";
+ private static final String DIR = "Direction";
+ private static final String HEADING = "RouteHeading";
+ private static final String TRIPS = "Trips";
+ private static final String TRIP = "Trip";
+
+ private static final String[] INTERNAL = {ROUTE_NO, DIR_ID, DIR, HEADING, TRIPS};
+ private static final Object[][] EXTERNAL = { {TRIP, TripParse.class} };
+
+ // Data gleaned from parsing
+ int m_routeNo;
+ int m_dirID;
+ String m_dir;
+ String m_heading;
+ ArrayList<TripParse> m_trips;
+
+ public RouteParse() {
+ super(ROUTE, INTERNAL, EXTERNAL);
+
+ m_routeNo = m_dirID = 0;
+ m_dir = m_heading = null;
+ m_trips = new ArrayList<TripParse>();
+ }
+
+ public int getRouteNo() { return m_routeNo; }
+ public int getDirectionID() { return m_dirID; }
+ public String getDirection() { return m_dir; }
+ public String getHeading() { return m_heading; }
+ public int getNumTrips() { return m_trips.size(); }
+ public TripParse getTrip(int idx) { return m_trips.get(idx); }
+
+ @Override
+ public void endContents(String uri, String localName, String qName,
+ String chars, Attributes attr) throws XmlParseException
+ {
+ if (localName.equals(ROUTE_NO)) {
+ m_routeNo = Integer.parseInt(chars);
+ }
+ else if (localName.equals(DIR_ID)) {
+ m_dirID = Integer.parseInt(chars);
+ }
+ else if (localName.equals(DIR)) {
+ m_dir = chars;
+ }
+ else if (localName.equals(HEADING)) {
+ m_heading = chars;
+ }
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException
+ {
+ if (localName.equals(TRIP)) {
+ ParseResult[] collected = collectParsedChildren(TripParse.class);
+ for (ParseResult pr : collected) {
+ Assert.assertTrue(pr instanceof TripParse);
+ m_trips.add((TripParse)pr);
+ }
+ }
+
+ }
+ }
+ public static class TripParse extends ParseResult {
+ private static final String TRIP = "Trip";
+ private static final String TRIP_DEST = "TripDestination";
+ private static final String TRIP_START = "TripStartTime";
+ private static final String ADJ_SCHED_TIME = "AdjustedScheduleTime";
+
+ private static final String[] INTERNAL = {TRIP_DEST, TRIP_START, ADJ_SCHED_TIME };
+ private static final Object[][] EXTERNAL = { };
+
+ // Data gleaned from parsing
+ String m_dest;
+ String m_startTime;
+ int m_adjSchedTime;;
+
+ public TripParse() {
+ super(TRIP, INTERNAL, EXTERNAL);
+
+ m_dest = m_startTime = null;
+ m_adjSchedTime = 0;
+ }
+
+ public String getDestination() { return m_dest; }
+ public String getStartTime() { return m_startTime; }
+ public int getAdjustedScheduleTime() { return m_adjSchedTime; }
+
+ @Override
+ public void endContents(String uri, String localName, String qName,
+ String chars, Attributes attr) throws XmlParseException
+ {
+ if (localName.equals(TRIP_DEST)) {
+ m_dest = chars;
+ }
+ else if (localName.equals(TRIP_START)) {
+ m_startTime = chars;
+ }
+ else if (localName.equals(ADJ_SCHED_TIME)) {
+ m_adjSchedTime = Integer.parseInt(chars);
+ }
+ }
+
+ @Override
+ public void endExternal(String uri, String localName, String qName)
+ throws XmlParseException
+ {
+ Assert.fail("Should not be attempting to parse external tags.");
+ }
+ }
+
+ // Parse some XML containing subtags that are handled both internally and externally
+ @Test
+ public void test_parseRouteSummary() throws IOException, SAXException
+ {
+ RouteSummaryParse rsp = new RouteSummaryParse();
+ ByteArrayInputStream bais = null;
+
+ try {
+ RouteParse rp;
+
+ bais = new ByteArrayInputStream(ROUTE_SUMMARY_FOR_STOP.getBytes("UTF-8"));
+ XMLReader reader = XMLReaderFactory.createXMLReader();
+ ParseHandler ph = new ParseHandler(rsp);
+ reader.setContentHandler(ph);
+ reader.parse(new InputSource(bais));
+
+ Assert.assertEquals(1234, rsp.getStopNo());
+ Assert.assertEquals("ONE-TWO-THREE-FOUR", rsp.getStopDescription());
+ Assert.assertEquals("", rsp.getError());
+
+ Assert.assertEquals(2, rsp.getNumRoutes());
+
+ rp = rsp.getRoute(0);
+ Assert.assertNotNull(rp);
+ Assert.assertEquals(123, rp.getRouteNo());
+ Assert.assertEquals(0, rp.getDirectionID());
+ Assert.assertEquals("NORTH", rp.getDirection());
+ Assert.assertEquals("First Mall", rp.getHeading());
+
+ rp = rsp.getRoute(1);
+ Assert.assertNotNull(rp);
+ Assert.assertEquals(123, rp.getRouteNo());
+ Assert.assertEquals(1, rp.getDirectionID());
+ Assert.assertEquals("SOUTH", rp.getDirection());
+ Assert.assertEquals("Second Mall", rp.getHeading());
+ }
+ finally {
+ if (null != bais) {
+ bais.close();
+ }
+ }
+ }
+
+ // Parse a 3-level external-tag hierarchy: RouteSummary contains Routes contains Trips
+ @Test
+ public void test_parseThreeLevels() throws IOException, SAXException
+ {
+ RouteSummaryParse rsp = new RouteSummaryParse();
+ ByteArrayInputStream bais = null;
+
+ try {
+ RouteParse rp;
+ TripParse tp;
+
+ bais = new ByteArrayInputStream(NEXT_TRIPS_FOR_STOP.getBytes("UTF-8"));
+ XMLReader reader = XMLReaderFactory.createXMLReader();
+ ParseHandler ph = new ParseHandler(rsp);
+ reader.setContentHandler(ph);
+ reader.parse(new InputSource(bais));
+
+ Assert.assertEquals(2438, rsp.getStopNo());
+ Assert.assertEquals("BRONSON SUNNYSIDE", rsp.getStopDescription());
+ Assert.assertEquals("", rsp.getError());
+
+ Assert.assertEquals(1, rsp.getNumRoutes());
+
+ rp = rsp.getRoute(0);
+ Assert.assertNotNull(rp);
+ Assert.assertEquals(4, rp.getRouteNo());
+ Assert.assertEquals(1, rp.getDirectionID());
+ Assert.assertEquals("Northbound", rp.getDirection());
+ Assert.assertEquals("Rideau C / Ctr Rideau", rp.getHeading());
+
+ Assert.assertEquals(3, rp.getNumTrips());
+
+ tp = rp.getTrip(0);
+ Assert.assertNotNull(tp);
+ Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
+ Assert.assertEquals("19:00", tp.getStartTime());
+ Assert.assertEquals(16, tp.getAdjustedScheduleTime());
+
+ tp = rp.getTrip(1);
+ Assert.assertNotNull(tp);
+ Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
+ Assert.assertEquals("19:30", tp.getStartTime());
+ Assert.assertEquals(40, tp.getAdjustedScheduleTime());
+
+ tp = rp.getTrip(2);
+ Assert.assertNotNull(tp);
+ Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
+ Assert.assertEquals("20:00", tp.getStartTime());
+ Assert.assertEquals(70, tp.getAdjustedScheduleTime());
+ }
+ finally {
+ if (null != bais) {
+ bais.close();
+ }
+ }
+ }
+}