Improve XML parsing to handle attributes as well.
authorChris Jaekl <chris@ringo.jaekl.net>
Sun, 6 Sep 2015 06:17:10 +0000 (15:17 +0900)
committerChris Jaekl <chris@ringo.jaekl.net>
Sun, 6 Sep 2015 06:17:10 +0000 (15:17 +0900)
Add debug-log dump() function to validate that XML has been parsed correctly.
Update (C) statements.

21 files changed:
cov.sh [new file with mode: 0755]
prod/net/jaekl/cfb/CFB.java
prod/net/jaekl/cfb/CfbBundle.java
prod/net/jaekl/cfb/analyze/Analysis.java
prod/net/jaekl/cfb/analyze/Analyzer.java
prod/net/jaekl/cfb/db/CfbSchema.java
prod/net/jaekl/cfb/db/Column.java
prod/net/jaekl/cfb/db/Schema.java
prod/net/jaekl/cfb/db/Table.java
prod/net/jaekl/cfb/db/driver/DbDriver.java
prod/net/jaekl/cfb/db/driver/PostgresqlDriver.java
prod/net/jaekl/cfb/util/Command.java
prod/net/jaekl/cfb/util/Util.java
prod/net/jaekl/cfb/xml/BugClass.java
prod/net/jaekl/cfb/xml/BugCollection.java
prod/net/jaekl/cfb/xml/BugInstance.java
prod/net/jaekl/cfb/xml/BugMethod.java
prod/net/jaekl/cfb/xml/LocalVariable.java
prod/net/jaekl/cfb/xml/SourceLine.java
prod/net/jaekl/qd/xml/ParseResult.java
test/net/jaekl/qd/xml/ParseResultTest.java

diff --git a/cov.sh b/cov.sh
new file mode 100755 (executable)
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
index 7dca443fa6b62aa65428b997bb9f4ad4b65a0871..d587c1878e882b0a0dfab3152669240149da5dd1 100644 (file)
@@ -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();
                }
        }
index a643c6edc5a6b4f00bf728487579119d2658f303..c83182c346338b0776e78ba0e50c2c71f15000a5 100644 (file)
@@ -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;
index e8a938aa33c33ea710f7149da80c1078fd897d47..4e289e2366c4816a679dd0b39019b118b90fb2b7 100644 (file)
@@ -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);
+               }
+       }
 }
index 715cae91544173511b0d256fae0ed9fd0a634b27..9a8d7a64c4ea6f7a4069dfb3b396e1ddd65f382f 100644 (file)
@@ -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;
        }
 }
index 52e067ba0b829f2a0026a0987e6175dec91f6f94..d2333fab8f932d3fc6bdb11605dd72915497c8b6 100644 (file)
@@ -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;
index bd06570dd2bfdfbe8560ca47f5ec60d0efd857a5..527ad7ddc5e2eea64c7653635393de02efa40b5a 100644 (file)
@@ -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 
index 176526d9b649aaadcedd1c4f24a979b9e9eb7e00..b6dc6012fc6d720ded5ecae5383c7943e4558afe 100644 (file)
@@ -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;
index 64b2caadddad54518131825d04bbe14b79f4ad2a..a68784223e6c716d919b0d58add30dd8520e015d 100644 (file)
@@ -1,5 +1,7 @@
 package net.jaekl.cfb.db;
 
+// Copyright (C) 2015 Christian Jaekl
+
 import java.util.ArrayList;
 import java.util.Arrays;
 
index 9de7264d9d2ca30ebec495ca59ca8087d655d009..c8649579685d12a9d38f80950e35fb55fb0c595f 100644 (file)
@@ -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;
index 55994b33b0d6f690f31d57bfa668aa9c55afdaee..0fe1f0809b129f812ea3e46819be0e834e1c2715 100644 (file)
@@ -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;
index 4242eea9602dedb5dcb293917d573670330d961f..672aac2fec9364a106922d40b14286536a4c3eff 100644 (file)
@@ -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;
index c0e7b1ca4b592ee9c2decc8e168fe3994ba3ec00..30f4c90c64bc40f63fc9afc7b5d312edd54b8c35 100644 (file)
@@ -1,5 +1,7 @@
 package net.jaekl.cfb.util;
 
+// Copyright (C) 2015 Christian Jaekl
+
 import java.io.PrintWriter;
 import java.io.StringWriter;
 
index eb2c5929347c5c108331286f6362e76d3097a3c1..40cc84b8a3874dd550be2e201ffec705f928d040 100644 (file)
@@ -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<SourceLine> m_sourceLines;
+       ArrayList<SourceLine> m_lines;
        
        public BugClass() {
                super(TAG, INTERNAL, EXTERNAL);
                m_className = "";
+               m_lines = new ArrayList<SourceLine>();
+       }
+
+       @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);
+                       }
+               }
+       }
 }
index b59bdc8263b630bb38b4056d5a007dbba67d2f72..8fb78e411ab973b770e30a618d26e82f52ad97ad 100644 (file)
@@ -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<BugInstance> m_bugs;
+       
        public BugCollection() {
                super(TAG, INTERNAL, EXTERNAL);
+               m_bugs = new ArrayList<BugInstance>();
        }
        
        @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);
+               }
        }
 
 }
index f1f8537177befd7c51ad867a89a03ee993e938b4..900cd1cfd37c1f9fa6fa5643b624dbf635d5fb38 100644 (file)
@@ -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<BugClass> m_classes;
        ArrayList<BugMethod> m_methods;
        ArrayList<LocalVariable> m_locals;
@@ -24,6 +28,7 @@ public class BugInstance extends ParseResult {
        public BugInstance() {
                super(TAG, INTERNAL, EXTERNAL);
                
+               m_type = null;
                m_classes = new ArrayList<BugClass>();
                m_methods = new ArrayList<BugMethod>();
                m_locals = new ArrayList<LocalVariable>();
@@ -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);
+               }
+       }
 }
index 2e001b23abe93116f71c47782bc1f21b428308e6..80afa93392a69b632cd3c9023982a5fc18d9c850 100644 (file)
@@ -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<SourceLine>();
        }
-
+       
        @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);
+                       }
+               }
+       }
 
 }
index b52823f02408029de4a1bb33cba6a47db77caa5e..fb198688068f58353ed80c3dd93bdbb65d4db1a1 100644 (file)
@@ -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);
+       }
 }
index 627667a161ac4e3902c6170412e5f32badf2c6b8..c719529e2d9231c1a396253174571c932767ca0b 100644 (file)
@@ -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 + ")");
+       }
 }
index e8210159951327dfbb7646f28c8d05bfe55e7f80..f653b368c81f17f4dee94e8211410e334050f058 100644 (file)
@@ -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<? extends ParseResult> cls) {
                ArrayList<ParseResult> collection = new ArrayList<ParseResult>();
                Iterator<ParseResult> 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;
        }
+
 }
 
index d58ab8c9a522484c4ada261145725e8a3ab486a3..7b0ea6fc57d0d6e29e7beb4437934e5d321e1a5f 100644 (file)
@@ -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 {
                        + "<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>"
+                       + "<Trip ghost=\"false\"><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>"
+                       + "<Trip ghost=\"true\"><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>"
+                       + "<Trip ghost=\"true\"><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>"
@@ -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) {