Partial implementation of XML parse for FindBugs output
[cfb.git] / test / net / jaekl / qd / xml / ParseResultTest.java
1 // Copyright (C) 2004, 2015 Christian Jaekl
2
3 package net.jaekl.qd.xml;
4
5 import java.io.ByteArrayInputStream;
6 import java.io.IOException;
7 import java.util.ArrayList;
8
9 import org.junit.Assert;
10
11 import org.junit.Test;
12 import org.xml.sax.Attributes;
13 import org.xml.sax.InputSource;
14 import org.xml.sax.SAXException;
15 import org.xml.sax.XMLReader;
16 import org.xml.sax.helpers.XMLReaderFactory;
17
18 public class ParseResultTest {
19         // Some samples of XML that we're going to (try to) parse\
20         private static final String MINIMAL_XML = 
21                         "<Root/>";
22         private static final String MINIMAL_XML_WITH_PROLOGUE = 
23                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root/>";
24         private static final String XML_WITH_MINOR_CONTENT = 
25                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root><One/></Root>";
26         private static final String ROOT_INSIDE_SECONDARY_ELEMENT = 
27                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Secondary><Root><One/></Root></Secondary>";
28         private static final String PROLOGUE_AND_SECONDARY_ELEMENT = 
29                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Secondary/>";
30         private static final String SIMPLE_INTERNAL_TAGS = 
31                         "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Root><One/><Two>content of two</Two><Three>3</Three></Root>";
32         private static final String ROUTE_SUMMARY_FOR_STOP = 
33                         "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
34                                         + "<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" 
35                                         +  "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n" 
36                                         +  "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
37                                         + "     <soap:Body>\n"
38                                         + "             <GetRouteSummaryForStopResponse xmlns=\"http://octranspo.com\">\n"
39                                         + "                     <GetRouteSummaryForStopResult>\n"
40                                         + "                             <StopNo xmlns=\"http://tempuri.org/\">1234</StopNo>\n"
41                                         + "                             <StopDescription" 
42                                         + "                              xmlns=\"http://tempuri.org/\">ONE-TWO-THREE-FOUR</StopDescription>\n"
43                                         + "                             <Error xmlns=\"http://tempuri.org/\"/>\n"
44                                         + "                             <Routes xmlns=\"http://tempuri.org/\">\n"
45                                         + "                                     <Route>\n"
46                                         + "                                             <RouteNo>123</RouteNo>\n"
47                                         + "                                             <DirectionID>0</DirectionID>\n"
48                                         + "                                             <Direction>NORTH</Direction>\n"
49                                         + "                                             <RouteHeading>First Mall</RouteHeading>\n"
50                                         + "                                     </Route>\n"
51                                         + "                                     <Route>\n"
52                                         + "                                             <RouteNo>123</RouteNo>\n"
53                                         + "                                             <DirectionID>1</DirectionID>\n"
54                                         + "                                             <Direction>SOUTH</Direction>\n"
55                                         + "                                             <RouteHeading>Second Mall</RouteHeading>\n"
56                                         + "                                     </Route>\n"
57                                         + "                             </Routes>\n"
58                                         + "                     </GetRouteSummaryForStopResult>\n"
59                                         + "             </GetRouteSummaryForStopResponse>\n"
60                                         + "     </soap:Body>\n"
61                                         + "</soap:Envelope>\n";
62         private static final String NEXT_TRIPS_FOR_STOP = 
63                         "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
64                         + "<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\">"
65                         + "<soap:Body><GetRouteSummaryForStopResponse xmlns=\"http://octranspo.com\">"
66                         + "<GetRouteSummaryForStopResult>"
67                         + "<StopNo xmlns=\"http://tempuri.org/\">2438</StopNo>"
68                         + "<StopDescription xmlns=\"http://tempuri.org/\">BRONSON SUNNYSIDE</StopDescription>"
69                         + "<Error xmlns=\"http://tempuri.org/\"/><Routes xmlns=\"http://tempuri.org/\">"
70                         + "<Route><RouteNo>4</RouteNo><DirectionID>1</DirectionID><Direction>Northbound</Direction>"
71                         + "<RouteHeading>Rideau C / Ctr Rideau</RouteHeading>"
72                         + "<Trips>"
73                         + "<Trip><TripDestination>Rideau Centre / Centre Rideau</TripDestination><TripStartTime>19:00</TripStartTime>"
74                         + "<AdjustedScheduleTime>16</AdjustedScheduleTime><AdjustmentAge>0.45</AdjustmentAge><LastTripOfSchedule/>"
75                         + "<BusType>4LB - IN</BusType><Latitude>45.408957</Latitude><Longitude>-75.664125</Longitude>"
76                         + "<GPSSpeed>66.4</GPSSpeed></Trip>"
77                         + "<Trip><TripDestination>Rideau Centre / Centre Rideau</TripDestination>"
78                         + "<TripStartTime>19:30</TripStartTime><AdjustedScheduleTime>40</AdjustedScheduleTime><AdjustmentAge>-1</AdjustmentAge>"
79                         + "<LastTripOfSchedule/><BusType>4LB - IN</BusType><Latitude/><Longitude/><GPSSpeed/></Trip>"
80                         + "<Trip><TripDestination>Rideau Centre / Centre Rideau</TripDestination><TripStartTime>20:00</TripStartTime>"
81                         + "<AdjustedScheduleTime>70</AdjustedScheduleTime><AdjustmentAge>-1</AdjustmentAge><LastTripOfSchedule/>"
82                         + "<BusType>4LB - IN</BusType><Latitude/><Longitude/><GPSSpeed/></Trip>"
83                         + "</Trips></Route></Routes></GetRouteSummaryForStopResult></GetRouteSummaryForStopResponse>"
84                         + "</soap:Body></soap:Envelope>";
85         
86         // Do the least possible parsing:  check for the <Root/> element only.
87         public static class MinimalParse extends ParseResult {
88                 private static final String[] INTERNAL = {};
89                 private static final Object[][] EXTERNAL = {} ;
90                 
91                 public MinimalParse() {
92                         super("Root", INTERNAL, EXTERNAL);
93                 }
94
95                 @Override
96                 public void endContents(String uri, String localName, String qName,
97                                 String chars, Attributes attr) throws XmlParseException 
98                 {
99                         Assert.fail("Should not have any contents to end.");
100                 }
101
102                 @Override
103                 public void endExternal(String uri, String localName, String qName)
104                                 throws XmlParseException 
105                 {
106                         Assert.fail("Should not have any external tags to end.");
107                 }
108         }
109         
110         // Check that we can parse a minimal document without errors.
111         // Because there's no content being parsed (beyond the root element), there is 
112         // no "correct" behaviour to assert.  The test is to confirm that we 
113         // don't do anything incorrect--no calls to endContent() nor endExternal(),
114         // and no exceptions thrown along the way.
115         @Test
116         public void test_withMinimalParse() throws IOException, SAXException {
117                 MinimalParse mp = new MinimalParse();
118                 ByteArrayInputStream bais = null;
119                 
120                 String[] data = { 
121                                         MINIMAL_XML, 
122                                         MINIMAL_XML_WITH_PROLOGUE, 
123                                         XML_WITH_MINOR_CONTENT,
124                                         ROOT_INSIDE_SECONDARY_ELEMENT 
125                                 };
126                 
127                 for (String datum : data) {
128                         try {
129                                 bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
130                                 XMLReader reader = XMLReaderFactory.createXMLReader();
131                                 ParseHandler ph = new ParseHandler(mp);
132                                 reader.setContentHandler(ph);
133                                 reader.parse(new InputSource(bais));
134                         }
135                         finally {
136                                 if (null != bais) { 
137                                         bais.close();
138                                 }
139                         }
140                 }
141         }
142         
143         // If we parse something that doesn't have the expected root element, we should generate an exception
144         @Test
145         public void test_minimalParseWithMismatchedRootElement() throws IOException {
146                 MinimalParse mp = new MinimalParse();
147                 ByteArrayInputStream bais = null;
148                 
149                 String[] data = { PROLOGUE_AND_SECONDARY_ELEMENT };
150                 
151                 for (String datum : data) {
152                         try {
153                                 bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
154                                 XMLReader reader = XMLReaderFactory.createXMLReader();
155                                 ParseHandler ph = new ParseHandler(mp);
156                                 reader.setContentHandler(ph);
157                                 reader.parse(new InputSource(bais));
158                                 Assert.fail("Should have thrown an exception.");
159                         }
160                         catch ( SAXException se ) {
161                                 Throwable cause = se.getCause();
162                                 Assert.assertNotNull(cause);
163                                 Assert.assertTrue(cause instanceof MissingInfoException);
164                                 MissingInfoException mie = (MissingInfoException) cause;
165                                 Assert.assertEquals("Root", mie.getTagName());
166                         }
167                         finally {
168                                 if (null != bais) { 
169                                         bais.close();
170                                 }
171                         }
172                 }
173         }
174         
175         // Do the some simple parsing:  <Root/> and some subtags that are processed internally
176         public static class SimpleParse extends ParseResult {
177                 private static final String ONE = "One";
178                 private static final String TWO = "Two";
179                 private static final String THREE = "Three";
180                 
181                 private static final String[] INTERNAL = {ONE, TWO, THREE};
182                 private static final Object[][] EXTERNAL = {} ;
183                 
184                 String m_one;
185                 String m_two;
186                 String m_three;
187                 
188                 public SimpleParse() {
189                         super("Root", INTERNAL, EXTERNAL);
190                         
191                         m_one = m_two = m_three = null;
192                 }
193                 
194                 public String getOne() { return m_one; }
195                 public String getTwo() { return m_two; }
196                 public String getThree() { return m_three; }
197
198                 @Override
199                 public void endContents(String uri, String localName, String qName,
200                                 String chars, Attributes attr) throws XmlParseException 
201                 {
202                         if (localName.equals(ONE)) {
203                                 m_one = chars;
204                         }
205                         else if (localName.equals(TWO)) {
206                                 m_two = chars;
207                         }
208                         else if (localName.equals(THREE)) {
209                                 m_three = chars;
210                         }
211                 }
212
213                 @Override
214                 public void endExternal(String uri, String localName, String qName)
215                                 throws XmlParseException 
216                 {
217                         Assert.fail("Should not have any external tags to end.");
218                 }
219         }
220         
221         // Parse some XML containing subtags that are handled internally by SimpleParse
222         @Test
223         public void test_parseWithInternalSubtags() throws IOException, SAXException 
224         {
225                 SimpleParse sp = new SimpleParse();
226                 ByteArrayInputStream bais = null;
227                 
228                 String[] data = {
229                                         SIMPLE_INTERNAL_TAGS
230                                 };
231                 
232                 for (String datum : data) {
233                         try {
234                                 bais = new ByteArrayInputStream(datum.getBytes("UTF-8"));
235                                 XMLReader reader = XMLReaderFactory.createXMLReader();
236                                 ParseHandler ph = new ParseHandler(sp);
237                                 reader.setContentHandler(ph);
238                                 reader.parse(new InputSource(bais));
239                                 
240                                 Assert.assertEquals("", sp.getOne());
241                                 Assert.assertEquals("content of two", sp.getTwo());
242                                 Assert.assertEquals("3", sp.getThree());
243                         }
244                         finally {
245                                 if (null != bais) { 
246                                         bais.close();
247                                 }
248                         }
249                 }
250         }
251         
252         // Parse sub-tags, handling some internally and some externally
253         public static class RouteSummaryParse extends ParseResult {
254                 private static final String STOP_NO = "StopNo";
255                 private static final String STOP_DESCR = "StopDescription";
256                 private static final String ERROR = "Error";
257                 private static final String ROUTES = "Routes";
258                 private static final String ROUTE = "Route";
259                 
260                 private static final String[] INTERNAL = {STOP_NO, STOP_DESCR, ERROR, ROUTES};
261                 private static final Object[][] EXTERNAL = { {ROUTE, RouteParse.class} };
262                 
263                 // Data gleaned from parsing
264                 int m_stopNo;
265                 String m_stopDescr;
266                 String m_error;
267                 ArrayList<RouteParse> m_routes;
268                 
269                 public RouteSummaryParse() {
270                         super("GetRouteSummaryForStopResult", INTERNAL, EXTERNAL);
271                         
272                         m_stopNo = 0;
273                         m_stopDescr = m_error = null;
274                         m_routes = new ArrayList<RouteParse>();
275                 }
276                 
277                 public int getStopNo() { return m_stopNo; }
278                 public String getStopDescription() { return m_stopDescr; }
279                 public String getError() { return m_error; }
280                 public int getNumRoutes() { return m_routes.size(); }
281                 public RouteParse getRoute(int idx) { return m_routes.get(idx); }
282
283                 @Override
284                 public void endContents(String uri, String localName, String qName,
285                                 String chars, Attributes attr) throws XmlParseException 
286                 {
287                         if (localName.equals(STOP_NO)) {
288                                 m_stopNo = Integer.parseInt(chars);
289                         }
290                         else if (localName.equals(STOP_DESCR)) {
291                                 m_stopDescr = chars;
292                         }
293                         else if (localName.equals(ERROR)) {
294                                 m_error = chars;
295                         }
296                 }
297
298                 @Override
299                 public void endExternal(String uri, String localName, String qName)
300                                 throws XmlParseException 
301                 {
302                         if (localName.equals(ROUTE)) {
303                                 ParseResult[] collected = collectParsedChildren(RouteParse.class);
304                                 for (ParseResult pr : collected) {
305                                         Assert.assertTrue(pr instanceof RouteParse);
306                                         m_routes.add((RouteParse)pr);
307                                 }
308                         }
309                 }
310         }
311         public static class RouteParse extends ParseResult {
312                 private static final String ROUTE = "Route";
313                 private static final String ROUTE_NO = "RouteNo";
314                 private static final String DIR_ID = "DirectionID";
315                 private static final String DIR = "Direction";
316                 private static final String HEADING = "RouteHeading";
317                 private static final String TRIPS = "Trips";
318                 private static final String TRIP = "Trip";
319                 
320                 private static final String[] INTERNAL = {ROUTE_NO, DIR_ID, DIR, HEADING, TRIPS};
321                 private static final Object[][] EXTERNAL = { {TRIP, TripParse.class} };
322                 
323                 // Data gleaned from parsing
324                 int m_routeNo;
325                 int m_dirID;
326                 String m_dir;
327                 String m_heading;
328                 ArrayList<TripParse> m_trips;
329                 
330                 public RouteParse() {
331                         super(ROUTE, INTERNAL, EXTERNAL);
332                         
333                         m_routeNo = m_dirID = 0;
334                         m_dir = m_heading = null;
335                         m_trips = new ArrayList<TripParse>();
336                 }
337                 
338                 public int getRouteNo() { return m_routeNo; }
339                 public int getDirectionID() { return m_dirID; }
340                 public String getDirection() { return m_dir; }
341                 public String getHeading() { return m_heading; }
342                 public int getNumTrips() { return m_trips.size(); }
343                 public TripParse getTrip(int idx) { return m_trips.get(idx); }
344
345                 @Override
346                 public void endContents(String uri, String localName, String qName,
347                                 String chars, Attributes attr) throws XmlParseException 
348                 {
349                         if (localName.equals(ROUTE_NO)) {
350                                 m_routeNo = Integer.parseInt(chars);
351                         }
352                         else if (localName.equals(DIR_ID)) {
353                                 m_dirID = Integer.parseInt(chars);
354                         }
355                         else if (localName.equals(DIR)) {
356                                 m_dir = chars;
357                         }
358                         else if (localName.equals(HEADING)) {
359                                 m_heading = chars;
360                         }
361                 }
362
363                 @Override
364                 public void endExternal(String uri, String localName, String qName)
365                                 throws XmlParseException 
366                 {
367                         if (localName.equals(TRIP)) {
368                                 ParseResult[] collected = collectParsedChildren(TripParse.class);
369                                 for (ParseResult pr : collected) {
370                                         Assert.assertTrue(pr instanceof TripParse);
371                                         m_trips.add((TripParse)pr);
372                                 }
373                         }
374                         
375                 }
376         }
377         public static class TripParse extends ParseResult {
378                 private static final String TRIP = "Trip";
379                 private static final String TRIP_DEST = "TripDestination";
380                 private static final String TRIP_START = "TripStartTime";
381                 private static final String ADJ_SCHED_TIME = "AdjustedScheduleTime";
382                 
383                 private static final String[] INTERNAL = {TRIP_DEST, TRIP_START, ADJ_SCHED_TIME };
384                 private static final Object[][] EXTERNAL = { };
385                 
386                 // Data gleaned from parsing
387                 String m_dest;
388                 String m_startTime;
389                 int m_adjSchedTime;;
390                 
391                 public TripParse() {
392                         super(TRIP, INTERNAL, EXTERNAL);
393                         
394                         m_dest = m_startTime = null;
395                         m_adjSchedTime = 0;
396                 }
397                 
398                 public String getDestination() { return m_dest; }
399                 public String getStartTime() { return m_startTime; }
400                 public int getAdjustedScheduleTime() { return m_adjSchedTime; }
401
402                 @Override
403                 public void endContents(String uri, String localName, String qName,
404                                 String chars, Attributes attr) throws XmlParseException 
405                 {
406                         if (localName.equals(TRIP_DEST)) {
407                                 m_dest = chars;
408                         }
409                         else if (localName.equals(TRIP_START)) {
410                                 m_startTime = chars;
411                         }
412                         else if (localName.equals(ADJ_SCHED_TIME)) {
413                                 m_adjSchedTime = Integer.parseInt(chars);
414                         }
415                 }
416
417                 @Override
418                 public void endExternal(String uri, String localName, String qName)
419                                 throws XmlParseException 
420                 {
421                         Assert.fail("Should not be attempting to parse external tags.");
422                 }
423         }
424         
425         // Parse some XML containing subtags that are handled both internally and externally
426         @Test
427         public void test_parseRouteSummary() throws IOException, SAXException 
428         {
429                 RouteSummaryParse rsp = new RouteSummaryParse();
430                 ByteArrayInputStream bais = null;
431                 
432                 try {
433                         RouteParse rp;
434                         
435                         bais = new ByteArrayInputStream(ROUTE_SUMMARY_FOR_STOP.getBytes("UTF-8"));
436                         XMLReader reader = XMLReaderFactory.createXMLReader();
437                         ParseHandler ph = new ParseHandler(rsp);
438                         reader.setContentHandler(ph);
439                         reader.parse(new InputSource(bais));
440                         
441                         Assert.assertEquals(1234, rsp.getStopNo());
442                         Assert.assertEquals("ONE-TWO-THREE-FOUR", rsp.getStopDescription());
443                         Assert.assertEquals("", rsp.getError());
444                         
445                         Assert.assertEquals(2, rsp.getNumRoutes());
446                         
447                         rp = rsp.getRoute(0);
448                         Assert.assertNotNull(rp);
449                         Assert.assertEquals(123, rp.getRouteNo());
450                         Assert.assertEquals(0, rp.getDirectionID());
451                         Assert.assertEquals("NORTH", rp.getDirection());
452                         Assert.assertEquals("First Mall", rp.getHeading());
453                         
454                         rp = rsp.getRoute(1);
455                         Assert.assertNotNull(rp);
456                         Assert.assertEquals(123, rp.getRouteNo());
457                         Assert.assertEquals(1, rp.getDirectionID());
458                         Assert.assertEquals("SOUTH", rp.getDirection());
459                         Assert.assertEquals("Second Mall", rp.getHeading());
460                 }
461                 finally {
462                         if (null != bais) { 
463                                 bais.close();
464                         }
465                 }
466         }
467         
468         // Parse a 3-level external-tag hierarchy:  RouteSummary contains Routes contains Trips
469         @Test
470         public void test_parseThreeLevels() throws IOException, SAXException 
471         {
472                 RouteSummaryParse rsp = new RouteSummaryParse();
473                 ByteArrayInputStream bais = null;
474                 
475                 try {
476                         RouteParse rp;
477                         TripParse tp;
478                         
479                         bais = new ByteArrayInputStream(NEXT_TRIPS_FOR_STOP.getBytes("UTF-8"));
480                         XMLReader reader = XMLReaderFactory.createXMLReader();
481                         ParseHandler ph = new ParseHandler(rsp);
482                         reader.setContentHandler(ph);
483                         reader.parse(new InputSource(bais));
484                         
485                         Assert.assertEquals(2438, rsp.getStopNo());
486                         Assert.assertEquals("BRONSON SUNNYSIDE", rsp.getStopDescription());
487                         Assert.assertEquals("", rsp.getError());
488                         
489                         Assert.assertEquals(1, rsp.getNumRoutes());
490                         
491                         rp = rsp.getRoute(0);
492                         Assert.assertNotNull(rp);
493                         Assert.assertEquals(4, rp.getRouteNo());
494                         Assert.assertEquals(1, rp.getDirectionID());
495                         Assert.assertEquals("Northbound", rp.getDirection());
496                         Assert.assertEquals("Rideau C / Ctr Rideau", rp.getHeading());
497                         
498                         Assert.assertEquals(3, rp.getNumTrips());
499                         
500                         tp = rp.getTrip(0);
501                         Assert.assertNotNull(tp);
502                         Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
503                         Assert.assertEquals("19:00", tp.getStartTime());
504                         Assert.assertEquals(16, tp.getAdjustedScheduleTime());
505                         
506                         tp = rp.getTrip(1);
507                         Assert.assertNotNull(tp);
508                         Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
509                         Assert.assertEquals("19:30", tp.getStartTime());
510                         Assert.assertEquals(40, tp.getAdjustedScheduleTime());
511
512                         tp = rp.getTrip(2);
513                         Assert.assertNotNull(tp);
514                         Assert.assertEquals("Rideau Centre / Centre Rideau", tp.getDestination());
515                         Assert.assertEquals("20:00", tp.getStartTime());
516                         Assert.assertEquals(70, tp.getAdjustedScheduleTime());
517                 }
518                 finally {
519                         if (null != bais) { 
520                                 bais.close();
521                         }
522                 }
523         }
524 }