/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.unit_tests;
import android.graphics.Bitmap;
import android.sax.Element;
import android.sax.ElementListener;
import android.sax.EndTextElementListener;
import android.sax.RootElement;
import android.sax.StartElementListener;
import android.sax.TextElementListener;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.LargeTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.format.Time;
import android.util.Log;
import android.util.Xml;
import com.android.internal.util.XmlUtils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class SafeSaxTest extends AndroidTestCase {
private static final String TAG = SafeSaxTest.class.getName();
private static final String ATOM_NAMESPACE = "http://www.w3.org/2005/Atom";
private static final String MEDIA_NAMESPACE = "http://search.yahoo.com/mrss/";
private static final String YOUTUBE_NAMESPACE = "http://gdata.youtube.com/schemas/2007";
private static final String GDATA_NAMESPACE = "http://schemas.google.com/g/2005";
private static class ElementCounter implements ElementListener {
int starts = 0;
int ends = 0;
public void start(Attributes attributes) {
starts++;
}
public void end() {
ends++;
}
}
private static class TextElementCounter implements TextElementListener {
int starts = 0;
String bodies = "";
public void start(Attributes attributes) {
starts++;
}
public void end(String body) {
this.bodies += body;
}
}
@SmallTest
public void testListener() throws Exception {
String xml = "\n"
+ "\n"
+ "a\n"
+ "\n"
+ "\n"
+ "b\n"
+ "\n"
+ "\n";
RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
Element entry = root.requireChild(ATOM_NAMESPACE, "entry");
Element id = entry.requireChild(ATOM_NAMESPACE, "id");
ElementCounter rootCounter = new ElementCounter();
ElementCounter entryCounter = new ElementCounter();
TextElementCounter idCounter = new TextElementCounter();
root.setElementListener(rootCounter);
entry.setElementListener(entryCounter);
id.setTextElementListener(idCounter);
Xml.parse(xml, root.getContentHandler());
assertEquals(1, rootCounter.starts);
assertEquals(1, rootCounter.ends);
assertEquals(2, entryCounter.starts);
assertEquals(2, entryCounter.ends);
assertEquals(2, idCounter.starts);
assertEquals("ab", idCounter.bodies);
}
@SmallTest
public void testMissingRequiredChild() throws Exception {
String xml = "";
RootElement root = new RootElement("feed");
root.requireChild("entry");
try {
Xml.parse(xml, root.getContentHandler());
fail("expected exception not thrown");
} catch (SAXException e) {
// Expected.
}
}
@SmallTest
public void testMixedContent() throws Exception {
String xml = "";
RootElement root = new RootElement("feed");
root.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
}
});
try {
Xml.parse(xml, root.getContentHandler());
fail("expected exception not thrown");
} catch (SAXException e) {
// Expected.
}
}
@LargeTest
public void testPerformance() throws Exception {
InputStream in = mContext.getResources().openRawResource(R.raw.youtube);
byte[] xmlBytes;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
xmlBytes = out.toByteArray();
} finally {
in.close();
}
Log.i("***", "File size: " + (xmlBytes.length / 1024) + "k");
VideoAdapter videoAdapter = new VideoAdapter();
ContentHandler handler = newContentHandler(videoAdapter);
for (int i = 0; i < 2; i++) {
pureSaxTest(new ByteArrayInputStream(xmlBytes));
saxyModelTest(new ByteArrayInputStream(xmlBytes));
saxyModelTest(new ByteArrayInputStream(xmlBytes), handler);
}
}
private static void pureSaxTest(InputStream inputStream) throws IOException, SAXException {
long start = System.currentTimeMillis();
VideoAdapter videoAdapter = new VideoAdapter();
Xml.parse(inputStream, Xml.Encoding.UTF_8, new YouTubeContentHandler(videoAdapter));
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "pure SAX: " + elapsed + "ms");
}
private static void saxyModelTest(InputStream inputStream) throws IOException, SAXException {
long start = System.currentTimeMillis();
VideoAdapter videoAdapter = new VideoAdapter();
Xml.parse(inputStream, Xml.Encoding.UTF_8, newContentHandler(videoAdapter));
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "Saxy Model: " + elapsed + "ms");
}
private static void saxyModelTest(InputStream inputStream, ContentHandler contentHandler)
throws IOException, SAXException {
long start = System.currentTimeMillis();
Xml.parse(inputStream, Xml.Encoding.UTF_8, contentHandler);
long elapsed = System.currentTimeMillis() - start;
Log.i(TAG, "Saxy Model (preloaded): " + elapsed + "ms");
}
private static class VideoAdapter {
public void addVideo(YouTubeVideo video) {
}
}
private static ContentHandler newContentHandler(VideoAdapter videoAdapter) {
return new HandlerFactory().newContentHandler(videoAdapter);
}
private static class HandlerFactory {
YouTubeVideo video;
public ContentHandler newContentHandler(VideoAdapter videoAdapter) {
RootElement root = new RootElement(ATOM_NAMESPACE, "feed");
final VideoListener videoListener = new VideoListener(videoAdapter);
Element entry = root.getChild(ATOM_NAMESPACE, "entry");
entry.setElementListener(videoListener);
entry.getChild(ATOM_NAMESPACE, "id")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.videoId = body;
}
});
entry.getChild(ATOM_NAMESPACE, "published")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
// TODO(tomtaylor): programmatically get the timezone
video.dateAdded = new Time(Time.TIMEZONE_UTC);
video.dateAdded.parse3339(body);
}
});
Element author = entry.getChild(ATOM_NAMESPACE, "author");
author.getChild(ATOM_NAMESPACE, "name")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.authorName = body;
}
});
Element mediaGroup = entry.getChild(MEDIA_NAMESPACE, "group");
mediaGroup.getChild(MEDIA_NAMESPACE, "thumbnail")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String url = attributes.getValue("", "url");
if (video.thumbnailUrl == null && url.length() > 0) {
video.thumbnailUrl = url;
}
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "content")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String url = attributes.getValue("", "url");
if (url != null) {
video.videoUrl = url;
}
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "player")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String url = attributes.getValue("", "url");
if (url != null) {
video.playbackUrl = url;
}
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "title")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.title = body;
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "category")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.category = body;
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "description")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.description = body;
}
});
mediaGroup.getChild(MEDIA_NAMESPACE, "keywords")
.setEndTextElementListener(new EndTextElementListener() {
public void end(String body) {
video.tags = body;
}
});
mediaGroup.getChild(YOUTUBE_NAMESPACE, "duration")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String seconds = attributes.getValue("", "seconds");
video.lengthInSeconds
= XmlUtils.convertValueToInt(seconds, 0);
}
});
mediaGroup.getChild(YOUTUBE_NAMESPACE, "statistics")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String viewCount = attributes.getValue("", "viewCount");
video.viewCount
= XmlUtils.convertValueToInt(viewCount, 0);
}
});
entry.getChild(GDATA_NAMESPACE, "rating")
.setStartElementListener(new StartElementListener() {
public void start(Attributes attributes) {
String average = attributes.getValue("", "average");
video.rating = average == null
? 0.0f : Float.parseFloat(average);
}
});
return root.getContentHandler();
}
class VideoListener implements ElementListener {
final VideoAdapter videoAdapter;
public VideoListener(VideoAdapter videoAdapter) {
this.videoAdapter = videoAdapter;
}
public void start(Attributes attributes) {
video = new YouTubeVideo();
}
public void end() {
videoAdapter.addVideo(video);
video = null;
}
}
}
private static class YouTubeContentHandler extends DefaultHandler {
final VideoAdapter videoAdapter;
YouTubeVideo video = null;
StringBuilder builder = null;
public YouTubeContentHandler(VideoAdapter videoAdapter) {
this.videoAdapter = videoAdapter;
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
if (uri.equals(ATOM_NAMESPACE)) {
if (localName.equals("entry")) {
video = new YouTubeVideo();
return;
}
if (video == null) {
return;
}
if (!localName.equals("id")
&& !localName.equals("published")
&& !localName.equals("name")) {
return;
}
this.builder = new StringBuilder();
return;
}
if (video == null) {
return;
}
if (uri.equals(MEDIA_NAMESPACE)) {
if (localName.equals("thumbnail")) {
String url = attributes.getValue("", "url");
if (video.thumbnailUrl == null && url.length() > 0) {
video.thumbnailUrl = url;
}
return;
}
if (localName.equals("content")) {
String url = attributes.getValue("", "url");
if (url != null) {
video.videoUrl = url;
}
return;
}
if (localName.equals("player")) {
String url = attributes.getValue("", "url");
if (url != null) {
video.playbackUrl = url;
}
return;
}
if (localName.equals("title")
|| localName.equals("category")
|| localName.equals("description")
|| localName.equals("keywords")) {
this.builder = new StringBuilder();
return;
}
return;
}
if (uri.equals(YOUTUBE_NAMESPACE)) {
if (localName.equals("duration")) {
video.lengthInSeconds = XmlUtils.convertValueToInt(
attributes.getValue("", "seconds"), 0);
return;
}
if (localName.equals("statistics")) {
video.viewCount = XmlUtils.convertValueToInt(
attributes.getValue("", "viewCount"), 0);
return;
}
return;
}
if (uri.equals(GDATA_NAMESPACE)) {
if (localName.equals("rating")) {
String average = attributes.getValue("", "average");
video.rating = average == null
? 0.0f : Float.parseFloat(average);
}
}
}
@Override
public void characters(char text[], int start, int length)
throws SAXException {
if (builder != null) {
builder.append(text, start, length);
}
}
String takeText() {
try {
return builder.toString();
} finally {
builder = null;
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
if (video == null) {
return;
}
if (uri.equals(ATOM_NAMESPACE)) {
if (localName.equals("published")) {
// TODO(tomtaylor): programmatically get the timezone
video.dateAdded = new Time(Time.TIMEZONE_UTC);
video.dateAdded.parse3339(takeText());
return;
}
if (localName.equals("name")) {
video.authorName = takeText();
return;
}
if (localName.equals("id")) {
video.videoId = takeText();
return;
}
if (localName.equals("entry")) {
// Add the video!
videoAdapter.addVideo(video);
video = null;
return;
}
return;
}
if (uri.equals(MEDIA_NAMESPACE)) {
if (localName.equals("description")) {
video.description = takeText();
return;
}
if (localName.equals("keywords")) {
video.tags = takeText();
return;
}
if (localName.equals("category")) {
video.category = takeText();
return;
}
if (localName.equals("title")) {
video.title = takeText();
}
}
}
}
private static class YouTubeVideo {
public String videoId; // the id used to lookup on YouTube
public String videoUrl; // the url to play the video
public String playbackUrl; // the url to share for users to play video
public String thumbnailUrl; // the url of the thumbnail image
public String title;
public Bitmap bitmap; // cached bitmap of the thumbnail
public int lengthInSeconds;
public int viewCount; // number of times the video has been viewed
public float rating; // ranges from 0.0 to 5.0
public Boolean triedToLoadThumbnail;
public String authorName;
public Time dateAdded;
public String category;
public String tags;
public String description;
}
}