From 5bc9bbe3fd54b9fc7aa3b92d2d37e95c41b9645a Mon Sep 17 00:00:00 2001 From: Chris Jaekl Date: Sun, 6 Sep 2015 15:17:10 +0900 Subject: [PATCH] Improve XML parsing to handle attributes as well. Add debug-log dump() function to validate that XML has been parsed correctly. Update (C) statements. --- cov.sh | 51 +++++++++++++++++++ prod/net/jaekl/cfb/CFB.java | 14 +++-- prod/net/jaekl/cfb/CfbBundle.java | 2 + prod/net/jaekl/cfb/analyze/Analysis.java | 48 ++++++++++++++++- prod/net/jaekl/cfb/analyze/Analyzer.java | 19 +++++-- prod/net/jaekl/cfb/db/CfbSchema.java | 2 + prod/net/jaekl/cfb/db/Column.java | 2 + prod/net/jaekl/cfb/db/Schema.java | 2 + prod/net/jaekl/cfb/db/Table.java | 2 + prod/net/jaekl/cfb/db/driver/DbDriver.java | 2 + .../jaekl/cfb/db/driver/PostgresqlDriver.java | 2 + prod/net/jaekl/cfb/util/Command.java | 2 + prod/net/jaekl/cfb/util/Util.java | 2 + prod/net/jaekl/cfb/xml/BugClass.java | 30 +++++++++-- prod/net/jaekl/cfb/xml/BugCollection.java | 34 ++++++++++--- prod/net/jaekl/cfb/xml/BugInstance.java | 34 ++++++++++++- prod/net/jaekl/cfb/xml/BugMethod.java | 38 ++++++++++++-- prod/net/jaekl/cfb/xml/LocalVariable.java | 23 ++++++++- prod/net/jaekl/cfb/xml/SourceLine.java | 29 ++++++++--- prod/net/jaekl/qd/xml/ParseResult.java | 36 ++++++++++--- test/net/jaekl/qd/xml/ParseResultTest.java | 35 +++++++++---- 21 files changed, 359 insertions(+), 50 deletions(-) create mode 100755 cov.sh diff --git a/cov.sh b/cov.sh new file mode 100755 index 0000000..0948141 --- /dev/null +++ b/cov.sh @@ -0,0 +1,51 @@ +#!/bin/bash +CFB_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +INSTR_DIR="${CFB_ROOT}/../instr" + +##################### +echo Compiling... +find "${CFB_ROOT}/prod" -name "*.java" | xargs javac -g -Xlint:deprecation +find "${CFB_ROOT}/test" -name "*.java" | xargs javac -g -classpath ${CFB_ROOT}/prod:${CLASSPATH} -Xlint:deprecation + +##################### +echo Cleaning old coverage files... +for x in "${INSTR_DIR}" report +do + if [ -d "${x}" ]; then + rm -rf "${x}" + fi + mkdir -p "${x}" +done +for x in result.xml template.xml +do + if [ -w ${x} ]; then + rm ${x} + fi +done + +##################### +echo Instrumenting... +java -classpath "${CLASSPATH}" -jar `pwd`/jcov/jcov.jar Instr -t template.xml -o "${INSTR_DIR}" -type all "${CFB_ROOT}/prod" + +##################### +echo Running unit tests... + +TESTS="" +for x in `cd ${CFB_ROOT}/test; find . -name '*Test.class'` +do + #echo CANDIDATE $x + TEST_CLASS=`echo ${x} | sed s:\^./:: | cut -d . -f 1 | sed s:/:.:g` + #echo TEST_CLASS ${TEST_CLASS} + TESTS="${TEST_CLASS} ${TESTS}" +done + +echo First run: locale es_ES, timezone Europe/Madrid +java -Duser.language=es -Duser.country=ES -Duser.timezone=Europe/Madrid -Djcov.template=${CFB_ROOT}/template.xml -Djcov.file=${CFB_ROOT}/result.xml -classpath "${INSTR_DIR}:${CFB_ROOT}/test:${CLASSPATH}:/usr/share/java/junit.jar:${CFB_ROOT}/jcov/jcov_file_saver.jar" org.junit.runner.JUnitCore ${TESTS} + +echo Second run: server default locale and timezone +java -Djcov.template=${CFB_ROOT}/template.xml -Djcov.file=${CFB_ROOT}/result.xml -classpath "${INSTR_DIR}:${CFB_ROOT}/test:${CLASSPATH}:/usr/share/java/junit.jar:${CFB_ROOT}/jcov/jcov_file_saver.jar" org.junit.runner.JUnitCore ${TESTS} + +##################### +echo Generating HTML Report... + +java -jar "${CFB_ROOT}/jcov/jcov.jar" RepGen -sourcepath "${CFB_ROOT}/prod" -log.level FINE result.xml diff --git a/prod/net/jaekl/cfb/CFB.java b/prod/net/jaekl/cfb/CFB.java index 7dca443..d587c18 100644 --- a/prod/net/jaekl/cfb/CFB.java +++ b/prod/net/jaekl/cfb/CFB.java @@ -1,5 +1,12 @@ package net.jaekl.cfb; +// Comparative FindBugs +// +// Tool to compare successive runs of FindBugs, +// flagging the change from one run to the next. +// +// Copyright (C) 2015 Christian Jaekl + import java.io.File; import java.io.IOException; import java.io.PrintWriter; @@ -13,6 +20,7 @@ import net.jaekl.cfb.analyze.Analyzer; import net.jaekl.cfb.db.CfbSchema; import net.jaekl.cfb.db.driver.DbDriver; import net.jaekl.cfb.db.driver.PostgresqlDriver; +import net.jaekl.qd.xml.XmlParseException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.GnuParser; @@ -123,9 +131,9 @@ public class CFB { if (null != findBugsDir) { m_fbDir = new File(findBugsDir); } - } + } - void doMain(PrintWriter pw, String[] args) throws SQLException, IOException { + void doMain(PrintWriter pw, String[] args) throws SQLException, IOException, XmlParseException { initArgs(); // read environment and system properties if ( ! parseArgs(pw, args) ) { return; @@ -157,7 +165,7 @@ public class CFB { try (PrintWriter pw = new PrintWriter(System.out)){ cfb.doMain(pw, args); pw.flush(); - } catch (SQLException | IOException exc) { + } catch (SQLException | IOException | XmlParseException exc) { exc.printStackTrace(); } } diff --git a/prod/net/jaekl/cfb/CfbBundle.java b/prod/net/jaekl/cfb/CfbBundle.java index a643c6e..c83182c 100644 --- a/prod/net/jaekl/cfb/CfbBundle.java +++ b/prod/net/jaekl/cfb/CfbBundle.java @@ -1,5 +1,7 @@ package net.jaekl.cfb; +// Copyright (C) 2015 Christian Jaekl + import java.util.Locale; import java.util.MissingResourceException; import java.util.ResourceBundle; diff --git a/prod/net/jaekl/cfb/analyze/Analysis.java b/prod/net/jaekl/cfb/analyze/Analysis.java index e8a938a..4e289e2 100644 --- a/prod/net/jaekl/cfb/analyze/Analysis.java +++ b/prod/net/jaekl/cfb/analyze/Analysis.java @@ -1,5 +1,51 @@ package net.jaekl.cfb.analyze; -public class Analysis { +// Copyright (C) 2015 Christian Jaekl + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; + +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.XMLReaderFactory; +import net.jaekl.cfb.xml.BugCollection; +import net.jaekl.qd.util.InputStreamWrapper; +import net.jaekl.qd.xml.ParseErrorHandler; +import net.jaekl.qd.xml.ParseHandler; + +public class Analysis { + BugCollection m_bugCollection; + + public Analysis() { + m_bugCollection = null; + } + + public BugCollection getBugCollection() { return m_bugCollection; } + + public void parse(File xml) throws FileNotFoundException, IOException, SAXException + { + m_bugCollection = new BugCollection(); + + try (InputStreamWrapper isw = new InputStreamWrapper(new FileInputStream(xml))) + { + XMLReader reader = XMLReaderFactory.createXMLReader(); + ParseHandler ph = new ParseHandler(m_bugCollection); + ParseErrorHandler peh = new ParseErrorHandler(); + reader.setContentHandler(ph); + reader.setErrorHandler(peh); + reader.parse(new InputSource(isw)); + } + } + + public void dump(PrintWriter pw) + { + if (null != m_bugCollection) { + m_bugCollection.dump(pw, 2); + } + } } diff --git a/prod/net/jaekl/cfb/analyze/Analyzer.java b/prod/net/jaekl/cfb/analyze/Analyzer.java index 715cae9..9a8d7a6 100644 --- a/prod/net/jaekl/cfb/analyze/Analyzer.java +++ b/prod/net/jaekl/cfb/analyze/Analyzer.java @@ -1,5 +1,7 @@ package net.jaekl.cfb.analyze; +// Copyright (C) 2015 Christian Jaekl + import java.io.File; import java.io.IOException; import java.io.PrintWriter; @@ -7,8 +9,11 @@ import java.text.MessageFormat; import java.util.Locale; import java.util.Locale.Category; +import org.xml.sax.SAXException; + import net.jaekl.cfb.CfbBundle; import net.jaekl.cfb.util.Command; +import net.jaekl.qd.xml.XmlParseException; public class Analyzer { File m_findbugsDir; @@ -17,7 +22,7 @@ public class Analyzer { m_findbugsDir = findbugsDir; } - public Analysis analyze(PrintWriter pw, File workDir, File fbp) throws IOException { + public Analysis analyze(PrintWriter pw, File workDir, File fbp) throws IOException, XmlParseException { Analysis result = new Analysis(); File fbOutput = outputWorkFile(workDir, fbp); @@ -40,7 +45,7 @@ public class Analyzer { } result = parseFbOutput(fbOutput); - + result.dump(pw); return result; } @@ -92,8 +97,14 @@ public class Analyzer { // Parse the output.xml that resulted from a FindBugs run, // and store its findings into an Analysis object. - Analysis parseFbOutput(File fbOutput) + Analysis parseFbOutput(File fbOutput) throws XmlParseException { - return null; + Analysis result = new Analysis(); + try { + result.parse(fbOutput); + } catch (IOException | SAXException exc) { + throw new XmlParseException(exc); + } + return result; } } diff --git a/prod/net/jaekl/cfb/db/CfbSchema.java b/prod/net/jaekl/cfb/db/CfbSchema.java index 52e067b..d2333fa 100644 --- a/prod/net/jaekl/cfb/db/CfbSchema.java +++ b/prod/net/jaekl/cfb/db/CfbSchema.java @@ -1,5 +1,7 @@ package net.jaekl.cfb.db; +// Copyright (C) 2015 Christian Jaekl + import static net.jaekl.cfb.db.Column.Null.*; import static net.jaekl.cfb.db.Column.Type.*; import net.jaekl.cfb.db.driver.DbDriver; diff --git a/prod/net/jaekl/cfb/db/Column.java b/prod/net/jaekl/cfb/db/Column.java index bd06570..527ad7d 100644 --- a/prod/net/jaekl/cfb/db/Column.java +++ b/prod/net/jaekl/cfb/db/Column.java @@ -1,5 +1,7 @@ package net.jaekl.cfb.db; +// Copyright (C) 2015 Christian Jaekl + public class Column { public enum Type { CHAR, INTEGER, TIMESTAMP, TIMESTAMPTZ, VARCHAR diff --git a/prod/net/jaekl/cfb/db/Schema.java b/prod/net/jaekl/cfb/db/Schema.java index 176526d..b6dc601 100644 --- a/prod/net/jaekl/cfb/db/Schema.java +++ b/prod/net/jaekl/cfb/db/Schema.java @@ -1,5 +1,7 @@ package net.jaekl.cfb.db; +// Copyright (C) 2015 Christian Jaekl + import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; diff --git a/prod/net/jaekl/cfb/db/Table.java b/prod/net/jaekl/cfb/db/Table.java index 64b2caa..a687842 100644 --- a/prod/net/jaekl/cfb/db/Table.java +++ b/prod/net/jaekl/cfb/db/Table.java @@ -1,5 +1,7 @@ package net.jaekl.cfb.db; +// Copyright (C) 2015 Christian Jaekl + import java.util.ArrayList; import java.util.Arrays; diff --git a/prod/net/jaekl/cfb/db/driver/DbDriver.java b/prod/net/jaekl/cfb/db/driver/DbDriver.java index 9de7264..c864957 100644 --- a/prod/net/jaekl/cfb/db/driver/DbDriver.java +++ b/prod/net/jaekl/cfb/db/driver/DbDriver.java @@ -1,5 +1,7 @@ package net.jaekl.cfb.db.driver; +// Copyright (C) 2015 Christian Jaekl + import static net.jaekl.cfb.db.Column.Null.*; import java.sql.Connection; diff --git a/prod/net/jaekl/cfb/db/driver/PostgresqlDriver.java b/prod/net/jaekl/cfb/db/driver/PostgresqlDriver.java index 55994b3..0fe1f08 100644 --- a/prod/net/jaekl/cfb/db/driver/PostgresqlDriver.java +++ b/prod/net/jaekl/cfb/db/driver/PostgresqlDriver.java @@ -1,5 +1,7 @@ package net.jaekl.cfb.db.driver; +// Copyright (C) 2015 Christian Jaekl + import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; diff --git a/prod/net/jaekl/cfb/util/Command.java b/prod/net/jaekl/cfb/util/Command.java index 4242eea..672aac2 100644 --- a/prod/net/jaekl/cfb/util/Command.java +++ b/prod/net/jaekl/cfb/util/Command.java @@ -1,5 +1,7 @@ package net.jaekl.cfb.util; +// Copyright (C) 2015 Christian Jaekl + import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; diff --git a/prod/net/jaekl/cfb/util/Util.java b/prod/net/jaekl/cfb/util/Util.java index c0e7b1c..30f4c90 100644 --- a/prod/net/jaekl/cfb/util/Util.java +++ b/prod/net/jaekl/cfb/util/Util.java @@ -1,5 +1,7 @@ package net.jaekl.cfb.util; +// Copyright (C) 2015 Christian Jaekl + import java.io.PrintWriter; import java.io.StringWriter; diff --git a/prod/net/jaekl/cfb/xml/BugClass.java b/prod/net/jaekl/cfb/xml/BugClass.java index eb2c592..40cc84b 100644 --- a/prod/net/jaekl/cfb/xml/BugClass.java +++ b/prod/net/jaekl/cfb/xml/BugClass.java @@ -1,9 +1,11 @@ package net.jaekl.cfb.xml; +import java.io.PrintWriter; import java.util.ArrayList; import org.xml.sax.Attributes; +import net.jaekl.qd.xml.MissingAttributeException; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -16,18 +18,24 @@ public class BugClass extends ParseResult { static final String CLASS_NAME = "classname"; String m_className; - ArrayList m_sourceLines; + ArrayList m_lines; public BugClass() { super(TAG, INTERNAL, EXTERNAL); m_className = ""; + m_lines = new ArrayList(); + } + + @Override + public void handleMainAttributes(Attributes attr) throws MissingAttributeException + { + m_className = getRequiredAttr(TAG, attr, CLASS_NAME); } @Override - public void endContents(String uri, String localName, String qName, String chars, Attributes attr) + public void endContents(String uri, String localName, String qName, String chars) throws XmlParseException { - m_className = getRequiredAttr(TAG, attr, CLASS_NAME); } @Override @@ -38,8 +46,22 @@ public class BugClass extends ParseResult { ParseResult[] collected = collectParsedChildren(SourceLine.class); for (ParseResult pr : collected) { assert(pr instanceof SourceLine); - m_sourceLines.add((SourceLine)pr); + m_lines.add((SourceLine)pr); } } } + + @Override + public void dump(PrintWriter pw, int indent) + { + super.dump(pw, indent); + String tab = String.format("%" + (indent + 2) + "s", ""); + + pw.println(tab + CLASS_NAME + "=" + m_className); + if (null != m_lines) { + for (SourceLine sl : m_lines) { + sl.dump(pw, indent + 2); + } + } + } } diff --git a/prod/net/jaekl/cfb/xml/BugCollection.java b/prod/net/jaekl/cfb/xml/BugCollection.java index b59bdc8..8fb78e4 100644 --- a/prod/net/jaekl/cfb/xml/BugCollection.java +++ b/prod/net/jaekl/cfb/xml/BugCollection.java @@ -1,6 +1,7 @@ package net.jaekl.cfb.xml; -import org.xml.sax.Attributes; +import java.io.PrintWriter; +import java.util.ArrayList; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -11,22 +12,39 @@ public class BugCollection extends ParseResult { static final String[] INTERNAL = { }; static final Object[][] EXTERNAL = { { BugInstance.TAG, BugInstance.class} }; + ArrayList m_bugs; + public BugCollection() { super(TAG, INTERNAL, EXTERNAL); + m_bugs = new ArrayList(); } @Override - public void endContents(String uri, String localName, String qName, - String chars, Attributes attr) throws XmlParseException { - // TODO Auto-generated method stub - + public void endContents(String uri, String localName, String qName, String chars) + throws XmlParseException + { + // no-op } @Override public void endExternal(String uri, String localName, String qName) - throws XmlParseException { - // TODO Auto-generated method stub - + throws XmlParseException + { + if (BugInstance.TAG.equals(localName)) { + ParseResult[] collected = collectParsedChildren(BugInstance.class); + for (ParseResult pr : collected) { + assert(pr instanceof BugInstance); + m_bugs.add((BugInstance) pr); + } + } + } + + @Override + public void dump(PrintWriter pw, int indent) { + super.dump(pw, indent); + for (BugInstance bug : m_bugs) { + bug.dump(pw, indent + 2); + } } } diff --git a/prod/net/jaekl/cfb/xml/BugInstance.java b/prod/net/jaekl/cfb/xml/BugInstance.java index f1f8537..900cd1c 100644 --- a/prod/net/jaekl/cfb/xml/BugInstance.java +++ b/prod/net/jaekl/cfb/xml/BugInstance.java @@ -1,9 +1,11 @@ package net.jaekl.cfb.xml; +import java.io.PrintWriter; import java.util.ArrayList; import org.xml.sax.Attributes; +import net.jaekl.qd.xml.MissingAttributeException; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -15,7 +17,9 @@ public class BugInstance extends ParseResult { { BugMethod.TAG, BugMethod.class}, { LocalVariable.TAG, LocalVariable.class}, { SourceLine.TAG, SourceLine.class} }; + static final String TYPE = "type"; + String m_type; ArrayList m_classes; ArrayList m_methods; ArrayList m_locals; @@ -24,6 +28,7 @@ public class BugInstance extends ParseResult { public BugInstance() { super(TAG, INTERNAL, EXTERNAL); + m_type = null; m_classes = new ArrayList(); m_methods = new ArrayList(); m_locals = new ArrayList(); @@ -31,9 +36,10 @@ public class BugInstance extends ParseResult { } @Override - public void endContents(String uri, String localName, String qName, String chars, Attributes attr) + public void endContents(String uri, String localName, String qName, String chars) throws XmlParseException { + // no-op } @Override @@ -69,5 +75,31 @@ public class BugInstance extends ParseResult { } } } + + @Override + public void handleMainAttributes(Attributes attr) throws MissingAttributeException + { + m_type = this.getRequiredAttr(TAG, attr, TYPE); + } + @Override + public void dump(PrintWriter pw, int indent) + { + int childIndent = indent + 2; + String margin = String.format("%" + indent + "s", ""); + + pw.println(margin + TAG + " (" + m_type + ")"); + for (BugClass bc : m_classes) { + bc.dump(pw, childIndent); + } + for (BugMethod bm : m_methods) { + bm.dump(pw, childIndent); + } + for (LocalVariable lv : m_locals) { + lv.dump(pw, childIndent); + } + for (SourceLine sl : m_lines) { + sl.dump(pw, childIndent); + } + } } diff --git a/prod/net/jaekl/cfb/xml/BugMethod.java b/prod/net/jaekl/cfb/xml/BugMethod.java index 2e001b2..80afa93 100644 --- a/prod/net/jaekl/cfb/xml/BugMethod.java +++ b/prod/net/jaekl/cfb/xml/BugMethod.java @@ -1,9 +1,11 @@ package net.jaekl.cfb.xml; +import java.io.PrintWriter; import java.util.ArrayList; import org.xml.sax.Attributes; +import net.jaekl.qd.xml.MissingAttributeException; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -34,16 +36,22 @@ public class BugMethod extends ParseResult { m_isStatic = false; m_sourceLines = new ArrayList(); } - + @Override - public void endContents(String uri, String localName, String qName, String chars, Attributes attr) - throws XmlParseException + public void handleMainAttributes(Attributes attr) throws MissingAttributeException { m_className = getRequiredAttr(TAG, attr, CLASS_NAME); m_methodName = getRequiredAttr(TAG, attr, METHOD_NAME); m_signature = getRequiredAttr(TAG, attr, SIGNATURE); m_isStatic = getRequiredAttr(TAG, attr, IS_STATIC).equals(TRUE); - m_role = getOptionalAttr(attr, ROLE, null); + m_role = getOptionalAttr(attr, ROLE, null); + } + + @Override + public void endContents(String uri, String localName, String qName, String chars) + throws XmlParseException + { + // no-op } @Override @@ -58,5 +66,27 @@ public class BugMethod extends ParseResult { } } } + + @Override + public void dump(PrintWriter pw, int indent) + { + super.dump(pw, indent); + String tab = String.format("%" + (indent + 2) + "s", ""); + + pw.println(tab + + (m_isStatic ? "static" : "") + + m_className + + "." + + m_methodName + + m_signature); + if (null != m_role) { + pw.println(tab + ROLE + "=" + m_role); + } + if (null != m_sourceLines) { + for (SourceLine sl : m_sourceLines) { + sl.dump(pw, indent + 2); + } + } + } } diff --git a/prod/net/jaekl/cfb/xml/LocalVariable.java b/prod/net/jaekl/cfb/xml/LocalVariable.java index b52823f..fb19868 100644 --- a/prod/net/jaekl/cfb/xml/LocalVariable.java +++ b/prod/net/jaekl/cfb/xml/LocalVariable.java @@ -1,7 +1,10 @@ package net.jaekl.cfb.xml; +import java.io.PrintWriter; + import org.xml.sax.Attributes; +import net.jaekl.qd.xml.MissingAttributeException; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -24,12 +27,18 @@ public class LocalVariable extends ParseResult { } @Override - public void endContents(String uri, String localName, String qName, String chars, Attributes attr) - throws XmlParseException + public void handleMainAttributes(Attributes attr) + throws MissingAttributeException { m_name = getRequiredAttr(TAG, attr, NAME); m_role = getRequiredAttr(TAG, attr, ROLE); } + + @Override + public void endContents(String uri, String localName, String qName, String chars) + throws XmlParseException + { + } @Override public void endExternal(String uri, String localName, String qName) @@ -37,4 +46,14 @@ public class LocalVariable extends ParseResult { { // no-op } + + @Override + public void dump(PrintWriter pw, int indent) + { + super.dump(pw, indent); + String tab = String.format("%" + (indent + 2) + "s", ""); + + pw.println(tab + NAME + "=" + m_name); + pw.println(tab + ROLE + "=" + m_role); + } } diff --git a/prod/net/jaekl/cfb/xml/SourceLine.java b/prod/net/jaekl/cfb/xml/SourceLine.java index 627667a..c719529 100644 --- a/prod/net/jaekl/cfb/xml/SourceLine.java +++ b/prod/net/jaekl/cfb/xml/SourceLine.java @@ -1,5 +1,8 @@ package net.jaekl.cfb.xml; +import java.io.PrintWriter; + +import net.jaekl.qd.xml.MissingAttributeException; import net.jaekl.qd.xml.ParseResult; import net.jaekl.qd.xml.XmlParseException; @@ -26,24 +29,38 @@ public class SourceLine extends ParseResult { } @Override - public void endContents(String uri, String localName, String qName, String chars, Attributes attr) - throws XmlParseException - { + public void handleMainAttributes(Attributes attr) throws MissingAttributeException { String scratch; - m_className = getRequiredAttr(localName, attr, ATTR_CLASS_NAME); + m_className = getRequiredAttr(TAG, attr, ATTR_CLASS_NAME); - scratch = getRequiredAttr(localName, attr, ATTR_START); + scratch = getRequiredAttr(TAG, attr, ATTR_START); m_start = Integer.parseInt(scratch); - scratch = getRequiredAttr(localName, attr, ATTR_END); + scratch = getRequiredAttr(TAG, attr, ATTR_END); m_end = Integer.parseInt(scratch); } + @Override + public void endContents(String uri, String localName, String qName, String chars) + throws XmlParseException + { + // no-op + } + @Override public void endExternal(String uri, String localName, String qName) throws XmlParseException { // no-op } + + @Override + public void dump(PrintWriter pw, int indent) + { + super.dump(pw, indent); + String tab = String.format("%" + (indent + 2) + "s", ""); + + pw.println(tab + m_className + " (" + m_start + " .. " + m_end + ")"); + } } diff --git a/prod/net/jaekl/qd/xml/ParseResult.java b/prod/net/jaekl/qd/xml/ParseResult.java index e821015..f653b36 100644 --- a/prod/net/jaekl/qd/xml/ParseResult.java +++ b/prod/net/jaekl/qd/xml/ParseResult.java @@ -8,6 +8,7 @@ package net.jaekl.qd.xml; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -67,17 +68,35 @@ public abstract class ParseResult } } - public abstract void endContents(String uri, String localName, String qName, String chars, Attributes attr) throws XmlParseException; + public abstract void endContents(String uri, String localName, String qName, String chars) throws XmlParseException; public abstract void endExternal(String uri, String localName, String qName) throws XmlParseException; + + // Called once for this tag itself + public void handleMainAttributes(Attributes attr) throws MissingAttributeException { + // Default action is to do nothing. + // Override this if you want to process tag attributes. + } + + // Called once for each internally-handled subtag + public void handleInternalAttributes(String tagName, Attributes attr) throws MissingAttributeException { + // Default action is to do nothing. + // Override this if you want to process tag attributes. + } 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); } + // Dump human-readable text describing this tag and its children. + // Default implemenation: print the tag name (only) + public void dump(PrintWriter pw, int indent) { + String margin = String.format("%"+indent+"s", ""); + pw.println(margin + getTagName()); + } + protected ParseResult[] collectParsedChildren(Class cls) { ArrayList collection = new ArrayList(); Iterator iter = m_childParsers.iterator(); @@ -94,7 +113,7 @@ public abstract class ParseResult } // returns true if this ParseResult's context has ended with this endElement() call - public boolean endElement(String uri, String localName, String qName) throws XmlParseException + protected boolean endElement(String uri, String localName, String qName) throws XmlParseException { assert (null != localName); @@ -122,13 +141,13 @@ public abstract class ParseResult } String chars = m_chars.toString(); - endContents(uri, localName, qName, chars, info.getAttributes()); + endContents(uri, localName, qName, chars); 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) + public ParseResult startElement(String uri, String localName, String qName, Attributes attrs) throws XmlParseException { assert (null != localName); @@ -140,6 +159,7 @@ public abstract class ParseResult if (m_tagName.equals(localName)) { m_haveSeenMyTag = true; + handleMainAttributes(attrs); return this; } else { @@ -152,7 +172,8 @@ public abstract class ParseResult } if (m_internal.contains(localName)) { - CurrentInfo info = new CurrentInfo(localName, attributes); + handleInternalAttributes(localName, attrs); + CurrentInfo info = new CurrentInfo(localName, attrs); m_current.push(info); return this; } @@ -162,7 +183,7 @@ public abstract class ParseResult try { ParseResult childParser = (ParseResult) parserClass.newInstance(); m_childParsers.add(childParser); - return childParser.startElement(uri, localName, qName, attributes); + return childParser.startElement(uri, localName, qName, attrs); } catch (IllegalAccessException iae) { throw new XmlParseException(iae); @@ -200,5 +221,6 @@ public abstract class ParseResult } return value; } + } diff --git a/test/net/jaekl/qd/xml/ParseResultTest.java b/test/net/jaekl/qd/xml/ParseResultTest.java index d58ab8c..7b0ea6f 100644 --- a/test/net/jaekl/qd/xml/ParseResultTest.java +++ b/test/net/jaekl/qd/xml/ParseResultTest.java @@ -5,6 +5,7 @@ package net.jaekl.qd.xml; import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Locale; import org.junit.Assert; @@ -70,14 +71,14 @@ public class ParseResultTest { + "41Northbound" + "Rideau C / Ctr Rideau" + "" - + "Rideau Centre / Centre Rideau19:00" + + "Rideau Centre / Centre Rideau19:00" + "160.45" + "4LB - IN45.408957-75.664125" + "66.4" - + "Rideau Centre / Centre Rideau" + + "Rideau Centre / Centre Rideau" + "19:3040-1" + "4LB - IN" - + "Rideau Centre / Centre Rideau20:00" + + "Rideau Centre / Centre Rideau20:00" + "70-1" + "4LB - IN" + "" @@ -94,7 +95,7 @@ public class ParseResultTest { @Override public void endContents(String uri, String localName, String qName, - String chars, Attributes attr) throws XmlParseException + String chars) throws XmlParseException { Assert.fail("Should not have any contents to end."); } @@ -197,7 +198,7 @@ public class ParseResultTest { @Override public void endContents(String uri, String localName, String qName, - String chars, Attributes attr) throws XmlParseException + String chars) throws XmlParseException { if (localName.equals(ONE)) { m_one = chars; @@ -281,8 +282,7 @@ public class ParseResultTest { 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 + public void endContents(String uri, String localName, String qName, String chars) throws XmlParseException { if (localName.equals(STOP_NO)) { m_stopNo = Integer.parseInt(chars); @@ -344,7 +344,7 @@ public class ParseResultTest { @Override public void endContents(String uri, String localName, String qName, - String chars, Attributes attr) throws XmlParseException + String chars) throws XmlParseException { if (localName.equals(ROUTE_NO)) { m_routeNo = Integer.parseInt(chars); @@ -376,6 +376,7 @@ public class ParseResultTest { } public static class TripParse extends ParseResult { private static final String TRIP = "Trip"; + private static final String GHOST = "ghost"; private static final String TRIP_DEST = "TripDestination"; private static final String TRIP_START = "TripStartTime"; private static final String ADJ_SCHED_TIME = "AdjustedScheduleTime"; @@ -386,22 +387,33 @@ public class ParseResultTest { // Data gleaned from parsing String m_dest; String m_startTime; - int m_adjSchedTime;; + int m_adjSchedTime; + boolean m_ghost; public TripParse() { super(TRIP, INTERNAL, EXTERNAL); m_dest = m_startTime = null; m_adjSchedTime = 0; + m_ghost = false; } public String getDestination() { return m_dest; } public String getStartTime() { return m_startTime; } public int getAdjustedScheduleTime() { return m_adjSchedTime; } + public boolean ghostAttrSet() { return m_ghost; } + + @Override + public void handleMainAttributes(Attributes attr) throws MissingAttributeException + { + String scratch = this.getRequiredAttr(TRIP, attr, GHOST); + Assert.assertNotNull(scratch); + m_ghost = scratch.toLowerCase(Locale.CANADA).equals("true"); + } @Override public void endContents(String uri, String localName, String qName, - String chars, Attributes attr) throws XmlParseException + String chars) throws XmlParseException { if (localName.equals(TRIP_DEST)) { m_dest = chars; @@ -502,18 +514,21 @@ public class ParseResultTest { Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination()); Assert.assertEquals("19:00", tp.getStartTime()); Assert.assertEquals(16, tp.getAdjustedScheduleTime()); + Assert.assertEquals(false, tp.ghostAttrSet()); 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()); + Assert.assertEquals(true, tp.ghostAttrSet()); 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()); + Assert.assertEquals(true, tp.ghostAttrSet()); } finally { if (null != bais) { -- 2.39.2