Package org.rssowl.ui.internal.editors.feed

Source Code of org.rssowl.ui.internal.editors.feed.NewsContentProvider

/*   **********************************************************************  **
**   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.editors.feed;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.rssowl.core.Owl;
import org.rssowl.core.internal.persist.LongArrayList;
import org.rssowl.core.internal.persist.SearchMark;
import org.rssowl.core.internal.persist.pref.DefaultPreferences;
import org.rssowl.core.persist.IBookMark;
import org.rssowl.core.persist.IEntity;
import org.rssowl.core.persist.IFolder;
import org.rssowl.core.persist.IFolderChild;
import org.rssowl.core.persist.IMark;
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.event.NewsAdapter;
import org.rssowl.core.persist.event.NewsEvent;
import org.rssowl.core.persist.event.NewsListener;
import org.rssowl.core.persist.event.SearchMarkAdapter;
import org.rssowl.core.persist.event.SearchMarkEvent;
import org.rssowl.core.persist.pref.IPreferenceScope;
import org.rssowl.core.persist.reference.BookMarkReference;
import org.rssowl.core.persist.reference.FeedLinkReference;
import org.rssowl.core.persist.reference.ModelReference;
import org.rssowl.core.persist.reference.NewsBinReference;
import org.rssowl.core.persist.reference.NewsReference;
import org.rssowl.core.persist.reference.SearchMarkReference;
import org.rssowl.core.persist.service.IModelSearch;
import org.rssowl.core.persist.service.PersistenceException;
import org.rssowl.core.util.CoreUtils;
import org.rssowl.core.util.DateUtils;
import org.rssowl.core.util.Pair;
import org.rssowl.core.util.SearchHit;
import org.rssowl.core.util.Triple;
import org.rssowl.ui.internal.Controller;
import org.rssowl.ui.internal.EntityGroup;
import org.rssowl.ui.internal.EntityGroupItem;
import org.rssowl.ui.internal.FolderNewsMark;
import org.rssowl.ui.internal.FolderNewsMark.FolderNewsMarkReference;
import org.rssowl.ui.internal.OwlUI;
import org.rssowl.ui.internal.editors.feed.NewsFilter.Type;
import org.rssowl.ui.internal.util.JobRunner;
import org.rssowl.ui.internal.util.ModelUtils;
import org.rssowl.ui.internal.util.UIBackgroundJob;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

/**
* @author bpasero
*/
@SuppressWarnings("restriction")
public class NewsContentProvider implements ITreeContentProvider {

  /* The maximum number of items returned from a FolderNewsMark */
  static final int MAX_FOLDER_ELEMENTS = 500;

  /* The maximum number of items that will get resolved from a FolderNewsMark */
  private static final int MAX_RESOLVED_FOLDER_ELEMENTS = 5000;

  /* The maximum number of items in a NewsMark before scoping the results as specified by the filter */
  private static final int NEWSMARK_SCOPE_SEARCH_LIMIT = 200;

  /* The maximum number of items in a Bookmark before scoping the results as specified by the filter */
  private static final int BOOKMARK_SCOPE_SEARCH_LIMIT = 500;

  /* System Property to override the limits above */
  private static final String NO_FOLDER_LIMIT_PROPERTY = "noFolderLimit"; //$NON-NLS-1$

  private final NewsBrowserViewer fBrowserViewer;
  private final NewsTableViewer fTableViewer;
  private final NewsGrouping fGrouping;
  private final NewsFilter fFilter;
  private NewsListener fNewsListener;
  private SearchMarkAdapter fSearchMarkListener;
  private INewsMark fInput;
  private final FeedView fFeedView;
  private final AtomicBoolean fDisposed = new AtomicBoolean(false);
  private final INewsDAO fNewsDao;
  private final IModelFactory fFactory;
  private final IModelSearch fSearch;
  private final boolean fNoFolderLimit;

  /* Cache displayed News */
  private final Map<Long, INews> fCachedNews;

  /* Enumeration of possible news event types */
  private static enum NewsEventType {
    PERSISTED, UPDATED, REMOVED, RESTORED
  }

  /**
   * @param tableViewer
   * @param browserViewer
   * @param feedView
   */
  public NewsContentProvider(NewsTableViewer tableViewer, NewsBrowserViewer browserViewer, FeedView feedView) {
    fTableViewer = tableViewer;
    fBrowserViewer = browserViewer;
    fFeedView = feedView;
    fGrouping = feedView.getGrouper();
    fFilter = feedView.getFilter();
    fCachedNews = new HashMap<Long, INews>();
    fNewsDao = DynamicDAO.getDAO(INewsDAO.class);
    fFactory = Owl.getModelFactory();
    fSearch = Owl.getPersistenceService().getModelSearch();
    fNoFolderLimit = System.getProperty(NO_FOLDER_LIMIT_PROPERTY) != null;
  }

  /*
   * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
   */
  public Object[] getElements(Object inputElement) {
    List<Object> elements = new ArrayList<Object>();

    /* Wrap into Object Array */
    if (!(inputElement instanceof Object[]))
      inputElement = new Object[] { inputElement };

    /* Foreach Object */
    Object[] objects = (Object[]) inputElement;
    for (Object object : objects) {

      /* This is a News */
      if (object instanceof INews && ((INews) object).isVisible()) {
        elements.add(object);
      }

      /* This is a NewsReference */
      else if (object instanceof NewsReference) {
        NewsReference newsRef = (NewsReference) object;
        INews news = obtainFromCache(newsRef);
        if (news != null)
          elements.add(news);
      }

      /* This is a FeedReference */
      else if (object instanceof FeedLinkReference) {
        synchronized (NewsContentProvider.this) {
          Collection<INews> news = fCachedNews.values();
          if (news != null) {
            if (fGrouping.getType() == NewsGrouping.Type.NO_GROUPING)
              elements.addAll(news);
            else
              elements.addAll(fGrouping.group(news));
          }
        }
      }

      /* This is a class that implements IMark */
      else if (object instanceof ModelReference) {
        Class<? extends IEntity> entityClass = ((ModelReference) object).getEntityClass();
        if (IMark.class.isAssignableFrom(entityClass) || IFolder.class.isAssignableFrom(entityClass)) { //Suppoer FolderNewsMark too
          synchronized (NewsContentProvider.this) {
            Collection<INews> news = fCachedNews.values();
            if (news != null) {
              if (fGrouping.getType() == NewsGrouping.Type.NO_GROUPING)
                elements.addAll(news);
              else
                elements.addAll(fGrouping.group(news));
            }
          }
        }
      }

      /* This is a EntityGroup */
      else if (object instanceof EntityGroup) {
        EntityGroup group = (EntityGroup) object;

        List<EntityGroupItem> items = group.getItems();
        for (EntityGroupItem item : items) {
          if (((INews) item.getEntity()).isVisible())
            elements.add(item.getEntity());
        }
      }
    }

    return elements.toArray();
  }

  /*
   * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
   */
  public Object[] getChildren(Object parentElement) {
    List<Object> children = new ArrayList<Object>();

    /* Handle EntityGroup */
    if (parentElement instanceof EntityGroup) {
      List<EntityGroupItem> items = ((EntityGroup) parentElement).getItems();
      for (EntityGroupItem item : items)
        children.add(item.getEntity());
    }

    return children.toArray();
  }

  /*
   * @see org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object)
   */
  public Object getParent(Object element) {

    /* Handle Grouping specially */
    if (fGrouping.isActive() && element instanceof INews) {
      Collection<EntityGroup> groups = fGrouping.group(Collections.singletonList((INews) element));
      if (groups.size() == 1)
        return groups.iterator().next();
    }

    return null;
  }

  /*
   * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
   */
  public boolean hasChildren(Object element) {
    return element instanceof EntityGroup;
  }

  /*
   * @see org.eclipse.jface.viewers.IContentProvider#dispose()
   */
  public synchronized void dispose() {
    fDisposed.set(true);
    unregisterListeners();
    fCachedNews.clear();
  }

  /*
   * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer,
   * java.lang.Object, java.lang.Object)
   */
  public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    /* Ignore - Input changes are handled via refreshCache(Object input) */
  }

  boolean isGroupingEnabled() {
    return fGrouping.getType() != NewsGrouping.Type.NO_GROUPING;
  }

  boolean isGroupingByFeed() {
    return fGrouping.getType() == NewsGrouping.Type.GROUP_BY_FEED;
  }

  boolean isGroupingByStickyness() {
    return fGrouping.getType() == NewsGrouping.Type.GROUP_BY_STICKY;
  }

  boolean isGroupingByLabel() {
    return fGrouping.getType() == NewsGrouping.Type.GROUP_BY_LABEL;
  }

  boolean isGroupingByState() {
    return fGrouping.getType() == NewsGrouping.Type.GROUP_BY_STATE;
  }

  synchronized void refreshCache(IProgressMonitor monitor, INewsMark input) throws PersistenceException {
    refreshCache(monitor, input, null);
  }

  @SuppressWarnings("unchecked")
  synchronized void refreshCache(IProgressMonitor monitor, INewsMark input, NewsComparator comparer) throws PersistenceException {

    /* If input is identical, keep the cache during this method to speed up lookup of already resolved items */
    Map<Long, INews> cacheCopy = null;
    if (fInput != null && fInput.equals(input))
      cacheCopy = new HashMap(fCachedNews);

    /* Update Input */
    fInput = input;

    /* Register Listeners if not yet done */
    if (fNewsListener == null)
      registerListeners();

    /* Clear old Data */
    fCachedNews.clear();

    /* Check if ContentProvider was already disposed or RSSOwl shutting down */
    if (canceled(monitor))
      return;

    /* Obtain the News */
    List<INews> resolvedNews = new ArrayList<INews>();

    /* Check if ContentProvider was already disposed or RSSOwl shutting down */
    if (canceled(monitor))
      return;

    /* Determine Set of News States based on the filter */
    Type filter = fFilter.getType();
    Set<State> states;
    if (filter == Type.SHOW_NEW)
      states = EnumSet.of(INews.State.NEW);
    else if (filter == Type.SHOW_UNREAD)
      states = EnumSet.of(INews.State.NEW, INews.State.UNREAD, INews.State.UPDATED);
    else
      states = INews.State.getVisible();

    /* Handle Folder, Newsbin and Saved Search or bookmark under certain circumstances */
    boolean needToFilter = true;
    if (input.isGetNewsRefsEfficient() || (input instanceof IBookMark && shouldResolveBookMarkWithSearch((IBookMark) input, filter))) {
      Triple<Boolean, Boolean, List<NewsReference>> result = getNewsRefsFromInput(input, fFilter, states, monitor);
      needToFilter = !result.getFirst();
      List<NewsReference> newsReferences = result.getThird();
      for (NewsReference newsRef : newsReferences) {

        /* Check if ContentProvider was already disposed or RSSOwl shutting down */
        if (canceled(monitor))
          return;

        INews resolvedNewsItem = null;

        /* Ask the local cache first */
        if (cacheCopy != null)
          resolvedNewsItem = cacheCopy.get(newsRef.getId());

        /* Otherwise resolve from DB */
        if (resolvedNewsItem == null)
          resolvedNewsItem = fNewsDao.load(newsRef.getId());

        /* Add if visible */
        if (resolvedNewsItem != null && resolvedNewsItem.isVisible())
          resolvedNews.add(resolvedNewsItem);

        /* News is null from a search, potential index issue - report it */
        else if (result.getSecond()) //TRUE if search was involved
          CoreUtils.reportIndexIssue();

        /* Never resolve more than MAX_RESOLVED_FOLDER_ELEMENTS for a folder */
        if (input instanceof FolderNewsMark && !fNoFolderLimit && resolvedNews.size() > MAX_RESOLVED_FOLDER_ELEMENTS)
          break;
      }

      /* Special treat folders and limit them by size */
      if (input instanceof FolderNewsMark)
        resolvedNews = limitFolderNewsMark(resolvedNews, comparer != null ? comparer : fFeedView.getComparator());
    }

    /* Resolve directly by state (check for news counts as optimization) */
    else if (shouldResolve(input, filter)) {
      resolvedNews.addAll(input.getNews(states));
    }

    /* Filter Elements as needed */
    if (needToFilter && isFilteredByOtherThanState())
      filterElements(resolvedNews);

    /* Check if ContentProvider was already disposed or RSSOwl shutting down */
    if (canceled(monitor))
      return;

    /* Add into Cache */
    for (INews news : resolvedNews) {
      fCachedNews.put(news.getId(), news);
    }
  }

  private boolean shouldResolveBookMarkWithSearch(IBookMark input, NewsFilter.Type filter) {

    /* Return if input is not a bookmark or not filtering at all */
    if (filter == Type.SHOW_ALL)
      return false;

    /* Return if filter can already quickly be handled from bookmark itself */
    if (filter == Type.SHOW_NEW || filter == Type.SHOW_UNREAD)
      return false;

    /* Check for number of new, unread and updated news */
    if (input.getNewsCount(EnumSet.of(INews.State.NEW, INews.State.UNREAD, INews.State.UPDATED)) > BOOKMARK_SCOPE_SEARCH_LIMIT)
      return true;

    /* Return if bookmark retention is setup, assuming that the number of elements is limited already */
    if (!hasRetentionLimit(input))
      return true;

    return false;
  }

  private boolean hasRetentionLimit(IBookMark bookmark) {
    IPreferenceScope preferences = Owl.getPreferenceService().getEntityScope(bookmark);

    /* High Retention: Read News Deleted */
    if (preferences.getBoolean(DefaultPreferences.DEL_READ_NEWS_STATE))
      return true;

    /* Medium Retention: Aged News Deleted */
    if (preferences.getBoolean(DefaultPreferences.DEL_NEWS_BY_AGE_STATE))
      return true;

    /* Low-High Retention: News Deleted by Count (Depends on actual count) */
    if (preferences.getBoolean(DefaultPreferences.DEL_NEWS_BY_COUNT_STATE) && preferences.getInteger(DefaultPreferences.DEL_NEWS_BY_COUNT_VALUE) <= BOOKMARK_SCOPE_SEARCH_LIMIT)
      return true;

    return false;
  }

  private boolean shouldResolve(INewsMark input, NewsFilter.Type filter) {

    /* Check for NEW News in Input */
    if (filter == Type.SHOW_NEW && input.getNewsCount(EnumSet.of(INews.State.NEW)) == 0)
      return false;

    /* Check for UNREAD News in Input */
    else if (filter == Type.SHOW_UNREAD && input.getNewsCount(EnumSet.of(INews.State.NEW, INews.State.UNREAD, INews.State.UPDATED)) == 0)
      return false;

    /* Check for Sticky News in Bookmark */
    else if (filter == Type.SHOW_STICKY && input instanceof IBookMark && ((IBookMark) input).getStickyNewsCount() == 0)
      return false;

    /* Check for Recent Date or 5 Days Age */
    else if ((filter == Type.SHOW_RECENT || filter == Type.SHOW_LAST_5_DAYS) && input instanceof IBookMark) {
      Date mostRecentNewsDate = ((IBookMark) input).getMostRecentNewsDate();
      if (mostRecentNewsDate != null) { //Date can be null e.g. when having more than 1 Bookmark for the same Feed (known issue)
        if (filter == Type.SHOW_RECENT && (mostRecentNewsDate.getTime() < (DateUtils.getToday().getTimeInMillis() - DateUtils.DAY)))
          return false;
        else if (filter == Type.SHOW_LAST_5_DAYS && (mostRecentNewsDate.getTime() < (DateUtils.getToday().getTimeInMillis() - 5 * DateUtils.DAY)))
          return false;
      }
    }

    return true;
  }

  private Triple<Boolean /* Filtered */, Boolean /* Searched */, List<NewsReference>> getNewsRefsFromInput(INewsMark input, NewsFilter newsFilter, Set<State> states, IProgressMonitor monitor) {
    Type filter = newsFilter.getType();

    /*
     * Optimization: If input is a saved search or bin with many results and the news filter is set to any condition that
     * is not scoped by news state, we get the results from a search to potentially get less results and so need less memory.
     */
    if (input instanceof ISearchMark || input instanceof INewsBin) {
      if (isFilteredByOtherThanState() && input.getNewsCount(states) > NEWSMARK_SCOPE_SEARCH_LIMIT) {
        ISearchCondition filterCondition = ModelUtils.getConditionForFilter(filter);
        List<SearchHit<NewsReference>> result = null;

        /* Inject into Saved Search */
        if (input instanceof ISearchMark) {
          ISearchMark searchMark = (ISearchMark) input;
          result = fSearch.searchNews(searchMark.getSearchConditions(), filterCondition, searchMark.matchAllConditions());
        }

        /* Location search for News Bin */
        else {
          INewsBin newsBin = (INewsBin) input;
          ISearchField locationField = fFactory.createSearchField(INews.LOCATION, INews.class.getName());
          ISearchCondition locationCondition = fFactory.createSearchCondition(locationField, SearchSpecifier.IS, ModelUtils.toPrimitive(Collections.singleton((IFolderChild) newsBin)));
          result = fSearch.searchNews(Arrays.asList(locationCondition, filterCondition), true);
        }

        /* Fill Newsreferences from Search Results */
        List<NewsReference> newsRefs = new ArrayList<NewsReference>(result.size());
        for (SearchHit<NewsReference> item : result) {
          newsRefs.add(item.getResult());
        }

        return Triple.create(true, true, newsRefs);
      }
    }

    /* Resolve items from bookmark through searching inside */
    else if (input instanceof IBookMark) {
      IBookMark bookmark = (IBookMark) input;

      /* Return early if bookmark should not be resolved at all */
      if (!shouldResolve(bookmark, filter))
        return Triple.create(true, false, Collections.<NewsReference> emptyList());

      ISearchCondition filterCondition = ModelUtils.getConditionForFilter(filter);
      ISearchField locationField = fFactory.createSearchField(INews.LOCATION, INews.class.getName());
      ISearchCondition locationCondition = fFactory.createSearchCondition(locationField, SearchSpecifier.IS, ModelUtils.toPrimitive(Collections.singleton((IFolderChild) bookmark)));
      List<SearchHit<NewsReference>> result = fSearch.searchNews(Arrays.asList(locationCondition, filterCondition), true);

      /* Fill Newsreferences from Search Results */
      List<NewsReference> newsRefs = new ArrayList<NewsReference>(result.size());
      for (SearchHit<NewsReference> item : result) {
        newsRefs.add(item.getResult());
      }

      return Triple.create(true, true, newsRefs);
    }

    /* Resolve Folder News Mark and pass in current filter */
    else if (input instanceof FolderNewsMark) {
      ((FolderNewsMark) input).resolve(filter, monitor);
      List<NewsReference> references = input.getNewsRefs(states);

      /* Optimization: If the folder has lots of elements and a text filter is set, limit result by this pattern */
      if (!fNoFolderLimit && input.getNewsCount(states) > MAX_FOLDER_ELEMENTS && newsFilter.isPatternSet()) {
        Iterator<NewsReference> iterator = references.iterator();
        while (iterator.hasNext()) {
          if (!newsFilter.isTextPatternMatch(iterator.next().getId()))
            iterator.remove();
        }
      }

      /* Optimization: If the folder contains more than MAX_RESOLVED_FOLDER_ELEMENTS, put the most recent at the beginning */
      if (!fNoFolderLimit && references.size() > MAX_RESOLVED_FOLDER_ELEMENTS)
        sortDescendingById(references);

      return Triple.create(true, true, references);
    }

    /* Return news refs by state */
    return Triple.create(false, false, input.getNewsRefs(states));
  }

  private void sortDescendingById(List<NewsReference> references) {
    Collections.sort(references, new Comparator<NewsReference>() {
      public int compare(NewsReference o1, NewsReference o2) {
        return o1.getId() > o2.getId() ? -1 : 1;
      }
    });
  }

  private synchronized Triple<Boolean /* Was Empty */, Collection<NewsEvent>, Collection<INews>> addToCache(Collection<NewsEvent> events, Collection<INews> addedNews) {
    boolean wasEmpty = fCachedNews.isEmpty();
    Collection<NewsEvent> visibleEvents = new ArrayList<NewsEvent>();
    Collection<INews> visibleNews = new ArrayList<INews>();

    /* Check if ContentProvider was already disposed or RSSOwl shutting down */
    if (canceled())
      return Triple.create(wasEmpty, visibleEvents, visibleNews);

    /* Use the filter (if set) to determine elements to cache */
    if (isFilteredByState() || isFilteredByOtherThanState()) {

      /* Quickly Map from News to Event */
      Map<INews, NewsEvent> mapNewsToEvent = new HashMap<INews, NewsEvent>(events.size());
      for (NewsEvent event : events) {
        mapNewsToEvent.put(event.getEntity(), event);
      }

      /* Filter the Added News */
      visibleNews.addAll(addedNews);
      filterElements(visibleNews);

      /* Also indicate related events for visible news */
      for (INews news : visibleNews)
        visibleEvents.add(mapNewsToEvent.get(news));
    }

    /* Not relevant for bookmarks, just add all */
    else {
      visibleEvents = events;
      visibleNews = addedNews;
    }

    /* Add to Cache */
    for (INews news : visibleNews) {
      fCachedNews.put(news.getId(), news);
    }

    /*
     * Since the folder news mark is bound to the lifecycle of the feedview,
     * make sure that the contents are updated properly from here.
     */
    if (fInput instanceof FolderNewsMark)
      ((FolderNewsMark) fInput).add(visibleNews);

    return Triple.create(wasEmpty, visibleEvents, visibleNews);
  }

  private synchronized Pair<List<NewsEvent>, List<INews>> updateCache(Set<NewsEvent> events, List<INews> updatedNews) {
    List<NewsEvent> visibleEvents = new ArrayList<NewsEvent>(events.size());
    List<INews> visibleNews = new ArrayList<INews>(updatedNews.size());

    /* Check if ContentProvider was already disposed or RSSOwl shutting down */
    if (canceled())
      return Pair.create(visibleEvents, visibleNews);

    for (NewsEvent event : events) {
      if (event.getEntity().getId() != null && fCachedNews.containsKey(event.getEntity().getId())) {
        visibleEvents.add(event);
        visibleNews.add(event.getEntity());
      }
    }

    return Pair.create(visibleEvents, visibleNews);
  }

  private synchronized Pair<List<NewsEvent>, List<INews>> removeFromCache(Set<NewsEvent> events, List<INews> deletedNews) {
    List<NewsEvent> visibleEvents = new ArrayList<NewsEvent>(events.size());
    List<INews> visibleNews = new ArrayList<INews>(deletedNews.size());

    /* Check if ContentProvider was already disposed or RSSOwl shutting down */
    if (canceled())
      return Pair.create(visibleEvents, visibleNews);

    /* Remove from Cache and keep track of contained items */
    for (NewsEvent event : events) {
      if (event.getEntity().getId() != null && fCachedNews.remove(event.getEntity().getId()) != null) {
        visibleEvents.add(event);
        visibleNews.add(event.getEntity());
      }
    }

    /*
     * Since the folder news mark is bound to the lifecycle of the feedview,
     * make sure that the contents are updated properly from here.
     */
    if (fInput instanceof FolderNewsMark)
      ((FolderNewsMark) fInput).remove(deletedNews);

    return Pair.create(visibleEvents, visibleNews);
  }

  private synchronized Pair<List<INews>, Boolean> newsChangedFromSearch(IProgressMonitor monitor, List<SearchMarkEvent> eventsRelatedToInput, boolean onlyHandleAddedNews) {

    /* Check if ContentProvider was already disposed or RSSOwl shutting down */
    if (canceled(monitor))
      return Pair.create(Collections.<INews> emptyList(), false);

    boolean needToFilter = true;
    boolean wasEmpty = fCachedNews.isEmpty();
    List<INews> addedNews = new ArrayList<INews>();

    /* Update Saved Search from Events */
    if (fInput instanceof ISearchMark) {

      /* Update cache alltogether based on search results */
      if (!onlyHandleAddedNews) {
        refreshCache(monitor, fInput);
        addedNews.addAll(fCachedNews.values());
        needToFilter = false;
      }

      /* Only show the added news */
      else {
        Set<Long> newsIds = extractNewsIds(eventsRelatedToInput);
        for (Long newsId : newsIds) {

          /* Skip already cached news */
          if (hasCachedNews(newsId))
            continue;

          /* Resolve News */
          INews news = fNewsDao.load(newsId);
          if (news != null && news.isVisible())
            addedNews.add(news);

          /* Check if ContentProvider was already disposed or RSSOwl shutting down */
          if (canceled(monitor))
            return Pair.create(Collections.<INews> emptyList(), false);
        }
      }
    }

    /* Update Folder News Mark from Events (we only add news, never remove) */
    else if (fInput instanceof FolderNewsMark) {
      FolderNewsMark folderNewsMark = (FolderNewsMark) fInput;
      Set<Long> newsIds = extractNewsIds(eventsRelatedToInput);
      for (Long newsId : newsIds) {

        /* Skip already cached news */
        if (hasCachedNews(newsId) || folderNewsMark.containsNews(newsId))
          continue;

        /* Resolve News */
        INews news = fNewsDao.load(newsId);
        if (news != null && news.isVisible())
          addedNews.add(news);

        /* Check if ContentProvider was already disposed or RSSOwl shutting down */
        if (canceled(monitor))
          return Pair.create(Collections.<INews> emptyList(), false);
      }
    }

    /* Check if ContentProvider was already disposed or RSSOwl shutting down */
    if (canceled(monitor))
      return Pair.create(Collections.<INews> emptyList(), false);

    /* Optimization: Only consider those news that pass the filter when news are added (or in general for Folder News Mark) */
    if (needToFilter && isFilteredByOtherThanState())
      filterElements(addedNews);

    /* Add to Cache */
    for (INews news : addedNews) {
      fCachedNews.put(news.getId(), news);
    }

    /* Add to Folder if necessary */
    if (fInput instanceof FolderNewsMark)
      ((FolderNewsMark) fInput).add(addedNews);

    return Pair.create(addedNews, wasEmpty);
  }

  private boolean isFilteredByState() {
    return fFilter.getType() == Type.SHOW_NEW || fFilter.getType() == Type.SHOW_UNREAD;
  }

  private boolean isFilteredByOtherThanState() {
    return fFilter.getType() == Type.SHOW_STICKY || fFilter.getType() == Type.SHOW_LABELED || fFilter.getType() == Type.SHOW_RECENT || fFilter.getType() == Type.SHOW_LAST_5_DAYS;
  }

  private void filterElements(Collection<INews> elements) {
    Iterator<INews> iterator = elements.iterator();
    while (iterator.hasNext()) {
      if (!fFilter.select(iterator.next(), true))
        iterator.remove();
    }
  }

  private Set<Long> extractNewsIds(List<SearchMarkEvent> events) {
    Set<Long> set = new HashSet<Long>();
    for (SearchMarkEvent event : events) {
      LongArrayList[] newsIds = ((SearchMark) event.getEntity()).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 (fFilter.getType() == Type.SHOW_NEW && i != INews.State.NEW.ordinal())
          continue;
        else if (fFilter.getType() == Type.SHOW_UNREAD && i == INews.State.READ.ordinal())
          continue;

        long[] elements = newsIds[i].getElements();
        for (long element : elements) {
          if (element > 0)
            set.add(element);
        }
      }
    }

    return set;
  }

  private List<INews> limitFolderNewsMark(List<INews> resolvedNews, NewsComparator comparer) {

    /* Return if no capping is required at all */
    if (fNoFolderLimit || resolvedNews.size() <= MAX_FOLDER_ELEMENTS)
      return resolvedNews;

    /* First add those news that are Labeled or Sticky if this group mode is active */
    List<INews> priorityItems = Collections.emptyList();
    if (isGroupingByLabel() || isGroupingByStickyness()) {
      priorityItems = new ArrayList<INews>();
      for (INews news : resolvedNews) {
        if (isGroupingByLabel() && !news.getLabels().isEmpty() || isGroupingByStickyness() && news.isFlagged())
          priorityItems.add(news);
      }
    }

    /* Check if Labeled/Sticky News already at limit size and return then */
    if (priorityItems.size() >= MAX_FOLDER_ELEMENTS)
      return priorityItems;

    /* Need to sort now to pick the top N remaining elements */
    Object[] elements = resolvedNews.toArray();
    comparer.sort(null, elements);

    /* Pick top N remaining Elements */
    int limit = MAX_FOLDER_ELEMENTS - priorityItems.size();
    List<INews> limitedResult = new ArrayList<INews>(Math.min(elements.length, MAX_FOLDER_ELEMENTS));
    for (int i = 0, c = 0; i < elements.length && c < limit; i++) {
      INews news = (INews) elements[i];
      if (!priorityItems.contains(news)) {
        limitedResult.add(news);
        c++;
      }
    }

    /* Fill in priority items if any */
    limitedResult.addAll(priorityItems);

    return limitedResult;
  }

  synchronized INewsMark getInput() {
    return fInput;
  }

  synchronized Collection<INews> getCachedNewsCopy() {
    return new ArrayList<INews>(fCachedNews.values());
  }

  synchronized boolean hasCachedNews() {
    return !fCachedNews.isEmpty();
  }

  synchronized boolean hasCachedNews(INews news) {
    return news.getId() != null && hasCachedNews(news.getId());
  }

  private synchronized boolean hasCachedNews(long newsId) {
    return fCachedNews.containsKey(newsId);
  }

  private synchronized INews obtainFromCache(NewsReference ref) {
    return obtainFromCache(ref.getId());
  }

  synchronized INews obtainFromCache(long newsId) {
    return fCachedNews.get(newsId);
  }

  private void registerListeners() {

    /* Saved Search Listener */
    fSearchMarkListener = new SearchMarkAdapter() {
      @Override
      public void newsChanged(Set<SearchMarkEvent> events) {
        final List<SearchMarkEvent> eventsRelatedToInput = new ArrayList<SearchMarkEvent>(1);

        /* Check if ContentProvider was already disposed or RSSOwl shutting down */
        if (canceled())
          return;

        /* Find those events that are related to the current input */
        for (SearchMarkEvent event : events) {
          ISearchMark searchMark = event.getEntity();
          if (fInput.equals(searchMark)) {
            eventsRelatedToInput.add(event);
            break; //Can only be one search mark per feed view
          } else if (fInput instanceof FolderNewsMark && ((FolderNewsMark) fInput).isRelatedTo(searchMark)) {
            eventsRelatedToInput.add(event);
          }
        }

        /* Check if ContentProvider was already disposed or RSSOwl shutting down */
        if (canceled())
          return;

        /* Properly update given searches are related to input */
        if (!eventsRelatedToInput.isEmpty()) {
          JobRunner.runInUIThread(fFeedView.getEditorControl(), new Runnable() {
            public void run() {
              final boolean onlyHandleAddedNews = fFeedView.isVisible();

              JobRunner.runUIUpdater(new UIBackgroundJob(fFeedView.getEditorControl()) {
                private List<INews> fAddedNews;
                private boolean fWasEmpty;

                @Override
                protected void runInBackground(IProgressMonitor monitor) {
                  if (canceled(monitor))
                    return;

                  Pair<List<INews>, Boolean> result = newsChangedFromSearch(monitor, eventsRelatedToInput, onlyHandleAddedNews);
                  fAddedNews = result.getFirst();
                  fWasEmpty = result.getSecond();
                }

                @Override
                protected void runInUI(IProgressMonitor monitor) {
                  if (canceled(monitor))
                    return;

                  /* Check if we need to Refresh at all */
                  if (onlyHandleAddedNews && (fAddedNews == null || fAddedNews.size() == 0))
                    return;

                  /* Refresh only Table Viewer if not using Newspaper Mode in Browser */
                  if (!browserShowsCollection())
                    fFeedView.refreshTableViewer(true, true); //TODO Seems some JFace caching problem here (redraw=true)

                  /* Browser shows Newspaper Mode: Only refresh under certain circumstances */
                  else {
                    if (canDoBrowserRefresh(fWasEmpty))
                      fFeedView.refreshBrowserViewer();
                    else
                      fFeedView.getNewsBrowserControl().setInfoBarVisible(true);
                  }
                }
              });
            }
          });

          /* Done */
          return;
        }
      }
    };

    DynamicDAO.addEntityListener(ISearchMark.class, fSearchMarkListener);

    /* News Listener */
    fNewsListener = new NewsAdapter() {

      /* News got Added */
      @Override
      public void entitiesAdded(final Set<NewsEvent> events) {
        JobRunner.runInUIThread(fFeedView.getEditorControl(), new Runnable() {
          public void run() {
            Set<NewsEvent> addedNews = null;

            /* Check if ContentProvider was already disposed or RSSOwl shutting down */
            if (canceled())
              return;

            /* Filter News which are from a different Feed than displayed */
            for (NewsEvent event : events) {
              if (event.getEntity().isVisible() && isInputRelatedTo(event, NewsEventType.PERSISTED)) {
                if (addedNews == null)
                  addedNews = new HashSet<NewsEvent>();

                addedNews.add(event);
              }

              /* Return on Shutdown or disposal */
              if (canceled())
                return;
            }

            /* Event not interesting for us or we are disposed */
            if (addedNews == null || addedNews.size() == 0)
              return;

            /* Handle */
            boolean refresh = handleAddedNews(addedNews);
            if (refresh) {
              if (!browserShowsCollection())
                fFeedView.refreshTableViewer(true, false);
              else
                fFeedView.refresh(true, false);
            }
          }
        });
      }

      /* News got Updated */
      @Override
      public void entitiesUpdated(final Set<NewsEvent> events) {
        JobRunner.runInUIThread(fFeedView.getEditorControl(), new Runnable() {
          public void run() {
            Set<NewsEvent> restoredNews = null;
            Set<NewsEvent> updatedNews = null;
            Set<NewsEvent> deletedNews = null;

            /* Check if ContentProvider was already disposed or RSSOwl shutting down */
            if (canceled())
              return;

            /* Filter News which are from a different Feed than displayed */
            for (NewsEvent event : events) {
              boolean isRestored = gotRestored(event, fFilter.getType());
              INews news = event.getEntity();

              /* Return on Shutdown or disposal */
              if (canceled())
                return;

              /* Check if input relates to news events */
              if (isInputRelatedTo(event, isRestored ? NewsEventType.RESTORED : NewsEventType.UPDATED)) {

                /* News got Deleted */
                if (!news.isVisible()) {
                  if (deletedNews == null)
                    deletedNews = new HashSet<NewsEvent>();

                  deletedNews.add(event);
                }

                /* News got Restored */
                else if (isRestored) {
                  if (restoredNews == null)
                    restoredNews = new HashSet<NewsEvent>();

                  restoredNews.add(event);
                }

                /* News got Updated */
                else {
                  if (updatedNews == null)
                    updatedNews = new HashSet<NewsEvent>();

                  updatedNews.add(event);
                }
              }
            }

            /* Return on Shutdown or disposal */
            if (canceled())
              return;

            boolean refresh = false;
            boolean updateSelectionFromDelete = false;

            /* Handle Restored News */
            if (restoredNews != null && !restoredNews.isEmpty())
              refresh = handleAddedNews(restoredNews);

            /* Handle Updated News */
            if (updatedNews != null && !updatedNews.isEmpty())
              refresh = handleUpdatedNews(updatedNews);

            /* Handle Deleted News */
            if (deletedNews != null && !deletedNews.isEmpty()) {
              refresh = handleDeletedNews(deletedNews);
              updateSelectionFromDelete = refresh;
            }

            /* Check if ContentProvider was already disposed or RSSOwl shutting down */
            if (canceled())
              return;

            /* Refresh and update selection due to deletion */
            if (updateSelectionFromDelete) {
              fTableViewer.updateSelectionAfterDelete(new Runnable() {
                public void run() {
                  refreshViewers(events, NewsEventType.REMOVED);
                }
              });
            }

            /* Normal refresh w/o deletion */
            else if (refresh)
              refreshViewers(events, NewsEventType.UPDATED);
          }
        });
      }

      /* News got Deleted */
      @Override
      public void entitiesDeleted(final Set<NewsEvent> events) {
        JobRunner.runInUIThread(fFeedView.getEditorControl(), new Runnable() {
          public void run() {
            Set<NewsEvent> deletedNews = null;

            /* Check if ContentProvider was already disposed or RSSOwl shutting down */
            if (canceled())
              return;

            /* Filter News which are from a different Feed than displayed */
            for (NewsEvent event : events) {
              INews news = event.getEntity();
              if ((news.isVisible() || news.getParentId() != 0) && isInputRelatedTo(event, NewsEventType.REMOVED)) {
                if (deletedNews == null)
                  deletedNews = new HashSet<NewsEvent>();

                deletedNews.add(event);
              }

              /* Return on Shutdown or disposal */
              if (canceled())
                return;
            }

            /* Event not interesting for us or we are disposed */
            if (deletedNews == null || deletedNews.size() == 0)
              return;

            /* Handle Deleted News */
            boolean refresh = handleDeletedNews(deletedNews);

            /* Check if ContentProvider was already disposed or RSSOwl shutting down */
            if (canceled())
              return;

            /* Handle Refresh */
            if (refresh) {
              if (!browserShowsCollection())
                fFeedView.refreshTableViewer(true, false);
              else
                fFeedView.refresh(true, false);
            }
          }
        });
      }
    };

    DynamicDAO.addEntityListener(INews.class, fNewsListener);
  }

  private boolean gotRestored(NewsEvent event, NewsFilter.Type filter) {
    INews news = event.getEntity();
    INews old = event.getOldNews();

    /* Quickly check common conditions under which the news can not be a restored one */
    if (news == null || old == null || !news.isVisible() || hasCachedNews(news))
      return false;

    INews.State newState = news.getState();
    INews.State oldState = old.getState();

    /* Restored: Deletion was undone */
    if (oldState == INews.State.HIDDEN || oldState == INews.State.DELETED)
      return true;

    /* Check if new state matches filter now */
    switch (filter) {
      case SHOW_NEW:
        return newState == INews.State.NEW && oldState != INews.State.NEW;

      case SHOW_UNREAD:
        return newState != INews.State.READ && oldState == INews.State.READ;

      case SHOW_STICKY:
        return CoreUtils.isStickyStateChange(Collections.singleton(event), true);

      case SHOW_LABELED:
        return CoreUtils.isLabelChange(Collections.singleton(event), true);
    }

    return false;
  }

  private void refreshViewers(final Set<NewsEvent> events, NewsEventType type) {

    /* Return on Shutdown or disposal */
    if (canceled())
      return;

    /*
     * Optimization: The Browser is likely only showing a single news and thus
     * there is no need to refresh the entire content but rather use the update
     * instead.
     */
    if (!browserShowsCollection()) {
      List<INews> items = new ArrayList<INews>(events.size());
      for (NewsEvent event : events) {
        items.add(event.getEntity());
      }

      /* Update Browser Viewer */
      if (fFeedView.isBrowserViewerVisible() && contains(fBrowserViewer.getInput(), items)) {

        /* Update */
        if (type == NewsEventType.UPDATED) {
          Set<NewsEvent> newsToUpdate = events;

          /*
           * Optimization: If more than a single news is to update, check
           * if the Browser only shows a single news to avoid a full refresh.
           */
          if (events.size() > 1) {
            NewsEvent event = findShowingEventFromBrowser(events);
            if (event != null)
              newsToUpdate = Collections.singleton(event);
          }

          fBrowserViewer.update(newsToUpdate);
        }

        /* Remove */
        else if (type == NewsEventType.REMOVED)
          fBrowserViewer.remove(items.toArray());
      }

      /* Check if ContentProvider was already disposed or RSSOwl shutting down */
      if (canceled())
        return;

      /* Refresh Table Viewer */
      fFeedView.refreshTableViewer(true, true);
    }

    /* Browser is showing Collection, thereby perform a refresh */
    else
      fFeedView.refresh(true, true);
  }

  private boolean handleAddedNews(Set<NewsEvent> events) {

    /*
     * Input can be NULL if this listener was called before NewsTableControl.setPartInput()
     * has been called (can happen if the viewer has thousands of items to load)
     */
    if (fFeedView.isTableViewerVisible() && fTableViewer.getInput() == null)
      return false;

    /* Receive added News */
    List<INews> addedNews = new ArrayList<INews>(events.size());
    for (NewsEvent event : events) {
      addedNews.add(event.getEntity());
    }

    /* Add to Cache */
    Triple<Boolean, Collection<NewsEvent>, Collection<INews>> result = addToCache(events, addedNews);
    boolean wasEmpty = result.getFirst();
    Collection<NewsEvent> visibleEvents = result.getSecond();
    Collection<INews> visibleNews = result.getThird();

    /* Return early if a refresh is required anyways */
    if (fGrouping.needsRefresh(visibleEvents, false)) {

      /* Avoid a refresh when user is reading a filled newspaper view at the moment */
      if (!browserShowsCollection() || canDoBrowserRefresh(wasEmpty, visibleEvents))
        return true;
    }

    /* Return on Shutdown or disposal */
    if (canceled())
      return false;

    /* Add to Viewers */
    addToViewers(visibleNews, visibleEvents, wasEmpty);

    return false;
  }

  /* Add a List of News to Table and Browser Viewers */
  private void addToViewers(Collection<INews> addedNews, Collection<NewsEvent> events, boolean wasEmpty) {

    /* Return on Shutdown or disposal */
    if (canceled())
      return;

    /* Return early if nothing to do */
    if (addedNews.isEmpty())
      return;

    /* Add to Table-Viewer if Visible (keep top item and selection stable) */
    if (fFeedView.isTableViewerVisible()) {
      Tree tree = fTableViewer.getTree();
      TreeItem topItem = tree.getTopItem();
      int indexOfTopItem = 0;
      if (topItem != null)
        indexOfTopItem = tree.indexOf(topItem);

      tree.setRedraw(false);
      try {
        fTableViewer.add(fTableViewer.getInput(), addedNews.toArray());
        if (topItem != null && indexOfTopItem != 0)
          tree.setTopItem(topItem);
      } finally {
        tree.setRedraw(true);
      }
    }

    /* Add to Browser-Viewer if showing entire Feed */
    else if (browserShowsCollection()) {

      /* Feedview is active and user reads news, thereby only show info about added news */
      if (!canDoBrowserRefresh(wasEmpty, events))
        fFeedView.getNewsBrowserControl().setInfoBarVisible(true);

      /* Otherwise refresh the browser viewer to show added news */
      else
        fBrowserViewer.add(fBrowserViewer.getInput(), addedNews.toArray());
    }
  }

  /* Some conditions under which a browser refresh is tolerated */
  @SuppressWarnings("unchecked")
  private boolean canDoBrowserRefresh(boolean wasEmpty) {
    return canDoBrowserRefresh(wasEmpty, Collections.EMPTY_SET);
  }

  /* Some conditions under which a browser refresh is tolerated */
  private boolean canDoBrowserRefresh(boolean wasEmpty, Collection<NewsEvent> events) {
    return (wasEmpty || !fFeedView.isVisible() || OwlUI.isMinimized() || CoreUtils.gotRestored(events));
  }

  /* Browser shows collection if maximized */
  private boolean browserShowsCollection() {
    Object input = fBrowserViewer.getInput();
    return (input instanceof BookMarkReference || input instanceof NewsBinReference || input instanceof SearchMarkReference || input instanceof FolderNewsMarkReference);
  }

  private boolean handleUpdatedNews(Set<NewsEvent> events) {

    /* Receive updated News */
    List<INews> updatedNews = new ArrayList<INews>(events.size());
    for (NewsEvent event : events) {
      updatedNews.add(event.getEntity());
    }

    /* Update Cache */
    Pair<List<NewsEvent>, List<INews>> result = updateCache(events, updatedNews);
    final List<NewsEvent> visibleEvents = result.getFirst();
    List<INews> visibleNews = result.getSecond();

    /* Return if news was not part of cache at all (e.g. limited Folder News Mark) */
    if (visibleNews.isEmpty())
      return false;

    /* Return on Shutdown or disposal */
    if (canceled())
      return false;

    /* Return early if refresh is required anyways for Grouper */
    if (fGrouping.needsRefresh(visibleEvents, true))
      return true;

    /* Return early if refresh is required anyways for Sorter */
    if (fFeedView.isTableViewerVisible()) { //Only makes sense if Browser not maximized
      ViewerComparator sorter = fTableViewer.getComparator();
      if (sorter instanceof NewsComparator && ((NewsComparator) sorter).needsRefresh(visibleEvents))
        return true;
    }

    /* Update in Table-Viewer */
    if (fFeedView.isTableViewerVisible())
      fTableViewer.update(visibleNews.toArray(), null);

    /* Update in Browser-Viewer */
    if (fFeedView.isBrowserViewerVisible() && contains(fBrowserViewer.getInput(), visibleNews)) {
      Collection<NewsEvent> newsToUpdate = visibleEvents;

      /*
       * Optimization: If more than a single news is to update, check
       * if the Browser only shows a single news to avoid a full refresh.
       */
      if (visibleEvents.size() > 1) {
        NewsEvent event = findShowingEventFromBrowser(visibleEvents);
        if (event != null)
          newsToUpdate = Collections.singleton(event);
      }

      fBrowserViewer.update(newsToUpdate);
    }

    return false;
  }

  private boolean handleDeletedNews(Set<NewsEvent> events) {

    /* Receive deleted News */
    List<INews> deletedNews = new ArrayList<INews>(events.size());
    for (NewsEvent event : events) {
      deletedNews.add(event.getEntity());
    }

    /* Remove from Cache */
    Pair<List<NewsEvent>, List<INews>> result = removeFromCache(events, deletedNews);
    List<NewsEvent> visibleEvents = result.getFirst();
    List<INews> visibleNews = result.getSecond();

    /* Return if news was not part of cache at all (e.g. limited Folder News Mark) */
    if (visibleNews.isEmpty())
      return false;

    /* Return on Shutdown or disposal */
    if (canceled())
      return false;

    /* Only refresh if grouping requires this from table viewer */
    if (isGroupingEnabled() && fFeedView.isTableViewerVisible() && fGrouping.needsRefresh(visibleEvents, false))
      return true;

    /* Otherwise: Remove from Table-Viewer */
    if (fFeedView.isTableViewerVisible())
      fTableViewer.remove(visibleNews.toArray());

    /* And: Remove from Browser-Viewer */
    if (fFeedView.isBrowserViewerVisible() && contains(fBrowserViewer.getInput(), visibleNews))
      fBrowserViewer.remove(visibleNews.toArray());

    return false;
  }

  private void unregisterListeners() {
    DynamicDAO.removeEntityListener(INews.class, fNewsListener);
    DynamicDAO.removeEntityListener(ISearchMark.class, fSearchMarkListener);
  }

  private boolean isInputRelatedTo(NewsEvent event, NewsEventType type) {
    INews news = event.getEntity();

    /* Check if BookMark references the News' Feed and is not a copy */
    if (fInput instanceof IBookMark) {

      /* Return early if news is from bin */
      if (news.getParentId() != 0)
        return false;

      /* Perform fast HashMap lookup first */
      if (hasCachedNews(news))
        return true;

      /* Otherwise compare by feed link */
      IBookMark bookmark = (IBookMark) fInput;
      if (bookmark.getFeedLinkReference().equals(news.getFeedReference()))
        return true;
    }

    /* Check if Saved Search contains the given News */
    else if (type != NewsEventType.PERSISTED && fInput instanceof ISearchMark) {
      return hasCachedNews(news) || fInput.containsNews(news);
    }

    /* Update / Remove: Check if News points to this Bin */
    else if (fInput instanceof INewsBin) {
      return news.getParentId() == fInput.getId();
    }

    /* In Memory Folder News Mark (aggregated news) */
    else if (fInput instanceof FolderNewsMark) {

      /* Perform fast HashMap lookup first */
      if (hasCachedNews(news))
        return true;

      /* Ask FolderNewsMark directly */
      return ((FolderNewsMark) fInput).isRelatedTo(news);
    }

    return false;
  }

  private boolean contains(Object input, List<INews> list) {

    /* Can only belong to this Feed since filtered before already */
    if (input instanceof BookMarkReference || input instanceof NewsBinReference || input instanceof SearchMarkReference || input instanceof FolderNewsMarkReference)
      return true;

    /* News */
    else if (input instanceof INews)
      return list.contains(input);

    /* Entity Group */
    else if (input instanceof EntityGroup) {
      List<EntityGroupItem> items = ((EntityGroup) input).getItems();
      for (EntityGroupItem item : items) {
        if (list.contains(item.getEntity()))
          return true;
      }
    }

    /* Other Input */
    else if (input instanceof Object[]) {
      Object inputNews[] = (Object[]) input;
      for (Object inputNewsItem : inputNews) {
        if (list.contains(inputNewsItem))
          return true;
      }
    }

    return false;
  }

  private NewsEvent findShowingEventFromBrowser(Collection<NewsEvent> events) {
    Object input = fBrowserViewer.getInput();
    if (input instanceof INews) {
      INews news = (INews) input;
      for (NewsEvent event : events) {
        if (news.equals(event.getEntity()))
          return event;
      }
    }

    return null;
  }

  private boolean canceled() {
    return canceled(null);
  }

  private boolean canceled(IProgressMonitor monitor) {
    return fDisposed.get() || Controller.getDefault().isShuttingDown() || (monitor != null && monitor.isCanceled());
  }
}
TOP

Related Classes of org.rssowl.ui.internal.editors.feed.NewsContentProvider

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.