/*
* $HeadURL$
* $Id$
* Copyright (c) 2006-2012 by Public Library of Science http://plos.org http://ambraproject.org
* 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.0Unless 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 org.ambraproject.views;
import org.ambraproject.models.Annotation;
import org.ambraproject.models.AnnotationType;
import org.ambraproject.models.UserProfile;
import org.ambraproject.util.TextUtils;
import org.apache.commons.lang.StringUtils;
import javax.annotation.Nullable;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.SimpleTimeZone;
/**
* Immutable view wrapper around annotations
*
* @author Alex Kudlick 3/12/12
*/
public class AnnotationView {
private static final int TRUNCATED_COMMENT_LENGTH = 256;
private static final AnnotationView[] EMPTY_ARRAY = new AnnotationView[0];
public static final Comparator<Annotation> REPLY_COMPARATOR = new Comparator<Annotation>() {
@Override
public int compare(Annotation reply1, Annotation reply2) {
return reply1.getCreated().compareTo(reply2.getCreated());
}
};
private final String originalTitle;
private final String title;
private final String body;
private final String originalBody;
private final String truncatedBody;
private final String bodyWithUrlLinkingNoPTags;
private final String truncatedBodyWithUrlLinkingNoPTags;
private final String bodyWithHighlightedText;
private final String competingInterestStatement;
private final String truncatedCompetingInterestStatement;
private final String annotationUri;
private final Long ID;
private final Long creatorID;
private final String creatorDisplayName;
private final String creatorFormattedName;
private final Long articleID;
private final Long parentID;
private final String articleDoi;
private final String articleTitle;
private final Date created;
private final String createdFormatted;
private final AnnotationType type;
private final AnnotationView[] replies;
private final Date lastReplyDate;
private final int totalNumReplies;
/**
* Create a new AnnotationView
*
* @param annotation the annotation being wrapped
* @param articleTitle the title of the article that is annotated
* @param articleDoi the doi of the article that is annotated
* @param fullReplyTree a full map of all child replies, from Id -> all replies to the annotation with that id.
* Allowed to be null or empty
*/
public AnnotationView(Annotation annotation, String articleDoi,
String articleTitle,
@Nullable Map<Long, List<Annotation>> fullReplyTree) {
this.ID = annotation.getID();
this.articleDoi = articleDoi;
this.articleTitle = articleTitle;
if (annotation.getAnnotationUri() != null) {
this.annotationUri = annotation.getAnnotationUri().replaceFirst("info:doi/", "");
} else {
this.annotationUri = null;
}
this.originalTitle = annotation.getTitle();
String escapedTitle = TextUtils.escapeHtml(annotation.getTitle());
this.title = escapedTitle;
if (annotation.getBody() == null) {
this.originalBody = "";
this.body = "";
this.truncatedBody = "";
this.bodyWithUrlLinkingNoPTags = "";
this.truncatedBodyWithUrlLinkingNoPTags = "";
this.bodyWithHighlightedText = "";
} else {
this.originalBody = annotation.getBody();
this.body = TextUtils.hyperlinkEnclosedWithPTags(TextUtils.escapeHtml(annotation.getBody()), 25);
this.truncatedBody = TextUtils.hyperlinkEnclosedWithPTags(TextUtils.truncateText(TextUtils.escapeHtml(annotation.getBody()), TRUNCATED_COMMENT_LENGTH), 25);
this.bodyWithUrlLinkingNoPTags = TextUtils.hyperlink(TextUtils.escapeHtml(annotation.getBody()), 25);
this.truncatedBodyWithUrlLinkingNoPTags = TextUtils.hyperlink(TextUtils.truncateText(TextUtils.escapeHtml(annotation.getBody()), TRUNCATED_COMMENT_LENGTH), 25);
if (annotation.getHighlightedText() != null) {
// highlighted text contains the highlighted text
// and a link to the paragraph location of where the highlighted text is located
String bodyWithHt = annotation.getHighlightedText() + "\n\n" + annotation.getBody();
this.bodyWithHighlightedText =TextUtils.hyperlinkEnclosedWithPTags(TextUtils.escapeHtml(bodyWithHt), 150);
} else {
this.bodyWithHighlightedText = this.body;
}
}
if (annotation.getCompetingInterestBody() == null) {
this.competingInterestStatement = "";
this.truncatedCompetingInterestStatement = "";
} else {
this.competingInterestStatement = TextUtils.escapeHtml(annotation.getCompetingInterestBody());
this.truncatedCompetingInterestStatement = TextUtils.truncateText(TextUtils.escapeHtml(annotation.getCompetingInterestBody()), TRUNCATED_COMMENT_LENGTH);
}
this.creatorID = annotation.getCreator().getID();
this.creatorDisplayName = annotation.getCreator().getDisplayName();
this.creatorFormattedName = createFormattedName(annotation.getCreator());
this.articleID = annotation.getArticleID();
this.parentID = annotation.getParentID();
this.type = annotation.getType();
//Defensive copy
Calendar date = Calendar.getInstance();
date.setTime(annotation.getCreated());
this.created = date.getTime();
SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
fmt.setTimeZone(new SimpleTimeZone(0, "UTC"));
this.createdFormatted = fmt.format(created);
if (fullReplyTree == null || fullReplyTree.isEmpty()) {
this.replies = EMPTY_ARRAY;
this.lastReplyDate = this.created;
this.totalNumReplies = 0;
} else {
List<Annotation> repliesToThis = fullReplyTree.get(annotation.getID());
if (repliesToThis == null || repliesToThis.isEmpty()) {
this.replies = EMPTY_ARRAY;
} else {
//sort the replies by date so our reply list will be ordered, earliest first
Collections.sort(repliesToThis, REPLY_COMPARATOR);
this.replies = new AnnotationView[repliesToThis.size()];
for (int i = 0; i < repliesToThis.size(); i++) {
this.replies[i] = new AnnotationView(repliesToThis.get(i), articleDoi, articleTitle, fullReplyTree);
}
}
//now populate lastReplyDate and totalNumReplies
this.totalNumReplies = calculateTotalNumReplies();
this.lastReplyDate = calculateMostRecentReplyDate();
}
}
private int calculateTotalNumReplies() {
int replyCount = 0;
replyCount += this.replies.length;
for (AnnotationView reply : this.replies) {
replyCount += reply.calculateTotalNumReplies();
}
return replyCount;
}
//since all the replies have been sorted by the time we call this method, we can just check the last entry in the reply array
private Date calculateMostRecentReplyDate() {
Date replyDate = this.created;
if (this.replies.length > 0) {
replyDate = this.replies[this.replies.length - 1].calculateMostRecentReplyDate();
}
return replyDate;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AnnotationView that = (AnnotationView) o;
if (ID != null ? !ID.equals(that.ID) : that.ID != null) return false;
if (annotationUri != null ? !annotationUri.equals(that.annotationUri) : that.annotationUri != null) return false;
if (articleDoi != null ? !articleDoi.equals(that.articleDoi) : that.articleDoi != null) return false;
if (articleTitle != null ? !articleTitle.equals(that.articleTitle) : that.articleTitle != null) return false;
if (articleID != null ? !articleID.equals(that.articleID) : that.articleID != null) return false;
if (parentID != null ? !parentID.equals(that.parentID) : that.parentID != null) return false;
if (body != null ? !body.equals(that.body) : that.body != null) return false;
if (competingInterestStatement != null ? !competingInterestStatement.equals(that.competingInterestStatement) : that.competingInterestStatement != null)
return false;
if (creatorID != null ? !creatorID.equals(that.creatorID) : that.creatorID != null) return false;
if (creatorDisplayName != null ? !creatorDisplayName.equals(that.creatorDisplayName) : that.creatorDisplayName != null)
return false;
if (creatorFormattedName != null ? !creatorFormattedName.equals(that.creatorFormattedName) : that.creatorFormattedName != null)
return false;
if (title != null ? !title.equals(that.title) : that.title != null) return false;
if (type != that.type) return false;
return true;
}
@Override
public int hashCode() {
int result = title != null ? title.hashCode() : 0;
result = 31 * result + (body != null ? body.hashCode() : 0);
result = 31 * result + (competingInterestStatement != null ? competingInterestStatement.hashCode() : 0);
result = 31 * result + (annotationUri != null ? annotationUri.hashCode() : 0);
result = 31 * result + (ID != null ? ID.hashCode() : 0);
result = 31 * result + (creatorID != null ? creatorID.hashCode() : 0);
result = 31 * result + (creatorDisplayName != null ? creatorDisplayName.hashCode() : 0);
result = 31 * result + (creatorFormattedName != null ? creatorFormattedName.hashCode() : 0);
result = 31 * result + (articleID != null ? articleID.hashCode() : 0);
result = 31 * result + (parentID != null ? parentID.hashCode() : 0);
result = 31 * result + (articleDoi != null ? articleDoi.hashCode() : 0);
result = 31 * result + (articleTitle != null ? articleTitle.hashCode() : 0);
result = 31 * result + (type != null ? type.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "AnnotationView{" +
"title='" + title + '\'' +
", body='" + body + '\'' +
", competingInterestStatement='" + competingInterestStatement + '\'' +
", annotationUri='" + annotationUri + '\'' +
", creatorID=" + creatorID +
", creatorDisplayName='" + creatorDisplayName + '\'' +
", articleID=" + articleID +
", parentID=" + parentID +
", articleDoi='" + articleDoi + '\'' +
", articleTitle='" + articleTitle + '\'' +
", type=" + type +
'}';
}
public String getTitle() {
return title;
}
private String createFormattedName(UserProfile up) {
StringBuilder name = new StringBuilder();
if (up.getGivenNames() != null && !up.getGivenNames().equals("")) {
name.append(up.getGivenNames());
}
if (up.getSurname() != null && !up.getSurname().equals("")) {
if (name.length() > 0) {
name.append(' ');
}
name.append(up.getSurname());
}
if (name.length() == 0)
name.append(up.getDisplayName());
return name.toString();
}
public String getBody() {
return body;
}
public String getTruncatedBody() {
return this.truncatedBody;
}
public String getBodyWithUrlLinkingNoPTags() {
return bodyWithUrlLinkingNoPTags;
}
public String getTruncatedBodyWithUrlLinkingNoPTags() {
return truncatedBodyWithUrlLinkingNoPTags;
}
public String getCompetingInterestStatement() {
return competingInterestStatement;
}
public String getTruncatedCompetingInterestStatement() {
return truncatedCompetingInterestStatement;
}
public AnnotationView[] getReplies() {
return replies.clone();
}
public String getAnnotationUri() {
return annotationUri;
}
public Long getID() {
return ID;
}
public Long getCreatorID() {
return creatorID;
}
public String getCreatorDisplayName() {
return creatorDisplayName;
}
public String getCreatorFormattedName() {
return creatorFormattedName;
}
public Long getArticleID() {
return articleID;
}
public Long getParentID() {
return parentID;
}
public Date getCreated() {
//Defensive copy
Calendar date = Calendar.getInstance();
date.setTime(created);
return date.getTime();
}
public String getCreatedFormatted() {
return createdFormatted;
}
public long getCreatedAsMillis() {
return created.getTime();
}
public AnnotationType getType() {
return type;
}
public String getArticleDoi() {
return articleDoi;
}
public String getArticleTitle() {
return articleTitle;
}
public String getOriginalBody() {
return originalBody;
}
public String getOriginalTitle() {
return originalTitle;
}
public Date getLastReplyDate() {
return lastReplyDate;
}
public int getTotalNumReplies() {
return totalNumReplies;
}
public String getBodyWithHighlightedText() {
return bodyWithHighlightedText;
}
}