• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.unit_tests;
18 
19 import android.graphics.Bitmap;
20 import android.sax.Element;
21 import android.sax.ElementListener;
22 import android.sax.EndTextElementListener;
23 import android.sax.RootElement;
24 import android.sax.StartElementListener;
25 import android.sax.TextElementListener;
26 import android.test.AndroidTestCase;
27 import android.test.suitebuilder.annotation.LargeTest;
28 import android.test.suitebuilder.annotation.SmallTest;
29 import android.text.format.Time;
30 import android.util.Log;
31 import android.util.Xml;
32 import com.android.internal.util.XmlUtils;
33 import org.xml.sax.Attributes;
34 import org.xml.sax.ContentHandler;
35 import org.xml.sax.SAXException;
36 import org.xml.sax.helpers.DefaultHandler;
37 
38 import java.io.ByteArrayInputStream;
39 import java.io.ByteArrayOutputStream;
40 import java.io.IOException;
41 import java.io.InputStream;
42 
43 public class SafeSaxTest extends AndroidTestCase {
44 
45     private static final String TAG = SafeSaxTest.class.getName();
46 
47     private static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
48     private static final String MEDIA_NAMESPACE = "http://search.yahoo.com/mrss/";
49     private static final String YOUTUBE_NAMESPACE = "http://gdata.youtube.com/schemas/2007";
50     private static final String GDATA_NAMESPACE = "http://schemas.google.com/g/2005";
51 
52     private static class ElementCounter implements ElementListener {
53         int starts = 0;
54         int ends = 0;
55 
start(Attributes attributes)56         public void start(Attributes attributes) {
57             starts++;
58         }
59 
end()60         public void end() {
61             ends++;
62         }
63     }
64 
65     private static class TextElementCounter implements TextElementListener {
66         int starts = 0;
67         String bodies = "";
68 
start(Attributes attributes)69         public void start(Attributes attributes) {
70             starts++;
71         }
72 
end(String body)73         public void end(String body) {
74             this.bodies += body;
75         }
76     }
77 
78     @SmallTest
testListener()79     public void testListener() throws Exception {
80         String xml = "<feed xmlns='http://www.w3.org/2005/Atom'>\n"
81                 + "<entry>\n"
82                 + "<id>a</id>\n"
83                 + "</entry>\n"
84                 + "<entry>\n"
85                 + "<id>b</id>\n"
86                 + "</entry>\n"
87                 + "</feed>\n";
88 
89         RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
90         Element entry = root.requireChild(ATOM_NAMESPACE, "entry");
91         Element id = entry.requireChild(ATOM_NAMESPACE, "id");
92 
93         ElementCounter rootCounter = new ElementCounter();
94         ElementCounter entryCounter = new ElementCounter();
95         TextElementCounter idCounter = new TextElementCounter();
96 
97         root.setElementListener(rootCounter);
98         entry.setElementListener(entryCounter);
99         id.setTextElementListener(idCounter);
100 
101         Xml.parse(xml, root.getContentHandler());
102 
103         assertEquals(1, rootCounter.starts);
104         assertEquals(1, rootCounter.ends);
105         assertEquals(2, entryCounter.starts);
106         assertEquals(2, entryCounter.ends);
107         assertEquals(2, idCounter.starts);
108         assertEquals("ab", idCounter.bodies);
109     }
110 
111     @SmallTest
testMissingRequiredChild()112     public void testMissingRequiredChild() throws Exception {
113         String xml = "<feed></feed>";
114         RootElement root = new RootElement("feed");
115         root.requireChild("entry");
116 
117         try {
118             Xml.parse(xml, root.getContentHandler());
119             fail("expected exception not thrown");
120         } catch (SAXException e) {
121             // Expected.
122         }
123     }
124 
125     @SmallTest
testMixedContent()126     public void testMixedContent() throws Exception {
127         String xml = "<feed><entry></entry></feed>";
128 
129         RootElement root = new RootElement("feed");
130         root.setEndTextElementListener(new EndTextElementListener() {
131             public void end(String body) {
132             }
133         });
134 
135         try {
136             Xml.parse(xml, root.getContentHandler());
137             fail("expected exception not thrown");
138         } catch (SAXException e) {
139             // Expected.
140         }
141     }
142 
143     @LargeTest
testPerformance()144     public void testPerformance() throws Exception {
145         InputStream in = mContext.getResources().openRawResource(R.raw.youtube);
146         byte[] xmlBytes;
147         try {
148             ByteArrayOutputStream out = new ByteArrayOutputStream();
149             byte[] buffer = new byte[1024];
150             int length;
151             while ((length = in.read(buffer)) != -1) {
152                 out.write(buffer, 0, length);
153             }
154             xmlBytes = out.toByteArray();
155         } finally {
156             in.close();
157         }
158 
159         Log.i("***", "File size: " + (xmlBytes.length / 1024) + "k");
160 
161         VideoAdapter videoAdapter = new VideoAdapter();
162         ContentHandler handler = newContentHandler(videoAdapter);
163         for (int i = 0; i < 2; i++) {
164             pureSaxTest(new ByteArrayInputStream(xmlBytes));
165             saxyModelTest(new ByteArrayInputStream(xmlBytes));
166             saxyModelTest(new ByteArrayInputStream(xmlBytes), handler);
167         }
168     }
169 
pureSaxTest(InputStream inputStream)170     private static void pureSaxTest(InputStream inputStream) throws IOException, SAXException {
171         long start = System.currentTimeMillis();
172         VideoAdapter videoAdapter = new VideoAdapter();
173         Xml.parse(inputStream, Xml.Encoding.UTF_8, new YouTubeContentHandler(videoAdapter));
174         long elapsed = System.currentTimeMillis() - start;
175         Log.i(TAG, "pure SAX: " + elapsed + "ms");
176     }
177 
saxyModelTest(InputStream inputStream)178     private static void saxyModelTest(InputStream inputStream) throws IOException, SAXException {
179         long start = System.currentTimeMillis();
180         VideoAdapter videoAdapter = new VideoAdapter();
181         Xml.parse(inputStream, Xml.Encoding.UTF_8, newContentHandler(videoAdapter));
182         long elapsed = System.currentTimeMillis() - start;
183         Log.i(TAG, "Saxy Model: " + elapsed + "ms");
184     }
185 
saxyModelTest(InputStream inputStream, ContentHandler contentHandler)186     private static void saxyModelTest(InputStream inputStream, ContentHandler contentHandler)
187             throws IOException, SAXException {
188         long start = System.currentTimeMillis();
189         Xml.parse(inputStream, Xml.Encoding.UTF_8, contentHandler);
190         long elapsed = System.currentTimeMillis() - start;
191         Log.i(TAG, "Saxy Model (preloaded): " + elapsed + "ms");
192     }
193 
194     private static class VideoAdapter {
addVideo(YouTubeVideo video)195         public void addVideo(YouTubeVideo video) {
196         }
197     }
198 
newContentHandler(VideoAdapter videoAdapter)199     private static ContentHandler newContentHandler(VideoAdapter videoAdapter) {
200         return new HandlerFactory().newContentHandler(videoAdapter);
201     }
202 
203     private static class HandlerFactory {
204         YouTubeVideo video;
205 
newContentHandler(VideoAdapter videoAdapter)206         public ContentHandler newContentHandler(VideoAdapter videoAdapter) {
207             RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
208 
209             final VideoListener videoListener = new VideoListener(videoAdapter);
210 
211             Element entry = root.getChild(ATOM_NAMESPACE, "entry");
212 
213             entry.setElementListener(videoListener);
214 
215             entry.getChild(ATOM_NAMESPACE, "id")
216                     .setEndTextElementListener(new EndTextElementListener() {
217                         public void end(String body) {
218                             video.videoId = body;
219                         }
220                     });
221 
222             entry.getChild(ATOM_NAMESPACE, "published")
223                     .setEndTextElementListener(new EndTextElementListener() {
224                         public void end(String body) {
225                             // TODO(tomtaylor): programmatically get the timezone
226                             video.dateAdded = new Time(Time.TIMEZONE_UTC);
227                             video.dateAdded.parse3339(body);
228                         }
229                     });
230 
231             Element author = entry.getChild(ATOM_NAMESPACE, "author");
232             author.getChild(ATOM_NAMESPACE, "name")
233                     .setEndTextElementListener(new EndTextElementListener() {
234                         public void end(String body) {
235                             video.authorName = body;
236                         }
237                     });
238 
239             Element mediaGroup = entry.getChild(MEDIA_NAMESPACE, "group");
240 
241             mediaGroup.getChild(MEDIA_NAMESPACE, "thumbnail")
242                     .setStartElementListener(new StartElementListener() {
243                         public void start(Attributes attributes) {
244                             String url = attributes.getValue("", "url");
245                             if (video.thumbnailUrl == null && url.length() > 0) {
246                                 video.thumbnailUrl = url;
247                             }
248                         }
249                     });
250 
251             mediaGroup.getChild(MEDIA_NAMESPACE, "content")
252                     .setStartElementListener(new StartElementListener() {
253                         public void start(Attributes attributes) {
254                             String url = attributes.getValue("", "url");
255                             if (url != null) {
256                                 video.videoUrl = url;
257                             }
258                         }
259                     });
260 
261             mediaGroup.getChild(MEDIA_NAMESPACE, "player")
262                     .setStartElementListener(new StartElementListener() {
263                         public void start(Attributes attributes) {
264                             String url = attributes.getValue("", "url");
265                             if (url != null) {
266                                 video.playbackUrl = url;
267                             }
268                         }
269                     });
270 
271             mediaGroup.getChild(MEDIA_NAMESPACE, "title")
272                     .setEndTextElementListener(new EndTextElementListener() {
273                         public void end(String body) {
274                             video.title = body;
275                         }
276                     });
277 
278             mediaGroup.getChild(MEDIA_NAMESPACE, "category")
279                     .setEndTextElementListener(new EndTextElementListener() {
280                         public void end(String body) {
281                             video.category = body;
282                         }
283                     });
284 
285             mediaGroup.getChild(MEDIA_NAMESPACE, "description")
286                     .setEndTextElementListener(new EndTextElementListener() {
287                         public void end(String body) {
288                             video.description = body;
289                         }
290                     });
291 
292             mediaGroup.getChild(MEDIA_NAMESPACE, "keywords")
293                     .setEndTextElementListener(new EndTextElementListener() {
294                         public void end(String body) {
295                             video.tags = body;
296                         }
297                     });
298 
299             mediaGroup.getChild(YOUTUBE_NAMESPACE, "duration")
300                     .setStartElementListener(new StartElementListener() {
301                         public void start(Attributes attributes) {
302                             String seconds = attributes.getValue("", "seconds");
303                             video.lengthInSeconds
304                                     = XmlUtils.convertValueToInt(seconds, 0);
305                         }
306                     });
307 
308             mediaGroup.getChild(YOUTUBE_NAMESPACE, "statistics")
309                     .setStartElementListener(new StartElementListener() {
310                         public void start(Attributes attributes) {
311                             String viewCount = attributes.getValue("", "viewCount");
312                             video.viewCount
313                                     = XmlUtils.convertValueToInt(viewCount, 0);
314                         }
315                     });
316 
317             entry.getChild(GDATA_NAMESPACE, "rating")
318                     .setStartElementListener(new StartElementListener() {
319                         public void start(Attributes attributes) {
320                             String average = attributes.getValue("", "average");
321                             video.rating = average == null
322                                     ? 0.0f : Float.parseFloat(average);
323                         }
324                     });
325 
326             return root.getContentHandler();
327         }
328 
329         class VideoListener implements ElementListener {
330 
331             final VideoAdapter videoAdapter;
332 
VideoListener(VideoAdapter videoAdapter)333             public VideoListener(VideoAdapter videoAdapter) {
334                 this.videoAdapter = videoAdapter;
335             }
336 
start(Attributes attributes)337             public void start(Attributes attributes) {
338                 video = new YouTubeVideo();
339             }
340 
end()341             public void end() {
342                 videoAdapter.addVideo(video);
343                 video = null;
344             }
345         }
346     }
347 
348     private static class YouTubeContentHandler extends DefaultHandler {
349 
350         final VideoAdapter videoAdapter;
351 
352         YouTubeVideo video = null;
353         StringBuilder builder = null;
354 
YouTubeContentHandler(VideoAdapter videoAdapter)355         public YouTubeContentHandler(VideoAdapter videoAdapter) {
356             this.videoAdapter = videoAdapter;
357         }
358 
359         @Override
startElement(String uri, String localName, String qName, Attributes attributes)360         public void startElement(String uri, String localName, String qName,
361                 Attributes attributes) throws SAXException {
362             if (uri.equals(ATOM_NAMESPACE)) {
363                 if (localName.equals("entry")) {
364                     video = new YouTubeVideo();
365                     return;
366                 }
367 
368                 if (video == null) {
369                     return;
370                 }
371 
372                 if (!localName.equals("id")
373                         && !localName.equals("published")
374                         && !localName.equals("name")) {
375                     return;
376                 }
377                 this.builder = new StringBuilder();
378                 return;
379 
380             }
381 
382             if (video == null) {
383                 return;
384             }
385 
386             if (uri.equals(MEDIA_NAMESPACE)) {
387                 if (localName.equals("thumbnail")) {
388                     String url = attributes.getValue("", "url");
389                     if (video.thumbnailUrl == null && url.length() > 0) {
390                         video.thumbnailUrl = url;
391                     }
392                     return;
393                 }
394 
395                 if (localName.equals("content")) {
396                     String url = attributes.getValue("", "url");
397                     if (url != null) {
398                         video.videoUrl = url;
399                     }
400                     return;
401                 }
402 
403                 if (localName.equals("player")) {
404                     String url = attributes.getValue("", "url");
405                     if (url != null) {
406                         video.playbackUrl = url;
407                     }
408                     return;
409                 }
410 
411                 if (localName.equals("title")
412                         || localName.equals("category")
413                         || localName.equals("description")
414                         || localName.equals("keywords")) {
415                     this.builder = new StringBuilder();
416                     return;
417                 }
418 
419                 return;
420             }
421 
422             if (uri.equals(YOUTUBE_NAMESPACE)) {
423                 if (localName.equals("duration")) {
424                     video.lengthInSeconds = XmlUtils.convertValueToInt(
425                             attributes.getValue("", "seconds"), 0);
426                     return;
427                 }
428 
429                 if (localName.equals("statistics")) {
430                     video.viewCount = XmlUtils.convertValueToInt(
431                             attributes.getValue("", "viewCount"), 0);
432                     return;
433                 }
434 
435                 return;
436             }
437 
438             if (uri.equals(GDATA_NAMESPACE)) {
439                 if (localName.equals("rating")) {
440                     String average = attributes.getValue("", "average");
441                     video.rating = average == null
442                             ? 0.0f : Float.parseFloat(average);
443                 }
444             }
445         }
446 
447         @Override
characters(char text[], int start, int length)448         public void characters(char text[], int start, int length)
449                 throws SAXException {
450             if (builder != null) {
451                 builder.append(text, start, length);
452             }
453         }
454 
takeText()455         String takeText() {
456             try {
457                 return builder.toString();
458             } finally {
459                 builder = null;
460             }
461         }
462 
463         @Override
endElement(String uri, String localName, String qName)464         public void endElement(String uri, String localName, String qName)
465                 throws SAXException {
466             if (video == null) {
467                 return;
468             }
469 
470             if (uri.equals(ATOM_NAMESPACE)) {
471                 if (localName.equals("published")) {
472                     // TODO(tomtaylor): programmatically get the timezone
473                     video.dateAdded = new Time(Time.TIMEZONE_UTC);
474                     video.dateAdded.parse3339(takeText());
475                     return;
476                 }
477 
478                 if (localName.equals("name")) {
479                     video.authorName = takeText();
480                     return;
481                 }
482 
483                 if (localName.equals("id")) {
484                     video.videoId = takeText();
485                     return;
486                 }
487 
488                 if (localName.equals("entry")) {
489                     // Add the video!
490                     videoAdapter.addVideo(video);
491                     video = null;
492                     return;
493                 }
494 
495                 return;
496             }
497 
498             if (uri.equals(MEDIA_NAMESPACE)) {
499                 if (localName.equals("description")) {
500                     video.description = takeText();
501                     return;
502                 }
503 
504                 if (localName.equals("keywords")) {
505                     video.tags = takeText();
506                     return;
507                 }
508 
509                 if (localName.equals("category")) {
510                     video.category = takeText();
511                     return;
512                 }
513 
514                 if (localName.equals("title")) {
515                     video.title = takeText();
516                 }
517             }
518         }
519     }
520 
521     private static class YouTubeVideo {
522         public String videoId;     // the id used to lookup on YouTube
523         public String videoUrl;       // the url to play the video
524         public String playbackUrl;    // the url to share for users to play video
525         public String thumbnailUrl;   // the url of the thumbnail image
526         public String title;
527         public Bitmap bitmap;      // cached bitmap of the thumbnail
528         public int lengthInSeconds;
529         public int viewCount;      // number of times the video has been viewed
530         public float rating;       // ranges from 0.0 to 5.0
531         public Boolean triedToLoadThumbnail;
532         public String authorName;
533         public Time dateAdded;
534         public String category;
535         public String tags;
536         public String description;
537     }
538 }
539 
540