/*
* Copyright 2013 Fae Hutter
*
* 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.vaguehope.onosendai.provider.successwhale;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Stack;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import com.vaguehope.onosendai.config.Account;
import com.vaguehope.onosendai.model.Meta;
import com.vaguehope.onosendai.model.MetaType;
import com.vaguehope.onosendai.model.Tweet;
import com.vaguehope.onosendai.model.TweetBuilder;
import com.vaguehope.onosendai.model.TweetList;
import com.vaguehope.onosendai.provider.ServiceRef;
import com.vaguehope.onosendai.util.EqualHelper;
import com.vaguehope.onosendai.util.StringHelper;
public class SuccessWhaleFeedXml implements ContentHandler {
private final Account account;
private final Collection<Meta> extraMetas;
private final List<Tweet> tweets = new ArrayList<Tweet>();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
protected SuccessWhaleFeedXml (final Account account, final InputStream dataIs) throws SAXException {
this(account, dataIs, null);
}
public SuccessWhaleFeedXml (final Account account, final InputStream dataIs, final Collection<Meta> extraMetas) throws SAXException {
this.account = account;
this.extraMetas = extraMetas;
final SAXParserFactory spf = SAXParserFactory.newInstance();
SAXParser sp;
try {
sp = spf.newSAXParser();
final XMLReader xmlReader = sp.getXMLReader();
xmlReader.setContentHandler(this);
try {
xmlReader.parse(new InputSource(dataIs));
}
catch (final IOException e) {
throw new SAXException(e);
}
}
catch (final ParserConfigurationException e) {
throw new SAXException(e);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
public TweetList getTweets () {
return new TweetList(this.tweets);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
private final MessageFormat likesMsgFmt = new MessageFormat("{0} {0,choice,0#likes|1#like|1<likes}"); //ES
private final MessageFormat commentsMsgFmt = new MessageFormat("{0} {0,choice,0#comments|1#comment|1<comments}"); //ES
private final Stack<String> stack = new Stack<String>();
private StringBuilder currentText;
private final TweetBuilder currentItem = new TweetBuilder();
private final TweetBuilder currentComment = new TweetBuilder();
private boolean addThisItem = true;
private String stashedFromUserName;
private String stashedToUserName;
private String stashedFirstLinkTitle;
private String stashedLinkUrl;
private String stashedLinkExpandedUrl;
private String stashedLinkTitle;
private String stashedLinkPreviewUrl;
private String stashedFetchedForUserid;
private String stashedService;
private String stashedRetweetedByUser;
private String stashedRetweetedByUserName;
private String stashedRetweetedByUserId;
private String stashedMentionUserName;
private String stashedMentionFullName;
private String stashedUserId;
private String stashedHashtagText;
private String stashedActionName;
private String stashedActionPostId;
private final DateTimeFormatter dateFormat = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ssZ");
@Override
public void startElement (final String uri, final String localName, final String qName, final Attributes attributes) throws SAXException {
this.stack.push(!localName.isEmpty() ? localName : qName);
if (this.currentText == null || this.currentText.length() > 0) {
this.currentText = new StringBuilder();
}
}
@Override
public void endElement (final String uri, final String localName, final String qName) throws SAXException {
final String elementName = !localName.isEmpty() ? localName : qName;
if (this.stack.size() == 3 && elementName.equals("item")) { // NOSONAR not a magic number.
if (this.addThisItem) {
this.currentItem.bodyIfAbsent(this.stashedFirstLinkTitle);
this.currentItem.fullname(this.stashedToUserName != null
? this.stashedFromUserName + " > " + this.stashedToUserName
: this.stashedFromUserName);
this.currentItem.meta(MetaType.ACCOUNT, this.account.getId());
if (this.extraMetas != null) this.currentItem.metas(this.extraMetas);
this.currentItem.meta(MetaType.SERVICE, ServiceRef.createServiceMeta(this.stashedService, this.stashedFetchedForUserid));
if (this.stashedRetweetedByUserId != null && !this.stashedRetweetedByUserId.equals(this.stashedFetchedForUserid)) {
this.currentItem.meta(MetaType.MENTION, this.stashedRetweetedByUser, String.format("RT by %s", this.stashedRetweetedByUserName));
}
final int mediaCount = this.currentItem.countMetaOfType(MetaType.MEDIA);
if (mediaCount > 1) this.currentItem.subtitle(String.format("%s pictures", mediaCount));
this.tweets.add(this.currentItem.build());
}
this.stashedFromUserName = null;
this.stashedToUserName = null;
this.stashedFirstLinkTitle = null;
this.stashedFetchedForUserid = null;
this.stashedService = null;
this.stashedRetweetedByUser = null;
this.stashedRetweetedByUserName = null;
this.stashedRetweetedByUserId = null;
this.addThisItem = true;
}
else if (this.stack.size() == 4) { // NOSONAR not a magic number.
if ("fetchedforuserid".equals(elementName)) {
this.stashedFetchedForUserid = this.currentText.toString();
}
else if ("service".equals(elementName)) {
this.stashedService = this.currentText.toString();
}
}
else if (this.stack.size() == 5) { // NOSONAR not a magic number.
if ("id".equals(elementName)) {
this.currentItem.id(this.currentText.toString());
}
else if ("fromuser".equals(elementName)) {
this.currentItem.username(this.currentText.toString());
}
else if ("fromusername".equals(elementName)) {
this.stashedFromUserName = this.currentText.toString();
}
else if ("tousername".equals(elementName)) {
this.stashedToUserName = this.currentText.toString();
}
else if ("text".equals(elementName)) {
this.currentItem.body(this.currentText.toString());
}
else if ("time".equals(elementName)) {
final long millis = this.dateFormat.parseMillis(this.currentText.toString());
this.currentItem.unitTimeSeconds(TimeUnit.MILLISECONDS.toSeconds(millis));
}
else if ("fromuseravatar".equals(elementName)) {
this.currentItem.avatarUrl(this.currentText.toString());
}
else if ("inreplytostatusid".equals(elementName) && this.currentText.length() > 0) {
this.currentItem.meta(MetaType.INREPLYTO, this.currentText.toString());
}
else if ("retweetedbyuserid".equals(elementName)) {
this.stashedRetweetedByUserId = this.currentText.toString();
}
else if ("retweetedbyusername".equals(elementName)) {
this.stashedRetweetedByUserName = this.currentText.toString();
}
else if ("retweetedbyuser".equals(elementName)) {
this.stashedRetweetedByUser = this.currentText.toString();
}
else if ("replytoid".equals(elementName)) {
this.currentItem.replyToId(this.currentText.toString());
}
else if ("numcomments".equals(elementName)) {
final int v = Integer.parseInt(this.currentText.toString());
if (v > 0) this.currentItem.subtitle(this.commentsMsgFmt.format(new Object[] { Integer.valueOf(v) }));
}
else if ("numlikes".equals(elementName)) {
final int v = Integer.parseInt(this.currentText.toString());
if (v > 0) this.currentItem.subtitle(this.likesMsgFmt.format(new Object[] { Integer.valueOf(v) }));
}
}
else if (this.stack.size() == 6) { // NOSONAR not a magic number.
if ("link".equals(elementName)) {
if (this.stashedLinkPreviewUrl != null && this.stashedLinkExpandedUrl != null) {
this.currentItem.meta(MetaType.MEDIA, this.stashedLinkPreviewUrl, this.stashedLinkExpandedUrl);
}
else if (this.stashedLinkPreviewUrl != null && this.stashedLinkUrl != null) {
this.currentItem.meta(MetaType.MEDIA, this.stashedLinkPreviewUrl, this.stashedLinkUrl);
}
if (this.stashedLinkExpandedUrl != null) {
this.currentItem.meta(MetaType.URL, this.stashedLinkExpandedUrl, this.stashedLinkTitle);
}
else if (this.stashedLinkUrl != null) {
this.currentItem.meta(MetaType.URL, this.stashedLinkUrl, this.stashedLinkTitle);
}
this.stashedLinkUrl = null;
this.stashedLinkExpandedUrl = null;
this.stashedLinkTitle = null;
this.stashedLinkPreviewUrl = null;
}
else if ("username".equals(elementName)) {
if (!EqualHelper.equal(this.stashedFetchedForUserid, this.stashedUserId)) {
this.currentItem.meta(MetaType.MENTION, this.stashedMentionUserName, this.stashedMentionFullName);
}
this.stashedMentionUserName = null;
this.stashedMentionFullName = null;
this.stashedUserId = null;
}
else if ("hashtag".equals(elementName)) {
this.currentItem.meta(MetaType.HASHTAG, this.stashedHashtagText);
this.stashedHashtagText = null;
}
else if ("comment".equals(elementName)) {
this.tweets.add(this.currentComment.build());
this.addThisItem = false;
}
else if ("action".equals(elementName)) {
if ("delete".equals(this.stashedActionName)) {
if (StringHelper.isEmpty(this.stashedActionPostId)) throw new IllegalStateException("Found </action> before </postid>.");
this.currentItem.meta(MetaType.EDIT_SID, this.stashedActionPostId);
this.stashedActionPostId = null;
}
}
}
else if (this.stack.size() == 7) { // NOSONAR not a magic number.
if ("url".equals(elementName) && "link".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.stashedLinkUrl = this.currentText.toString();
}
else if ("expanded-url".equals(elementName) && "link".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.stashedLinkExpandedUrl = this.currentText.toString();
}
else if ("title".equals(elementName) && "link".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.stashedLinkTitle = this.currentText.toString();
if (this.stashedFirstLinkTitle == null) this.stashedFirstLinkTitle = this.stashedLinkTitle;
}
else if ("preview".equals(elementName) && "link".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.stashedLinkPreviewUrl = this.currentText.toString();
}
else if ("text".equals(elementName) && "hashtag".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.stashedHashtagText = this.currentText.toString();
}
else if ("user".equals(elementName) && "username".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.stashedMentionUserName = this.currentText.toString();
}
else if ("username".equals(elementName) && "username".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.stashedMentionFullName = this.currentText.toString();
}
else if ("id".equals(elementName)) {
if ("username".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.stashedUserId = this.currentText.toString();
}
else if ("comment".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.currentComment.id(this.currentText.toString());
}
}
else if ("message".equals(elementName) && "comment".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.currentComment.body(this.currentText.toString());
}
else if ("created-time".equals(elementName) && "comment".equals(this.stack.get(5))) { // NOSONAR not a magic number.
final long millis = this.dateFormat.parseMillis(this.currentText.toString());
this.currentComment.unitTimeSeconds(TimeUnit.MILLISECONDS.toSeconds(millis));
}
else if ("name".equals(elementName) && "action".equals(this.stack.get(5))) {
this.stashedActionName = this.currentText.toString();
}
}
else if (this.stack.size() == 8) { // NOSONAR not a magic number.
if ("name".equals(elementName) && "comment".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.currentComment.fullname(this.currentText.toString());
}
else if ("fromuseravatar".equals(elementName) && "comment".equals(this.stack.get(5))) { // NOSONAR not a magic number.
this.currentComment.avatarUrl(this.currentText.toString());
}
else if ("postid".equals(elementName) && "action".equals(this.stack.get(5))) {
this.stashedActionPostId = this.currentText.toString();
}
}
this.stack.pop();
}
@Override
public void characters (final char[] ch, final int start, final int length) throws SAXException {
this.currentText.append(ch, start, length);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@Override
public void endDocument () throws SAXException { /* UNUSED */}
@Override
public void endPrefixMapping (final String prefix) throws SAXException { /* UNUSED */}
@Override
public void ignorableWhitespace (final char[] ch, final int start, final int length) throws SAXException { /* UNUSED */}
@Override
public void processingInstruction (final String target, final String data) throws SAXException { /* UNUSED */}
@Override
public void setDocumentLocator (final Locator locator) { /* UNUSED */}
@Override
public void skippedEntity (final String name) throws SAXException { /* UNUSED */}
@Override
public void startDocument () throws SAXException { /* UNUSED */}
@Override
public void startPrefixMapping (final String prefix, final String uri) throws SAXException { /* UNUSED */}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
}