package de.blau.android.tasks;
import java.io.IOException;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Locale;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.text.Html;
import de.blau.android.App;
import de.blau.android.osm.JosmXmlSerializable;
import de.blau.android.util.DateFormatter;
/**
* A bug in the OpenStreetBugs database, or a prospective new bug.
* This now works with the OSM Notes system, many references to bugs remain for hysterical reasons :-).
* @author Andrew Gregory
* @author Simon
*/
public class Note extends Task implements Serializable, JosmXmlSerializable {
/**
*
*/
private static final long serialVersionUID = 4L;
/**
* Date pattern used to parse the 'date' attribute of a 'note' from XML.
*/
private static final String DATE_PATTERN_NOTE_CREATED_AT = "yyyy-MM-dd HH:mm:ss z";
/**
* This the standard data/time format used in .osn files and elsewhere in the API, and yes it is different than the above
*/
private final SimpleDateFormat JOSM_DATE = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
/** created and closed dates **/
private Date created = null;
private Date closed = null;
/** Bug comments. */
private ArrayList<NoteComment> comments = null;
private State originalState; // track what we original had
/**
* Create a Bug from an OSB GPX XML wpt element.
* @param parser Parser up to a wpt element.
* @throws IOException If there was a problem parsing the XML.
* @throws XmlPullParserException If there was a problem parsing the XML.
* @throws NumberFormatException If there was a problem parsing the XML.
*/
public Note(XmlPullParser parser) throws XmlPullParserException, IOException, NumberFormatException {
// note tag has already been read ... very ugly should refactor
lat = (int)(Double.parseDouble(parser.getAttributeValue(null, "lat")) * 1E7d);
lon = (int)(Double.parseDouble(parser.getAttributeValue(null, "lon")) * 1E7d);
parseBug(parser);
}
public void parseBug(XmlPullParser parser) throws XmlPullParserException, IOException, NumberFormatException {
int eventType;
final int START = 0;
final int COMMENTS = 1;
final int COMMENT = 2;
int state = START;
String text = "No Text";
String nickname = "No Name";
int uid = -1;
String action = "Unknown action";
Date timestamp = null;
while ((eventType = parser.next()) != XmlPullParser.END_DOCUMENT) {
String tagName = parser.getName();
if (state == START) {
if (eventType == XmlPullParser.END_TAG) {
if ("note".equals(tagName)) {
break;
}
}
if (eventType == XmlPullParser.START_TAG) {
if ("note".equals(tagName)) {
lat = (int)(Double.parseDouble(parser.getAttributeValue(null, "lat")) * 1E7d);
lon = (int)(Double.parseDouble(parser.getAttributeValue(null, "lon")) * 1E7d);
}
if ("id".equals(tagName) && parser.next() == XmlPullParser.TEXT) {
id = Long.parseLong(parser.getText().trim());
}
if ("status".equals(tagName) && parser.next() == XmlPullParser.TEXT) {
if (parser.getText().trim().equalsIgnoreCase("closed")) {
close();
originalState = State.CLOSED;
} else {
open();
originalState = State.OPEN;
}
}
if ("date_created".equals(tagName) && parser.next() == XmlPullParser.TEXT) {
String trimmedDate = parser.getText().trim();
try {
created = DateFormatter.getDate(
DATE_PATTERN_NOTE_CREATED_AT, trimmedDate);
} catch (java.text.ParseException pex) {
created = new Date();
}
}
if ("date_closed".equals(tagName) && parser.next() == XmlPullParser.TEXT) {
String trimmedDate = parser.getText().trim();
try {
closed = DateFormatter.getDate(
DATE_PATTERN_NOTE_CREATED_AT, trimmedDate);
} catch (java.text.ParseException pex) {
closed = new Date();
}
}
if ("comments".equals(tagName)) {
comments = new ArrayList<NoteComment>();
state = COMMENTS;
}
}
}
else if (state == COMMENTS) {
if ((eventType == XmlPullParser.END_TAG) && "comments".equals(tagName)) {
state = START;
}
else if ((eventType == XmlPullParser.START_TAG) && "comment".equals(tagName)) {
state = COMMENT;
text = "No Text";
nickname = "No Name";
uid = -1;
action = "Unknown action";
timestamp = null;
}
}
else if (state == COMMENT) {
if ((eventType == XmlPullParser.END_TAG) && "comment".equals(tagName)) {
comments.add(new NoteComment(this, text, nickname, uid, action, timestamp));
state = COMMENTS;
}
else if (eventType == XmlPullParser.START_TAG) {
if ("user".equals(tagName) && parser.next() == XmlPullParser.TEXT) {
nickname = parser.getText().trim();
}
if ("uid".equals(tagName) && parser.next() == XmlPullParser.TEXT) {
uid = Integer.parseInt(parser.getText().trim());
}
if ("action".equals(tagName) && parser.next() == XmlPullParser.TEXT) {
action = parser.getText().trim();
}
if ("html".equals(tagName) && parser.next() == XmlPullParser.TEXT) {
text = parser.getText().trim();
}
if ("date".equals(tagName) && parser.next() == XmlPullParser.TEXT) {
String trimmedDate = parser.getText().trim();
try {
timestamp = DateFormatter.getDate(
DATE_PATTERN_NOTE_CREATED_AT, trimmedDate);
} catch (java.text.ParseException pex) {
timestamp = new Date();
}
}
}
}
}
}
/**
* Create a new bug.
* @param lat Latitude *1E7.
* @param lon Longitude *1E7.
*/
public Note(int lat, int lon) {
id = App.getTaskStorage().getNextId();
this.created = new Date();
this.lat = lat;
this.lon = lon;
open();
comments = new ArrayList<NoteComment>();
}
/**
* Get the complete bug comment suitable for use with the OSB database.
* @return All the comments concatenated (joined with <hr />).
*/
public String getComment() {
StringBuilder result = new StringBuilder();
for (NoteComment comment : comments) {
if (result.length() > 0) {
result.append("<hr />");
}
result.append(comment.toString());
}
return result.toString();
}
/**
* Get a string descriptive of the bug. This is intended to be used as a
* short bit of text representative of the bug.
* @return The first comment of the bug.
*/
@Override
public String getDescription() {
return "note "+ (comments != null && comments.size() > 0 ? Html.fromHtml(comments.get(0).getText()) : "<new>"); //TODO externalize string
}
/**
* Get the timestamp of the most recent change.
* @return The timestamp of the most recent change.
*/
@Override
public Date getLastUpdate() {
Date result = null;
for (NoteComment c : comments) {
Date t = c.getTimestamp();
if (t != null && (result == null || t.after(result))) {
result = t;
}
}
if (result == null) {
result = new Date();
}
return result;
}
public void addComment(String comment) {
if (comment != null && comment.length() > 0) {
comments.add(new NoteComment(this,comment));
}
}
/**
* Return the number of comments
*
* @return the number of comments attached to this note
*/
public int count() {
return comments == null ? 0 : comments.size();
}
public NoteComment getLastComment() {
if (comments != null && comments.size() > 0) {
return comments.get(comments.size()-1);
}
return null;
}
public State getOriginalState() {
return originalState;
}
public String bugFilterKey() {
return "NOTES";
}
@Override
public void toJosmXml(final XmlSerializer s)
throws IllegalArgumentException, IllegalStateException, IOException {
s.startTag("", "note");
s.attribute("", "id", Long.toString(id));
s.attribute("", "lat", Double.toString((lat / 1E7)));
s.attribute("", "lon", Double.toString((lon / 1E7)));
if (created != null) {
s.attribute("", "created_at", toJOSMDate(created));
}
if (closed != null) {
s.attribute("", "closed_at", toJOSMDate(closed));
}
if (count() > 0) {
for (NoteComment c:comments) {
c.toJosmXml(s);
}
}
s.endTag("", "note");
}
String toJOSMDate(Date date) {
String josmDate = JOSM_DATE.format(date);
return josmDate.substring(0, josmDate.length()-2); // strip last two digits
}
}