/* ********************************************************************** ** ** 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.util; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredSelection; import org.rssowl.core.Owl; import org.rssowl.core.persist.IAttachment; 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.ILabel; import org.rssowl.core.persist.IModelFactory; import org.rssowl.core.persist.INews; import org.rssowl.core.persist.INewsBin; import org.rssowl.core.persist.ISearchCondition; import org.rssowl.core.persist.ISearchField; import org.rssowl.core.persist.SearchSpecifier; import org.rssowl.core.persist.pref.IPreferenceScope; import org.rssowl.core.persist.reference.NewsReference; import org.rssowl.core.persist.service.IModelSearch; 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.URIUtils; import org.rssowl.ui.internal.Activator; import org.rssowl.ui.internal.EntityGroup; import org.rssowl.ui.internal.EntityGroupItem; import org.rssowl.ui.internal.FolderNewsMark; import org.rssowl.ui.internal.OwlUI; import org.rssowl.ui.internal.editors.feed.NewsFilter; import org.rssowl.ui.internal.editors.feed.NewsGrouping; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; /** * Helper class for various Model-Transformations required by the UI. * * @author bpasero */ public class ModelUtils { /* This utility class constructor is hidden */ private ModelUtils() { // Protect default constructor } /** * @param entities A list of folder childs. * @return Returns a multi-dimensional array where an array of {@link Long} is * stored in the first index representing IDs of all {@link IFolder} in the * list. The second index is an array of {@link Long} that represents the IDs * of all {@link IBookMark} in the list. The third index is an array of * {@link Long} that represents the IDs of all {@link INewsBin} in the list. * Returns <code>null</code> if the list of {@link IFolderChild} did not * contain any folders or bookmarks. */ public static Long[][] toPrimitive(Collection<IFolderChild> entities) { List<Long> folderIds = null; List<Long> bookmarkIds = null; List<Long> newsbinIds = null; int folderCounter = 0; int bookmarkCounter = 0; int newsbinCounter = 0; for (IEntity entity : entities) { /* Folder */ if (entity instanceof IFolder) { if (folderIds == null) folderIds = new ArrayList<Long>(); folderIds.add(entity.getId()); folderCounter++; } /* FolderMark (for aggregations) */ else if (entity instanceof FolderNewsMark) { if (folderIds == null) folderIds = new ArrayList<Long>(); folderIds.add(((FolderNewsMark) entity).getFolder().getId()); folderCounter++; } /* BookMark */ else if (entity instanceof IBookMark) { if (bookmarkIds == null) bookmarkIds = new ArrayList<Long>(); bookmarkIds.add(entity.getId()); bookmarkCounter++; } /* NewsBin */ else if (entity instanceof INewsBin) { if (newsbinIds == null) newsbinIds = new ArrayList<Long>(); newsbinIds.add(entity.getId()); newsbinCounter++; } /* Other type not supported */ else throw new IllegalArgumentException("Only Folders, Feeds and News Bins are allowed!"); //$NON-NLS-1$ } if (folderIds == null && bookmarkIds == null && newsbinIds == null) return null; Long[][] result = new Long[3][]; int maxEntityCount = Math.max(folderCounter, Math.max(bookmarkCounter, newsbinCounter)); result[CoreUtils.FOLDER] = toArray(folderIds, maxEntityCount); result[CoreUtils.BOOKMARK] = toArray(bookmarkIds, maxEntityCount); result[CoreUtils.NEWSBIN] = toArray(newsbinIds, maxEntityCount);; return result; } private static Long[] toArray(List<Long> values, int fillFactor) { Long[] array = new Long[fillFactor]; for (int i = 0; i < fillFactor; i++) { if (values != null && i < values.size()) array[i] = values.get(i); else array[i] = 0L; } return array; } /** * @param selection Any structured selection. * @return A List of Entities from the given Selection. In case the selection * contains an instanceof <code>EntityGroup</code>, only the content of the * group is considered. */ public static List<IEntity> getEntities(IStructuredSelection selection) { if (selection.isEmpty()) return new ArrayList<IEntity>(0); List<?> elements = selection.toList(); List<IEntity> entities = new ArrayList<IEntity>(elements.size()); for (Object object : elements) { if (object instanceof IEntity && !entities.contains(object)) entities.add((IEntity) object); else if (object instanceof EntityGroup) { List<EntityGroupItem> items = ((EntityGroup) object).getItems(); for (EntityGroupItem item : items) if (!entities.contains(item.getEntity())) entities.add(item.getEntity()); } } return entities; } /** * @param selection Any structured selection. * @return A List of {@link IFolderChild} from the given Selection containing * {@link IFolder}, {@link IBookMark} and {@link INewsBin}. */ public static List<IFolderChild> getFoldersBookMarksBins(IStructuredSelection selection) { if (selection.isEmpty()) return new ArrayList<IFolderChild>(0); List<?> elements = selection.toList(); List<IFolderChild> entities = new ArrayList<IFolderChild>(elements.size()); for (Object object : elements) { if (object instanceof IFolder || object instanceof IBookMark || object instanceof INewsBin) entities.add((IFolderChild) object); } return entities; } /** * @param <T> * @param selection * @param entityClass * @return A List of Entities that are instances of <code>entityClass</code> * from the given selection. In case the selection contains an instanceof * <code>EntityGroup</code>, only the content of the group is considered. */ public static <T extends IEntity> List<T> getEntities(IStructuredSelection selection, Class<T> entityClass) { if (selection.isEmpty()) return new ArrayList<T>(0); List<?> elements = selection.toList(); List<T> entities = new ArrayList<T>(elements.size()); for (Object object : elements) { if (entityClass.isInstance(object) && !entities.contains(entityClass.cast(object))) entities.add(entityClass.cast(object)); else if (object instanceof EntityGroup) { List<EntityGroupItem> items = ((EntityGroup) object).getItems(); for (EntityGroupItem item : items) { if (entityClass.isInstance(item.getEntity()) && !entities.contains(entityClass.cast(item.getEntity()))) entities.add(entityClass.cast(item.getEntity())); } } } return entities; } /** * Will return all News of the List of Objects also considering EntityGroups. * * @param objects * @return all News of the List of Objects also considering EntityGroups. */ public static Collection<INews> normalize(List<?> objects) { List<INews> normalizedNews = new ArrayList<INews>(objects.size()); for (Object object : objects) { /* News */ if (object instanceof INews && !normalizedNews.contains(object)) { normalizedNews.add((INews) object); } /* Group */ else if (object instanceof EntityGroup) { EntityGroup group = (EntityGroup) object; if (NewsGrouping.GROUP_CATEGORY_ID.equals(group.getCategory())) { List<IEntity> entities = group.getEntities(); for (IEntity entity : entities) { if (!normalizedNews.contains(entity)) normalizedNews.add((INews) entity); } } } } return normalizedNews; } /** * @param selection Any list of selected <code>INews</code> or * <code>EntityGroup</code>. * @return Returns a Set of <code>ILabel</code> that <em>all entities</em> of * the given Selection had applied to. */ public static Set<ILabel> getLabelsForAll(IStructuredSelection selection) { Set<ILabel> labelsForAll = new HashSet<ILabel>(5); List<INews> selectedNews = getEntities(selection, INews.class); /* For each selected News */ for (INews news : selectedNews) { Set<ILabel> newsLabels = news.getLabels(); /* Only add Label if contained in all News */ LabelLoop: for (ILabel newsLabel : newsLabels) { if (!labelsForAll.contains(newsLabel)) { for (INews news2 : selectedNews) { if (!news2.getLabels().contains(newsLabel)) break LabelLoop; } labelsForAll.add(newsLabel); } } } return labelsForAll; } /** * @param selection a {@link IStructuredSelection} of {@link INews}. * @return a {@link List} of {@link IAttachment} and {@link URI} from the * selection of {@link INews} pointing to downloadable attachment links. */ public static List<Pair<IAttachment, URI>> getAttachmentLinks(IStructuredSelection selection) { List<Pair<IAttachment, URI>> attachmentLinks = new ArrayList<Pair<IAttachment, URI>>(); Collection<INews> news = normalize(selection.toList()); for (INews newsitem : news) { List<IAttachment> attachments = newsitem.getAttachments(); for (IAttachment attachment : attachments) { URI link = attachment.getLink(); if (link != null) { if (!link.isAbsolute()) { try { link = URIUtils.resolve(URIUtils.toHTTP(newsitem.getFeedReference().getLink()), link); } catch (URISyntaxException e) { Activator.getDefault().logError(e.getMessage(), e); continue; //Proceed with other Attachments } } attachmentLinks.add(Pair.create(attachment, link)); } } } return attachmentLinks; } /** * Helper method to load a specific value from preferences by bypassing an * implementation detail (see Bug 1291: Simplify filter and group settings * treatment). * * @param preferences the actual scope to load from. * @param key the key of the setting to load. * @param fallback the fallback preferences to use in case the setting has a * negative value. * @param fallbackKey the key of the fallback setting to load in case the * setting has a negative value. * @return the integer value from the given preferences. Never negative. */ public static int loadIntegerValueWithFallback(IPreferenceScope preferences, String key, IPreferenceScope fallback, String fallbackKey) { int iVal = preferences.getInteger(key); if (iVal >= 0) return iVal; return Math.max(0, fallback.getInteger(fallbackKey)); } /** * Uses the {@link IModelSearch} to determine the number of news inside the * given bookmark. * * @param bookmark the {@link IBookMark} to count news of. * @return the number of news inside the bookmark. */ public static int countNews(IBookMark bookmark) { ISearchField locationField = Owl.getModelFactory().createSearchField(INews.LOCATION, INews.class.getName()); ISearchCondition condition = Owl.getModelFactory().createSearchCondition(locationField, SearchSpecifier.SCOPE, ModelUtils.toPrimitive(Arrays.asList(new IFolderChild[] { bookmark }))); List<SearchHit<NewsReference>> result = Owl.getPersistenceService().getModelSearch().searchNews(Collections.singleton(condition), false); return result.size(); } /** * @param selection the {@link IStructuredSelection} to look for * {@link IFolder} and {@link IFolderChild}. Never <code>null</code>. * @return a {@link Pair} of location as {@link IFolder} and position as * {@link IFolderChild} or <code>null</code> if no position can be determined. */ public static Pair<IFolder, IFolderChild> getLocationAndPosition(IStructuredSelection selection) { IFolder folder = null; IFolderChild position = null; /* Check Selection */ if (!selection.isEmpty()) { Object firstElement = selection.getFirstElement(); if (firstElement instanceof IFolder) folder = (IFolder) firstElement; else if (firstElement instanceof IFolderChild) { folder = ((IFolderChild) firstElement).getParent(); position = ((IFolderChild) firstElement); } } /* Otherwise use visible root-folder */ if (folder == null) folder = OwlUI.getSelectedBookMarkSet(); return Pair.create(folder, position); } /** * @param selection the selected elements as {@link ISelection}. * @return <code>true</code> if the selection contains a {@link EntityGroup} * and <code>false</code> otherwise. */ public static boolean isEntityGroupSelected(ISelection selection) { if (selection instanceof IStructuredSelection) { List<?> list = ((IStructuredSelection) selection).toList(); for (Object object : list) { if (object instanceof EntityGroup) return true; } } return false; } /** * @param type the {@link NewsFilter} type. * @return a {@link ISearchCondition} matching the provided NewsFilter.Type or * <code>null</code> if none. */ public static ISearchCondition getConditionForFilter(NewsFilter.Type type) { IModelFactory factory = Owl.getModelFactory(); switch (type) { case SHOW_ALL: return null; case SHOW_NEW: ISearchField field = factory.createSearchField(INews.STATE, INews.class.getName()); return factory.createSearchCondition(field, SearchSpecifier.IS, EnumSet.of(INews.State.NEW)); case SHOW_UNREAD: field = factory.createSearchField(INews.STATE, INews.class.getName()); return factory.createSearchCondition(field, SearchSpecifier.IS, EnumSet.of(INews.State.NEW, INews.State.UNREAD, INews.State.UPDATED)); case SHOW_STICKY: field = factory.createSearchField(INews.IS_FLAGGED, INews.class.getName()); return factory.createSearchCondition(field, SearchSpecifier.IS, true); case SHOW_LABELED: field = factory.createSearchField(INews.LABEL, INews.class.getName()); return factory.createSearchCondition(field, SearchSpecifier.IS, "*"); //$NON-NLS-1$ case SHOW_LAST_5_DAYS: long now = System.currentTimeMillis(); long lastFiveDays = DateUtils.getToday().getTimeInMillis() - 5 * DateUtils.DAY; long minutes = (now - lastFiveDays) / 60000; field = factory.createSearchField(INews.AGE_IN_MINUTES, INews.class.getName()); return factory.createSearchCondition(field, SearchSpecifier.IS_LESS_THAN, (int) (minutes * -1)); case SHOW_RECENT: now = System.currentTimeMillis(); long recent = DateUtils.getToday().getTimeInMillis() - DateUtils.DAY; minutes = (now - recent) / 60000; field = factory.createSearchField(INews.AGE_IN_MINUTES, INews.class.getName()); return factory.createSearchCondition(field, SearchSpecifier.IS_LESS_THAN, (int) (minutes * -1)); } return null; } }