/* ********************************************************************** **
** 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;
import org.eclipse.core.runtime.IProgressMonitor;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.persist.LongArrayList;
import org.rssowl.core.internal.persist.Mark;
import org.rssowl.core.internal.persist.NewsContainer;
import org.rssowl.core.internal.persist.SearchMark;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.IFolderChild;
import org.rssowl.core.persist.IModelFactory;
import org.rssowl.core.persist.INews;
import org.rssowl.core.persist.INews.State;
import org.rssowl.core.persist.INewsBin;
import org.rssowl.core.persist.INewsMark;
import org.rssowl.core.persist.ISearchCondition;
import org.rssowl.core.persist.ISearchField;
import org.rssowl.core.persist.ISearchMark;
import org.rssowl.core.persist.SearchSpecifier;
import org.rssowl.core.persist.dao.DynamicDAO;
import org.rssowl.core.persist.dao.INewsDAO;
import org.rssowl.core.persist.reference.FeedLinkReference;
import org.rssowl.core.persist.reference.ModelReference;
import org.rssowl.core.persist.reference.NewsReference;
import org.rssowl.core.persist.service.IModelSearch;
import org.rssowl.core.persist.service.PersistenceException;
import org.rssowl.core.util.SearchHit;
import org.rssowl.ui.internal.editors.feed.NewsFilter;
import org.rssowl.ui.internal.editors.feed.NewsFilter.Type;
import org.rssowl.ui.internal.util.ModelUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* An internal subclass of {@link Mark} that implements {@link INewsMark} to
* provide the news of all bookmarks, bins and saved searches inside a folder.
* The {@link FolderNewsMark} is created dynamically whenever a folder is opened
* in the feedview and is never persisted to the DB.
*
* @author bpasero
*/
@SuppressWarnings("restriction")
public class FolderNewsMark extends Mark implements INewsMark {
private final Set<Long> fNewsContainer;
private final IFolder fFolder;
private final INewsDAO fNewsDao;
private final IModelFactory fFactory;
private final IModelSearch fSearch;
/**
* Internal implementation of the <code>ModelReference</code> for the internal
* Type <code>FolderNewsMark</code>.
*
* @author bpasero
*/
public static final class FolderNewsMarkReference extends ModelReference {
/**
* @param id
*/
public FolderNewsMarkReference(long id) {
super(id, FolderNewsMark.class);
}
@Override
public IFolder resolve() throws PersistenceException {
throw new UnsupportedOperationException();
}
}
/**
* @param folder the {@link IFolder} that makes the contents of this
* {@link INewsMark}.
*/
public FolderNewsMark(IFolder folder) {
super(folder.getId(), folder, folder.getName());
fFolder = folder;
fNewsContainer = new HashSet<Long>();
fNewsDao = DynamicDAO.getDAO(INewsDAO.class);
fFactory = Owl.getModelFactory();
fSearch = Owl.getPersistenceService().getModelSearch();
}
/**
* @param news the {@link List} of {@link INews} to add into this news mark.
*/
public void add(Collection<INews> news) {
synchronized (this) {
for (INews item : news) {
if (item != null && item.getId() != null)
fNewsContainer.add(item.getId());
}
}
}
private void addAll(long[] elements) {
synchronized (this) {
for (long element : elements) {
if (element > 0)
fNewsContainer.add(element);
}
}
}
private void addAll(List<SearchHit<NewsReference>> results) {
synchronized (this) {
for (SearchHit<NewsReference> result : results) {
fNewsContainer.add(result.getResult().getId());
}
}
}
/**
* @param news the {@link List} of {@link INews} to remove from this news
* mark.
*/
public void remove(Collection<INews> news) {
synchronized (this) {
for (INews item : news) {
if (item != null && item.getId() != null) {
fNewsContainer.remove(item.getId());
}
}
}
}
/*
* @see org.rssowl.core.persist.INewsMark#getNewsRefs()
*/
public List<NewsReference> getNewsRefs() {
synchronized (this) {
List<NewsReference> news = new ArrayList<NewsReference>(fNewsContainer.size());
for (Long id : fNewsContainer) {
news.add(new NewsReference(id));
}
return news;
}
}
/*
* @see org.rssowl.core.persist.INewsMark#getNewsRefs(java.util.Set)
*/
public List<NewsReference> getNewsRefs(Set<State> states) {
return getNewsRefs();
}
/*
* @see org.rssowl.core.persist.INewsMark#getNewsCount(java.util.Set)
*/
public int getNewsCount(Set<State> states) {
synchronized (this) {
return fNewsContainer.size();
}
}
/*
* @see org.rssowl.core.persist.INewsMark#containsNews(org.rssowl.core.persist.INews)
*/
public boolean containsNews(INews news) {
return news.getId() != null && containsNews(news.getId());
}
/**
* @param newsId the identifier of the news.
* @return <code>true</code> if this news mark contains the news and
* <code>false</code> otherwise.
*/
public boolean containsNews(long newsId) {
synchronized (this) {
return fNewsContainer.contains(newsId);
}
}
/*
* @see org.rssowl.core.persist.INewsMark#getNews()
*/
public List<INews> getNews() {
synchronized (this) {
List<INews> news = new ArrayList<INews>(fNewsContainer.size());
for (Long id : fNewsContainer) {
INews item = fNewsDao.load(id);
if (item != null)
news.add(item);
}
return news;
}
}
/*
* @see org.rssowl.core.persist.INewsMark#getNews(java.util.Set)
*/
public List<INews> getNews(Set<State> states) {
return getNews();
}
/*
* @see org.rssowl.core.persist.INewsMark#isGetNewsRefsEfficient()
*/
public boolean isGetNewsRefsEfficient() {
return true;
}
/*
* @see org.rssowl.core.persist.IEntity#toReference()
*/
public ModelReference toReference() {
return new FolderNewsMarkReference(getId());
}
/**
* @return the {@link IFolder} that serves as input to this {@link INewsMark}.
*/
public IFolder getFolder() {
return fFolder;
}
/*
* @see org.rssowl.core.internal.persist.AbstractEntity#setProperty(java.lang.String, java.io.Serializable)
*/
@Override
public synchronized void setProperty(String key, Serializable value) {
fFolder.setProperty(key, value);
}
/*
* @see org.rssowl.core.internal.persist.Mark#getName()
*/
@Override
public synchronized String getName() {
return fFolder.getName();
}
/*
* @see org.rssowl.core.internal.persist.AbstractEntity#getProperties()
*/
@Override
public synchronized Map<String, Serializable> getProperties() {
return fFolder.getProperties();
}
/*
* @see org.rssowl.core.internal.persist.AbstractEntity#getProperty(java.lang.String)
*/
@Override
public synchronized Object getProperty(String key) {
return fFolder.getProperty(key);
}
/*
* @see org.rssowl.core.internal.persist.Mark#getParent()
*/
/*
* @see org.rssowl.core.internal.persist.Mark#getParent()
*/
@Override
public synchronized IFolder getParent() {
return fFolder.getParent();
}
/**
* @param mark the {@link INewsMark} to search in this folder.
* @return <code>true</code> if this folder contains the given
* {@link INewsMark} and <code>false</code> otherwise.
*/
public boolean contains(INewsMark mark) {
return contains(fFolder, mark);
}
private boolean contains(IFolder folder, INewsMark mark) {
List<IFolderChild> children = folder.getChildren();
for (IFolderChild child : children) {
if (child instanceof IFolder && contains((IFolder) child, mark))
return true;
if (child.equals(mark))
return true;
}
return false;
}
/**
* @param searchMark the search to find out if its contained in this folder.
* @return <code>true</code> if the given Search is contained in the
* {@link IFolder} of this news mark and <code>false</code> otherwise.
*/
public boolean isRelatedTo(ISearchMark searchMark) {
return isRelatedTo(fFolder, searchMark);
}
private boolean isRelatedTo(IFolder folder, ISearchMark searchMark) {
List<IFolderChild> children = folder.getChildren();
for (IFolderChild child : children) {
/* Check contained in Folder */
if (child instanceof IFolder && isRelatedTo((IFolder) child, searchMark))
return true;
/* Check identical to Child */
else if (child.equals(searchMark))
return true;
}
return false;
}
/**
* @param news the news to check if its related to this news mark.
* @return <code>true</code> if the given News belongs to any
* {@link IBookMark} or {@link INewsBin} of the given {@link IFolder}.
*/
public boolean isRelatedTo(INews news) {
/* Check News Container first */
if (containsNews(news))
return true;
/* Then check by Feedlink for new news yet unknown to the news mark */
FeedLinkReference feedRef = news.getFeedReference();
return isRelatedTo(fFolder, news, feedRef);
}
private boolean isRelatedTo(IFolder folder, INews news, FeedLinkReference ref) {
List<IFolderChild> children = folder.getChildren();
for (IFolderChild child : children) {
/* Check contained in Folder */
if (child instanceof IFolder && isRelatedTo((IFolder) child, news, ref))
return true;
/* News could be part of the Feed (but is no copy) */
else if (news.getParentId() == 0 && child instanceof IBookMark) {
IBookMark bookmark = (IBookMark) child;
if (bookmark.getFeedLinkReference().equals(ref))
return true;
}
/* News could be part of Bin (and is a copy) */
else if (news.getParentId() != 0 && child instanceof INewsBin) {
INewsBin bin = (INewsBin) child;
if (bin.getId() == news.getParentId())
return true;
}
}
return false;
}
/**
* @param type the filter type to scope the resulting news properly.
* @param monitor a monitor to react to cancellation.
*/
public void resolve(NewsFilter.Type type, IProgressMonitor monitor) {
doResolve(type, monitor, false);
}
/**
* @param monitor a monitor to react to cancellation.
* @return a {@link NewsContainer} providing information on included states
* and news of this {@link IFolder}.
*/
public NewsContainer resolveNewsContainer(IProgressMonitor monitor) {
return doResolve(NewsFilter.Type.SHOW_ALL, monitor, true);
}
private NewsContainer doResolve(NewsFilter.Type type, IProgressMonitor monitor, boolean resolveContainer) {
NewsContainer container = null;
if (resolveContainer)
container = new NewsContainer(Collections.<INews.State, Boolean> emptyMap());
/* Clear caches */
synchronized (this) {
fNewsContainer.clear();
}
/* Return eary on cancellation */
if (Controller.getDefault().isShuttingDown() || (monitor != null && monitor.isCanceled()))
return container;
/* Retrieve filter condition */
ISearchCondition filterCondition = ModelUtils.getConditionForFilter(type);
/* Resolve Bookmarks and Newsbins using Location search */
{
List<ISearchCondition> conditions = new ArrayList<ISearchCondition>(2);
ISearchField field = fFactory.createSearchField(INews.LOCATION, INews.class.getName());
ISearchCondition locationCondition = fFactory.createSearchCondition(field, SearchSpecifier.IS, ModelUtils.toPrimitive(Collections.singleton((IFolderChild) fFolder)));
conditions.add(locationCondition);
if (filterCondition != null)
conditions.add(filterCondition);
List<SearchHit<NewsReference>> results = fSearch.searchNews(conditions, conditions.size() == 2);
addAll(results);
/* Add to container if necessary */
if (resolveContainer)
addAll(container, results);
/* Return eary on cancellation */
if (Controller.getDefault().isShuttingDown() || (monitor != null && monitor.isCanceled()))
return container;
}
/* Resolve Searches */
{
Set<ISearchMark> searches = new HashSet<ISearchMark>();
findSearches(fFolder, searches);
for (ISearchMark search : searches) {
/* Inject the filter condition into the search if it is not an OR query or only 1 condition specified */
if ((type == Type.SHOW_RECENT || type == Type.SHOW_LAST_5_DAYS || type == Type.SHOW_STICKY || type == Type.SHOW_LABELED)) {
List<ISearchCondition> conditions = search.getSearchConditions();
List<SearchHit<NewsReference>> results = fSearch.searchNews(conditions, filterCondition, search.matchAllConditions());
addAll(results);
/* Add to container if necessary */
if (resolveContainer)
addAll(container, results);
}
/* Otherwise pick up the results from the search directly */
else {
LongArrayList[] newsIds = ((SearchMark) search).internalGetNewsContainer().internalGetNewsIds();
for (int i = 0; i < newsIds.length; i++) {
/* Ignore hidden/deleted and states that are filtered */
if (i == INews.State.HIDDEN.ordinal() || i == INews.State.DELETED.ordinal())
continue;
else if (type == Type.SHOW_NEW && i != INews.State.NEW.ordinal())
continue;
else if (type == Type.SHOW_UNREAD && i == INews.State.READ.ordinal())
continue;
addAll(newsIds[i].getElements());
}
/* Add to container if necessary */
if (resolveContainer)
addAll(container, newsIds);
}
/* Return eary on cancellation */
if (Controller.getDefault().isShuttingDown() || (monitor != null && monitor.isCanceled()))
return container;
}
}
return container;
}
private void addAll(NewsContainer container, LongArrayList[] newsIds) {
for (int i = 0; i < newsIds.length && i < container.internalGetNewsIds().length; i++) {
LongArrayList list = container.internalGetNewsIds()[i];
for (int j = 0; j < newsIds[i].size(); j++)
list.add(newsIds[i].get(j));
}
}
private void addAll(NewsContainer container, List<SearchHit<NewsReference>> results) {
LongArrayList[] newsIds = container.internalGetNewsIds();
for (SearchHit<NewsReference> result : results) {
INews.State state = (State) result.getData(INews.STATE);
if (newsIds.length > state.ordinal())
newsIds[state.ordinal()].add(result.getResult().getId());
}
}
private void findSearches(IFolder folder, Set<ISearchMark> searches) {
List<IFolderChild> children = folder.getChildren();
for (IFolderChild child : children) {
if (child instanceof IFolder)
findSearches((IFolder) child, searches);
else if (child instanceof ISearchMark)
searches.add((ISearchMark) child);
}
}
}