Partial implementation of XML parse for FindBugs output
[cfb.git] / prod / net / jaekl / qd / xml / ParseResult.java
1 // Copyright (C) 2004, 2015 Christian Jaekl
2
3 // Abstract class representing the result of parsing an XML Element.
4 // A class derived from this one will know how to parse a subtree inside an XML file, and 
5 // will contain the result of that parse within itself when the parse has completed.
6 //
7 // Note that this code will need to be augmented and fixed if XML namespace support is desired.
8
9 package net.jaekl.qd.xml;
10
11 import java.util.ArrayList;
12 import java.util.HashMap;
13 import java.util.HashSet;
14 import java.util.Iterator;
15 import java.util.Stack;
16
17 import org.xml.sax.Attributes;
18 import org.xml.sax.helpers.AttributesImpl;
19
20 public abstract class ParseResult
21 {
22         Stack<CurrentInfo> m_current;                                                           // Name of the element that we're currently inside
23         StringBuilder m_chars;                                                                          // character content of m_current.peek()
24         ArrayList<ParseResult> m_childParsers;                                          // Set of all child parsers
25         boolean m_haveSeenMyTag;                                                                        // Have I encountered my own (root) tag yet?
26
27         String m_tagName;                                                                                       // Name of the (root) element tag that I'm parsing
28         HashSet<String> m_internal;                                                                     // Tags that we will store as members of this class instance
29         HashMap<String,Class<? extends ParseResult>> m_external;        // Tags that we will store as child ParseResult-derived objects
30
31         // Information about the "current" tag, which we'll keep on the m_current stack.
32         static class CurrentInfo {
33                 String m_tagName;
34                 Attributes m_attr;
35                 
36                 public CurrentInfo(String tagName, Attributes attr) {
37                         m_tagName = tagName;
38                         m_attr = new AttributesImpl(attr);
39                 }
40                 
41                 public String getTagName() { return m_tagName; }
42                 public final Attributes getAttributes() { return m_attr; }
43         }
44         
45         @SuppressWarnings("unchecked")
46         public ParseResult(String tagName, String[] internalMemberTags, Object[][] externalParserTags)
47         {
48                 m_current = new Stack<CurrentInfo>();
49                 m_chars = new StringBuilder();
50                 m_childParsers = new ArrayList<ParseResult>();
51                 m_haveSeenMyTag = false;
52                 
53                 m_tagName = tagName;
54                 m_internal = new HashSet<String>();
55                 m_external = new HashMap<String, Class<? extends ParseResult>>();
56
57                 for (String internalTag : internalMemberTags) {
58                         m_internal.add(internalTag);
59                 }
60
61                 for (int idx = 0; idx < externalParserTags.length; ++idx) {
62                         String externalTag = (String)externalParserTags[idx][0];
63                         Class<? extends ParseResult>  parserClass = (Class<? extends ParseResult>)externalParserTags[idx][1];
64                         m_external.put(externalTag, parserClass);
65                 }
66         }
67
68         public abstract void endContents(String uri, String localName, String qName, String chars, Attributes attr) throws XmlParseException;
69         public abstract void endExternal(String uri, String localName, String qName) throws XmlParseException;
70         
71         public String getTagName() { return m_tagName; }
72         public boolean haveSeenMyTag() { return m_haveSeenMyTag; }
73
74         public void characters(char[] ch, int start, int length) throws XmlParseException
75         {
76                 m_chars.append(ch, start, length);
77         }
78         
79         protected ParseResult[] collectParsedChildren(Class<? extends ParseResult> cls) {
80                 ArrayList<ParseResult> collection = new ArrayList<ParseResult>();
81                 Iterator<ParseResult> iter = m_childParsers.iterator();
82                 while (iter.hasNext()) {
83                         ParseResult pr = iter.next();
84                         if (pr.getClass().isAssignableFrom(cls)) {
85                                 collection.add(pr);
86                                 iter.remove();
87                         }
88                 }
89                 
90                 ParseResult[] result = new ParseResult[collection.size()];
91                 return collection.toArray(result);
92         }
93
94         // returns true if this ParseResult's context has ended with this endElement() call
95         public boolean endElement(String uri, String localName, String qName) throws XmlParseException
96         {
97                 assert (null != localName);
98                 
99                 boolean isInternal = m_internal.contains(localName);
100
101                 if (! m_haveSeenMyTag) {
102                         // We're in some unrecognised prologue.  Ignore it and move on.
103                         return false;
104                 }
105                 
106                 if (m_tagName.equals(localName)) {
107                         validate();
108                         return true;
109                 }
110                 
111                 if (!isInternal) {
112                         // Unrecognized tag.  Ignore it.
113                         return false;
114                 }
115                 
116                 CurrentInfo info = m_current.pop();
117                 String tag = info.getTagName();
118                 if ( ! tag.equals(localName) ) {
119                         throw new MismatchedTagsException(tag, localName);
120                 }
121                 
122                 String chars = m_chars.toString();
123                 endContents(uri, localName, qName, chars, info.getAttributes());
124                 
125                 return false;
126         }
127         
128         // returns either itself, or a new ParseResult-derived object, whichever should handle parsing the inside of this element
129         public ParseResult startElement(String uri, String localName, String qName, Attributes attributes) 
130                         throws XmlParseException
131         {
132                 assert (null != localName);
133
134                 m_chars.setLength(0);
135                 
136                 if (! m_haveSeenMyTag) {
137                         // Have we opened our own (root) tag yet?
138                         
139                         if (m_tagName.equals(localName)) {
140                                 m_haveSeenMyTag = true;
141                                 return this;
142                         }
143                         else {
144                                 // One of two things has happened here.
145                                 // Either (a) we've got some sort of wrapper here, and have not yet reach our own tag, 
146                                 //     or (b) we're parsing XML that doesn't match expectations.
147                                 // In either case, we're going to ignore this tag, and scan forward looking for our own root.
148                                 return this;
149                         }
150                 }
151
152                 if (m_internal.contains(localName)) {
153                         CurrentInfo info = new CurrentInfo(localName, attributes);
154                         m_current.push(info);
155                         return this;
156                 }
157
158                 Class<? extends ParseResult> parserClass = m_external.get(localName);
159                 if (null != parserClass) {
160                         try {
161                                 ParseResult childParser = (ParseResult) parserClass.newInstance();
162                                 m_childParsers.add(childParser);
163                                 return childParser.startElement(uri, localName, qName, attributes);
164                         }
165                         catch (IllegalAccessException iae) {
166                                 throw new XmlParseException(iae);
167                         }
168                         catch (InstantiationException ie) {
169                                 throw new XmlParseException(ie);
170                         }
171                 }
172                 
173                 // Not a recognized tag.  Ignore it, rather than complaining. 
174                 return this;
175         }
176         
177         public void validate() throws XmlParseException
178         {
179                 // Default implementation is a no-op.
180                 // Override if you want to validate on endElement()
181         }
182 }
183