/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2009 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.ui.internal.views.explorer;
import org.eclipse.core.runtime.Assert;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IEntity;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.IMark;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.INewsBin;
import org.rssowl.core.persist.INewsMark;
import org.rssowl.core.persist.ISearchMark;
import org.rssowl.core.util.DateUtils;
import org.rssowl.ui.internal.EntityGroup;
import org.rssowl.ui.internal.EntityGroupItem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
import java.util.List;
/**
* @author bpasero
*/
public class BookMarkGrouping {
/* Some Date Constants */
private static final long DAY = 24 * 60 * 60 * 1000;
private static final long WEEK = 7 * DAY;
/* Popularity Ratio */
private static final float VERY_POPULAR_RATIO = 0.8f;
private static final float POPULAR_RATIO = 0.5f;
private static final float FAIRLY_POPULAR_RATIO = 0.2f;
/* ID of Group Category */
static final String GROUP_CATEGORY_ID = "org.rssowl.ui.internal.views.explorer.BookMarkGrouping"; //$NON-NLS-1$
/** Supported Grouping Types */
public enum Type {
/** Grouping is Disabled */
NO_GROUPING,
/** Group by Last Visit Date */
GROUP_BY_LAST_VISIT,
/** Group by Popularity */
GROUP_BY_POPULARITY,
/** Group by Type */
GROUP_BY_TYPE,
/** Group by State */
GROUP_BY_STATE
}
/** Valid Groups */
public enum Group {
/** Group: Visited Today */
TODAY(Messages.BookMarkGrouping_TODAY),
/** Group: Visited Yesterday */
YESTERDAY(Messages.BookMarkGrouping_YESTERDAY),
/** Group: Visited Earlier this Week */
EARLIER_THIS_WEEK(Messages.BookMarkGrouping_EARLIER_WEEK),
/** Group: Visited Last Week */
LAST_WEEK(Messages.BookMarkGrouping_LAST_WEEK),
/** Group: Older */
OLDER(Messages.BookMarkGrouping_MORE_THAN_WEEK),
/** Group: Never Visited */
NEVER(Messages.BookMarkGrouping_NEVER),
/** Group: Very Popular */
VERY_POPULAR(Messages.BookMarkGrouping_VERY_POPULAR),
/** Group: Popular */
POPULAR(Messages.BookMarkGrouping_POPULAR),
/** Group: Fairly Popular */
FAIRLY_POPULAR(Messages.BookMarkGrouping_FAILRY_POPULAR),
/** Group: Unpopular */
UNPOPULAR(Messages.BookMarkGrouping_UNPOPULAR),
/** Group: Bins */
BIN(Messages.BookMarkGrouping_NEWS_BINS),
/** Group: Searches */
SEARCH(Messages.BookMarkGrouping_SAVED_SEARCHES),
/** Group: Bookmarks */
BOOKMARK(Messages.BookMarkGrouping_BOOKMARKS),
/** Group: Sticky */
STICKY(Messages.BookMarkGrouping_STICKY),
/** Group: New */
NEW(Messages.BookMarkGrouping_NEW),
/** Group: Unread */
UNREAD(Messages.BookMarkGrouping_UNREAD),
/** Group: Other */
OTHER(Messages.BookMarkGrouping_OTHER);
String fName;
Group(String name) {
fName = name;
}
String getName() {
return fName;
}
}
/* Current Type of Grouping */
private Type fType = Type.NO_GROUPING;
/* Get the Type of grouping as defined in the Type Enum */
Type getType() {
return fType;
}
/**
* Set the Type of grouping as defined in the Type Enum
*
* @param type The new Grouping Type.
*/
public void setType(Type type) {
fType = type;
}
/**
* Group the Input based on the selected Type
*
* @param input The Input to Group.
* @return The Input grouped in an array of EntityGroup, as specified by the
* Type of Group.
*/
public EntityGroup[] group(List<? extends IEntity> input) {
Assert.isTrue(fType != Type.NO_GROUPING, "Grouping is not enabled!"); //$NON-NLS-1$
/* Group by Last Visit Date */
if (Type.GROUP_BY_LAST_VISIT == fType)
return createLastVisitDateGroups(input);
/* Group by Popularity */
else if (Type.GROUP_BY_POPULARITY == fType)
return createPopularityGroups(input);
/* Group by State */
else if (Type.GROUP_BY_STATE == fType)
return createStateGroups(input);
/* Group by Type */
else if (Type.GROUP_BY_TYPE == fType)
return createTypeGroups(input);
/* Should not happen */
return new EntityGroup[0];
}
/* Create the Group for the Type GROUP_BY_LAST_VISIT */
private EntityGroup[] createLastVisitDateGroups(List<? extends IEntity> input) {
/* Today */
Calendar today = DateUtils.getToday();
long todayMillis = today.getTimeInMillis();
/* Yesterday */
Date yesterday = new Date(todayMillis - DAY);
/* Earlier this Week */
today.set(Calendar.DAY_OF_WEEK, today.getFirstDayOfWeek());
Date earlierThisWeek = today.getTime();
/* Last Week */
Date lastWeek = new Date(earlierThisWeek.getTime() - WEEK);
/* Build Groups */
EntityGroup gToday = new EntityGroup(Group.TODAY.ordinal(), GROUP_CATEGORY_ID, Group.TODAY.getName());
EntityGroup gYesterday = new EntityGroup(Group.YESTERDAY.ordinal(), GROUP_CATEGORY_ID, Group.YESTERDAY.getName());
EntityGroup gEarlierThisWeek = new EntityGroup(Group.EARLIER_THIS_WEEK.ordinal(), GROUP_CATEGORY_ID, Group.EARLIER_THIS_WEEK.getName());
EntityGroup gLastWeek = new EntityGroup(Group.LAST_WEEK.ordinal(), GROUP_CATEGORY_ID, Group.LAST_WEEK.getName());
EntityGroup gOlder = new EntityGroup(Group.OLDER.ordinal(), GROUP_CATEGORY_ID, Group.OLDER.getName());
EntityGroup gNever = new EntityGroup(Group.NEVER.ordinal(), GROUP_CATEGORY_ID, Group.NEVER.getName());
/* Group Input */
for (Object object : input) {
if (object instanceof IMark) {
IMark mark = (IMark) object;
Date lastVisitDate = mark.getLastVisitDate();
/* Feed was never visited */
if (lastVisitDate == null)
new EntityGroupItem(gNever, mark);
/* Feed was visited Today */
else if (lastVisitDate.getTime() >= todayMillis)
new EntityGroupItem(gToday, mark);
/* Feed was visited Yesterday */
else if (lastVisitDate.compareTo(yesterday) >= 0)
new EntityGroupItem(gYesterday, mark);
/* Feed was visited Earlier this Week */
else if (lastVisitDate.compareTo(earlierThisWeek) >= 0)
new EntityGroupItem(gEarlierThisWeek, mark);
/* Feed was visited Last Week */
else if (lastVisitDate.compareTo(lastWeek) >= 0)
new EntityGroupItem(gLastWeek, mark);
/* Feed was visited more than a Week ago */
else
new EntityGroupItem(gOlder, mark);
}
}
/* Select all that are non empty */
return maskEmpty(new ArrayList<EntityGroup>(Arrays.asList(new EntityGroup[] { gNever, gToday, gYesterday, gEarlierThisWeek, gLastWeek, gOlder })));
}
/* Create the Group for the Type GROUP_BY_POPULARITY */
private EntityGroup[] createPopularityGroups(List<? extends IEntity> input) {
/* Build Groups */
EntityGroup gVeryPopular = new EntityGroup(Group.VERY_POPULAR.ordinal(), GROUP_CATEGORY_ID, Group.VERY_POPULAR.getName());
EntityGroup gPopular = new EntityGroup(Group.POPULAR.ordinal(), GROUP_CATEGORY_ID, Group.POPULAR.getName());
EntityGroup gFairlyPopular = new EntityGroup(Group.FAIRLY_POPULAR.ordinal(), GROUP_CATEGORY_ID, Group.FAIRLY_POPULAR.getName());
EntityGroup gUnpopular = new EntityGroup(Group.UNPOPULAR.ordinal(), GROUP_CATEGORY_ID, Group.UNPOPULAR.getName());
/* Get the Max. Popularity */
float maxPopularity = -1;
for (Object object : input) {
if (object instanceof IMark) {
IMark bookmark = (IMark) object;
maxPopularity = Math.max(maxPopularity, bookmark.getPopularity());
}
}
/* Group Input */
for (Object object : input) {
if (object instanceof IMark) {
IMark bookmark = (IMark) object;
float popularity = bookmark.getPopularity();
float ratio = maxPopularity > 0 ? popularity / maxPopularity : 0;
/* Feed is Very Popular */
if (ratio >= VERY_POPULAR_RATIO)
new EntityGroupItem(gVeryPopular, bookmark);
/* Feed is Popular */
else if (ratio >= POPULAR_RATIO)
new EntityGroupItem(gPopular, bookmark);
/* Feed is Fairly Popular */
else if (ratio >= FAIRLY_POPULAR_RATIO)
new EntityGroupItem(gFairlyPopular, bookmark);
/* Feed is Unpopular */
else
new EntityGroupItem(gUnpopular, bookmark);
}
}
/* Select all that are non empty */
return maskEmpty(new ArrayList<EntityGroup>(Arrays.asList(new EntityGroup[] { gVeryPopular, gPopular, gFairlyPopular, gUnpopular })));
}
/* Create the Group for the Type GROUP_BY_TYPE */
private EntityGroup[] createTypeGroups(List<? extends IEntity> input) {
/* Build Groups */
EntityGroup gBins = new EntityGroup(Group.BIN.ordinal(), GROUP_CATEGORY_ID, Group.BIN.getName());
EntityGroup gSearches = new EntityGroup(Group.SEARCH.ordinal(), GROUP_CATEGORY_ID, Group.SEARCH.getName());
EntityGroup gBookmarks = new EntityGroup(Group.BOOKMARK.ordinal(), GROUP_CATEGORY_ID, Group.BOOKMARK.getName());
/* Group Input */
for (Object object : input) {
/* Bookmark */
if (object instanceof IBookMark)
new EntityGroupItem(gBookmarks, (IEntity) object);
/* Bin */
else if (object instanceof INewsBin)
new EntityGroupItem(gBins, (IEntity) object);
/* Saved Search */
else if (object instanceof ISearchMark)
new EntityGroupItem(gSearches, (IEntity) object);
}
/* Select all that are non empty */
return maskEmpty(new ArrayList<EntityGroup>(Arrays.asList(new EntityGroup[] { gBins, gSearches, gBookmarks })));
}
/* Create the Group for the Type GROUP_BY_STATE */
private EntityGroup[] createStateGroups(List<? extends IEntity> input) {
/* Build Groups */
EntityGroup gSticky = new EntityGroup(Group.STICKY.ordinal(), GROUP_CATEGORY_ID, Group.STICKY.getName());
EntityGroup gNew = new EntityGroup(Group.NEW.ordinal(), GROUP_CATEGORY_ID, Group.NEW.getName());
EntityGroup gUnread = new EntityGroup(Group.UNREAD.ordinal(), GROUP_CATEGORY_ID, Group.UNREAD.getName());
EntityGroup gOther = new EntityGroup(Group.OTHER.ordinal(), GROUP_CATEGORY_ID, Group.OTHER.getName());
/* Group Input */
for (Object object : input) {
if (object instanceof INewsMark) {
INewsMark mark = (INewsMark) object;
/* Early exclude saved searches (buggy) */
if (mark instanceof ISearchMark)
new EntityGroupItem(gOther, mark);
/* Contains Sticky */
else if (mark instanceof IBookMark && ((IBookMark) mark).getStickyNewsCount() > 0)
new EntityGroupItem(gSticky, mark);
/* Contains New */
else if (mark.getNewsCount(EnumSet.of(INews.State.NEW)) > 0)
new EntityGroupItem(gNew, mark);
/* Contains Unread */
else if (mark.getNewsCount(EnumSet.of(INews.State.NEW, INews.State.UNREAD, INews.State.UPDATED)) > 0)
new EntityGroupItem(gUnread, mark);
/* Other */
else
new EntityGroupItem(gOther, mark);
}
}
/* Select all that are non empty */
return maskEmpty(new ArrayList<EntityGroup>(Arrays.asList(new EntityGroup[] { gSticky, gNew, gUnread, gOther })));
}
private EntityGroup[] maskEmpty(List<EntityGroup> items) {
List<EntityGroup> maskedItems = new ArrayList<EntityGroup>();
for (EntityGroup item : items) {
if (item.size() > 0)
maskedItems.add(item);
}
return maskedItems.toArray(new EntityGroup[maskedItems.size()]);
}
boolean needsRefresh(Class<? extends IEntity> entityClass) {
/* In case the Grouping is not active at all */
if (fType == Type.NO_GROUPING)
return false;
/* Early handle News */
if (entityClass.equals(INews.class))
return fType == Type.GROUP_BY_STATE;
/* Folder Event (e.g. Mark deleted) */
if (entityClass.equals(IFolder.class))
return true;
/* Bookmark Event */
if (IBookMark.class.isAssignableFrom(entityClass))
return true;
/* Searchmark Event */
if (ISearchMark.class.isAssignableFrom(entityClass))
return true;
/* News Bin Event */
if (INewsBin.class.isAssignableFrom(entityClass))
return true;
return false;
}
boolean isActive() {
return fType != Type.NO_GROUPING;
}
}