/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2011 RSSOwl Development Team **
** http://www.rssowl.org/ **
** **
** All rights reserved **
** **
** This program and the accompanying materials are made available under **
** the terms of the Eclipse Public License v1.0 which accompanies this **
** distribution, and is available at: **
** http://www.rssowl.org/legal/epl-v10.html **
** **
** A copy is found in the file epl-v10.html and important notices to the **
** license from the team is found in the textfile LICENSE.txt distributed **
** in this package. **
** **
** This copyright notice MUST APPEAR in all copies of the file! **
** **
** Contributors: **
** RSSOwl Development Team - initial API and implementation **
** **
** ********************************************************************** */
package org.rssowl.core.util;
import org.rssowl.core.internal.newsaction.DeleteNewsAction;
import org.rssowl.core.internal.newsaction.LabelNewsAction;
import org.rssowl.core.internal.newsaction.MarkReadNewsAction;
import org.rssowl.core.internal.newsaction.MarkStickyNewsAction;
import org.rssowl.core.internal.newsaction.MarkUnreadNewsAction;
import org.rssowl.core.persist.IFilterAction;
import org.rssowl.core.persist.ILabel;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.ISearchFilter;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.event.NewsEvent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A {@link SyncItem} is used to synchronize changes to {@link INews} with an
* online service like Google Reader.
*
* @author bpasero
*/
public class SyncItem implements Serializable {
/* Serial Version UID to support class changes */
private static final long serialVersionUID = 4093540431879243015L;
/* Set of unread states */
private static final Set<INews.State> UNREAD_STATES = EnumSet.of(INews.State.NEW, INews.State.UNREAD, INews.State.UPDATED);
private final String fId;
private final String fStreamId;
private boolean fMarkedRead;
private boolean fMarkedUnread;
private boolean fStarred;
private boolean fUnStarred;
private List<String> fAddedLabels;
private List<String> fRemovedLabels;
/**
* Creates a {@link SyncItem} out of a {@link NewsEvent}.
*
* @param event the {@link NewsEvent} to create a {@link SyncItem} from.
* @return the {@link SyncItem} from the {@link NewsEvent}.
*/
public static SyncItem toSyncItem(NewsEvent event) {
boolean requiresSync = false;
INews item = event.getEntity();
SyncItem syncItem = toSyncItem(item);
/* State Change */
INews.State oldState = event.getOldNews().getState();
INews.State newState = item.getState();
if (oldState != newState) {
/* Marked Read */
if (newState == INews.State.READ && UNREAD_STATES.contains(oldState)) {
syncItem.setMarkedRead();
requiresSync = true;
}
/* Marked Unread */
else if (newState == INews.State.UNREAD && oldState == INews.State.READ) {
syncItem.setMarkedUnread();
requiresSync = true;
}
/* Delete */
else if (newState == INews.State.HIDDEN || newState == INews.State.DELETED) {
/* Mark Read if Unread and remove Star if Flagged */
if (UNREAD_STATES.contains(oldState)) {
syncItem.setMarkedRead();
if (item.isFlagged())
syncItem.setUnStarred();
requiresSync = true;
}
/* Remove Star if Flagged */
else if (oldState == INews.State.READ && item.isFlagged()) {
syncItem.setUnStarred();
requiresSync = true;
}
}
/* Restored */
else if (!event.getOldNews().isVisible() && event.getEntity().isVisible()) {
/* Restore Unread if previously unread */
if (UNREAD_STATES.contains(newState)) {
syncItem.setMarkedUnread();
requiresSync = true;
}
/* Restore Star if previously starred */
if (event.getEntity().isFlagged()) {
syncItem.setStarred();
requiresSync = true;
}
}
}
/* Sticky Change */
boolean oldSticky = event.getOldNews().isFlagged();
boolean newSticky = item.isFlagged();
if (oldSticky != newSticky) {
if (oldSticky)
syncItem.setUnStarred();
else
syncItem.setStarred();
requiresSync = true;
}
/* Label Change */
Set<ILabel> oldLabels = event.getOldNews().getLabels();
Set<ILabel> newLabels = item.getLabels();
if (!Arrays.equals(oldLabels.toArray(), newLabels.toArray())) {
Set<String> oldLabelNames = new HashSet<String>(oldLabels.size());
for (ILabel oldLabel : oldLabels) {
oldLabelNames.add(oldLabel.getName());
}
for (ILabel newLabel : newLabels) {
if (!oldLabelNames.remove(newLabel.getName())) {
syncItem.addLabel(newLabel.getName());
requiresSync = true;
}
}
for (String oldLabelName : oldLabelNames) {
syncItem.removeLabel(oldLabelName);
requiresSync = true;
}
}
return requiresSync ? syncItem : null;
}
/**
* Creates a {@link SyncItem} out of a {@link ISearchFilter}.
*
* @param filter the {@link ISearchFilter} to create a {@link SyncItem} from.
* @param item the {@link INews} the {@link ISearchFilter} is operating on.
* @return the {@link SyncItem} from the {@link ISearchFilter}.
*/
public static SyncItem toSyncItem(ISearchFilter filter, INews item) {
boolean requiresSync = false;
SyncItem syncItem = toSyncItem(item);
List<IFilterAction> actions = filter.getActions();
for (IFilterAction action : actions) {
String actionId = action.getActionId();
/* State Change (Mark Read) */
if (MarkReadNewsAction.ID.equals(actionId)) {
syncItem.setMarkedRead();
requiresSync = true;
}
/* State Change (Mark Unread) */
if (MarkUnreadNewsAction.ID.equals(actionId)) {
syncItem.setMarkedUnread();
requiresSync = true;
}
/* Delete (Sync like Mark Read) */
if (DeleteNewsAction.ID.equals(actionId)) {
syncItem.setMarkedRead();
requiresSync = true;
}
/* Sticky Change */
if (MarkStickyNewsAction.ID.equals(actionId)) {
syncItem.setStarred();
requiresSync = true;
}
/* Label Change */
if (LabelNewsAction.ID.equals(actionId)) {
Object data = action.getData();
if (data != null && data instanceof Long) {
Long labelId = (Long) data;
ILabel label = DynamicDAO.load(ILabel.class, labelId);
if (label != null) {
syncItem.addLabel(label.getName());
requiresSync = true;
}
}
}
}
return requiresSync ? syncItem : null;
}
/**
* @param news the {@link INews} to create a {@link SyncItem} from.
* @return the {@link SyncItem} from the given {@link INews}.
*/
public static SyncItem toSyncItem(INews news) {
String itemId = news.getGuid().getValue();
String streamId = news.getInReplyTo();
return new SyncItem(itemId, streamId);
}
SyncItem(String id, String streamId) {
fId = id;
fStreamId = streamId;
}
/**
* @return the identifier of this item.
*/
public String getId() {
return fId;
}
/**
* @return the identifier of the stream this item belongs to.
*/
public String getStreamId() {
return fStreamId;
}
/**
* Marks the item as read.
*/
public void setMarkedRead() {
fMarkedRead = true;
fMarkedUnread = false;
}
/**
* Marks the item as unread.
*/
public void setMarkedUnread() {
fMarkedUnread = true;
fMarkedRead = false;
}
/**
* Marks the item as starred.
*/
public void setStarred() {
fStarred = true;
fUnStarred = false;
}
/**
* Marks the item as un-starred.
*/
public void setUnStarred() {
fUnStarred = true;
fStarred = false;
}
/**
* @param label the label to add to the item.
*/
public void addLabel(String label) {
if (fAddedLabels == null)
fAddedLabels = new ArrayList<String>(3);
if (!fAddedLabels.contains(label))
fAddedLabels.add(label);
if (fRemovedLabels != null)
fRemovedLabels.remove(label);
}
/**
* @param label the label to remove from the item.
*/
public void removeLabel(String label) {
if (fRemovedLabels == null)
fRemovedLabels = new ArrayList<String>(1);
if (!fRemovedLabels.contains(label))
fRemovedLabels.add(label);
if (fAddedLabels != null)
fAddedLabels.remove(label);
}
/**
* @return <code>true</code> if the item is marked read.
*/
public boolean isMarkedRead() {
return fMarkedRead;
}
/**
* @return <code>true</code> if the item is marked unread.
*/
public boolean isMarkedUnread() {
return fMarkedUnread;
}
/**
* @return <code>true</code> if the item is starred.
*/
public boolean isStarred() {
return fStarred;
}
/**
* @return <code>true</code> if the item is unstarred.
*/
public boolean isUnStarred() {
return fUnStarred;
}
/**
* @return the {@link List} of labels to add.
*/
public List<String> getAddedLabels() {
return fAddedLabels != null ? fAddedLabels : Collections.<String> emptyList();
}
/**
* @return the {@link List} of labels to be removed.
*/
public List<String> getRemovedLabels() {
return fRemovedLabels != null ? fRemovedLabels : Collections.<String> emptyList();
}
/**
* Takes the properties from the provided {@link SyncItem} and updates them in
* this item.
*
* @param item the other {@link SyncItem} to merge into this item.
*/
public void merge(SyncItem item) {
/* Mark Read */
if (item.fMarkedRead)
setMarkedRead();
/* Mark Unread */
if (item.fMarkedUnread)
setMarkedUnread();
/* Set Starred */
if (item.fStarred)
setStarred();
/* Set Unstarred */
if (item.fUnStarred)
setUnStarred();
/* Add Labels */
if (item.fAddedLabels != null) {
for (String label : item.fAddedLabels) {
addLabel(label);
}
}
/* Remove Labels */
if (item.fRemovedLabels != null) {
for (String label : item.fRemovedLabels) {
removeLabel(label);
}
}
}
/**
* This method will apply the properties of this {@link SyncItem} to the given
* {@link INews}.
*
* @param news the {@link INews} to apply all properties of this
* {@link SyncItem} to.
*/
public void applyTo(INews news) {
/* Mark Read */
if (isMarkedRead()) {
news.setProperty(SyncUtils.GOOGLE_MARKED_READ, true);
news.removeProperty(SyncUtils.GOOGLE_MARKED_UNREAD);
}
/* Mark Unread */
if (isMarkedUnread()) {
news.setProperty(SyncUtils.GOOGLE_MARKED_UNREAD, true);
news.removeProperty(SyncUtils.GOOGLE_MARKED_READ);
}
/* Set Starred */
if (isStarred())
news.setFlagged(true);
/* Set Unstarred */
if (isUnStarred())
news.setFlagged(false);
/* Update Labels */
if (fAddedLabels != null || fRemovedLabels != null) {
Object labelsObj = news.getProperty(SyncUtils.GOOGLE_LABELS);
if (labelsObj == null)
labelsObj = new String[0];
if (labelsObj instanceof String[]) {
Set<String> labels = new HashSet<String>(Arrays.asList((String[]) labelsObj));
if (fAddedLabels != null)
labels.addAll(fAddedLabels);
if (fRemovedLabels != null)
labels.removeAll(fRemovedLabels);
news.setProperty(SyncUtils.GOOGLE_LABELS, labels.toArray(new String[labels.size()]));
}
}
}
/**
* @param item the other {@link SyncItem} to check for equivalence.
* @return <code>true</code> in case the provided {@link SyncItem} has the
* identical properties as this one and <code>false</code> otherwise.
*/
public boolean isEquivalent(SyncItem item) {
if (fMarkedRead != item.fMarkedRead)
return false;
if (fMarkedUnread != item.fMarkedUnread)
return false;
if (fStarred != item.fStarred)
return false;
if (fUnStarred != item.fUnStarred)
return false;
if (!isLabelsEquivalent(fAddedLabels, item.fAddedLabels))
return false;
if (!isLabelsEquivalent(fRemovedLabels, item.fRemovedLabels))
return false;
return true;
}
@SuppressWarnings("null")
private boolean isLabelsEquivalent(List<String> labelsA, List<String> labelsB) {
if (labelsA == null && labelsB != null)
return false;
if (labelsA != null && labelsB == null)
return false;
if (labelsA == null && labelsB == null)
return true;
return Arrays.equals(labelsA.toArray(), labelsB.toArray());
}
/*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((fId == null) ? 0 : fId.hashCode());
return result;
}
/*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
SyncItem other = (SyncItem) obj;
if (fId == null) {
if (other.fId != null)
return false;
} else if (!fId.equals(other.fId))
return false;
return true;
}
}