// BlogBridge -- RSS feed reader, manager, and web based service // Copyright (C) 2002-2007 by R. Pito Salas // // This program is free software; you can redistribute it and/or modify it under // the terms of the GNU General Public License as published by the Free Software Foundation; // either version 2 of the License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; // without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License along with this program; // if not, write to the Free Software Foundation, Inc., 59 Temple Place, // Suite 330, Boston, MA 02111-1307 USA // // Contact: R. Pito Salas // mailto:pitosalas@users.sourceforge.net // More information: about BlogBridge // http://www.blogbridge.com // http://sourceforge.net/projects/blogbridge // // $Id: ListModel.java,v 1.15 2007/08/27 15:07:49 spyromus Exp $ // package com.salas.bb.whatshot; import com.jgoodies.binding.value.ValueModel; import com.salas.bb.domain.IArticle; import com.salas.bb.domain.IFeed; import com.salas.bb.domain.IGuide; import com.salas.bb.search.ResultsListModel; import com.salas.bb.utils.StringUtils; import com.salas.bb.utils.swingworker.SwingWorker; import javax.swing.*; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; /** * The results list model. */ public class ListModel extends ResultsListModel implements ILinkResolverListener { /** Minimum number of references in a group for the link to be hot. */ private static final int MIN_REFS_IN_GROUP = 2; /** Maximum number of hot links to put in the model from the results. */ private static final int MAX_HOT_LINKS = 20; /** Hotlink sequence number which is used to generate unique keys within the model. */ private int hotlinkSeqNum; /** What's hot engine used to get the results. */ private final Engine engine; /** Filter: minimum starz. '1' means no filtering. */ private final ValueModel mdlStarz; /** Filter: unread only article allowed. */ private final ValueModel mdlOnlyUnread; /** Filter: time options. */ private final ValueModel mdlTimeOption; /** Resolves links into titles. */ private final LinkResolver resolver; /** The list of all hotlinks in the model. */ private List<Engine.HotLink> hotlinks = new ArrayList<Engine.HotLink>(); /** The guide to look for hot links in. */ private IGuide targetGuide; /** The ignore-pattern compiled from ignorePatterns or <code>NULL</code> if not set. */ private Pattern ignorePattern; /** Don't count self references flag. */ private boolean dontCountSelfReferences; /** Suppress single source links. */ private boolean suppressSameSourceLinks; /** * Creates a list model for a given engine. * * @param engine engine. * @param mdlStarz starz filter model. * @param mdlOnlyUnread unread only flag model. * @param mdlTimeOption time options. */ public ListModel(Engine engine, ValueModel mdlStarz, ValueModel mdlOnlyUnread, ValueModel mdlTimeOption) { this.engine = engine; this.mdlStarz = mdlStarz; this.mdlOnlyUnread = mdlOnlyUnread; this.mdlTimeOption = mdlTimeOption; resolver = new LinkResolver(this); FilterListener fl = new FilterListener(); mdlStarz.addValueChangeListener(fl); mdlOnlyUnread.addValueChangeListener(fl); mdlTimeOption.addValueChangeListener(fl); } /** * Invoked when the group resolution is completed. * * @param group group resolved. */ public void onGroupResolved(final HotResultGroup group) { SwingUtilities.invokeLater(new Runnable() { public void run() { fireGroupUpdated(group); } }); } /** * Stops link resolution immediately. */ public void stopLinkResolution() { if (resolver != null) resolver.stop(); } /** * Start scanning and filling the model. * * @return returns the scanner to start. */ public SwingWorker scan() { return new Scanner(); } /** * Processes the links from the engine. * * @param links links to process. */ void processLinks(List<Engine.HotLink> links) { for (Engine.HotLink hotLink : links) { // We don't need links with little references and // we allow only some given number of hotlinks if (hotLink.size() < MIN_REFS_IN_GROUP) break; hotlinks.add(hotLink); } review(); } private void review() { List<HotResultItem> groupItems = new LinkedList<HotResultItem>(); List<HotResultGroup> newGroups = new LinkedList<HotResultGroup>(); for (Engine.HotLink link : hotlinks) { List<IArticle> matchingArticles = getMatchingArticles(link); if (matchingArticles == null) continue; String linkString = link.getLink().toString(); groupItems.clear(); for (IArticle article : matchingArticles) { groupItems.add(new HotResultItem(article, linkString)); } if (groupItems.size() >= MIN_REFS_IN_GROUP) { HotResultGroup group = new HotResultGroup(hotlinkSeqNum++, link); for (HotResultItem item : groupItems) group.add(item); newGroups.add(group); } } // Sort groups by the number of items in them Collections.sort(newGroups); fireClear(); int i = 0; for (HotResultGroup group : newGroups) { group.setVisible(true); // Start resolving the group title if (!group.isResolved()) { String title = resolver.resolve(group); if (title != null) group.setResolvedTitle(title); } fireGroupAdded(group, true); // Fire all items for (HotResultItem item : group) fireItemAdded(item, group); i++; if (i > MAX_HOT_LINKS) break; } } private List<IArticle> getMatchingArticles(Engine.HotLink link) { List<IArticle> matches; // Check against ignore pattern if (ignorePattern != null && ignorePattern.matcher(link.getLink().toString()).find()) return null; // Check if has links from one source only IFeed feed = null; matches = new LinkedList<IArticle>(); boolean fine = !suppressSameSourceLinks; for (IArticle article : link) { if (!matches(link, article)) continue; matches.add(article); if (!fine) { if (feed == null) feed = article.getFeed(); else if (feed != article.getFeed()) fine = true; } } return !fine ? null : matches; } private boolean matches(Engine.HotLink link, IArticle article) { // Check the rating if ((Boolean)mdlOnlyUnread.getValue() && article.isRead()) return false; // Check the time TimeOption to = (TimeOption)mdlTimeOption.getValue(); if (article.getPublicationDate().getTime() < System.currentTimeMillis() - to.getOffset()) return false; // Check the starz Integer starz = (Integer)mdlStarz.getValue(); IFeed feed = article.getFeed(); if (starz > 1 && feed.getRating() < starz) return false; // Check self-referencing if (dontCountSelfReferences) { String linkHost = link.getLink().getHost(); URL articleLink = article.getLink(); if (articleLink != null && articleLink.getHost().equalsIgnoreCase(linkHost)) return false; } // Check guides return targetGuide == null || targetGuide.getID() == -1 || feed.belongsTo(targetGuide); } /** * Sets advanced filtering setup. * * @param ignorePatterns ignore patterns in textual form (one per line). * @param dontCountSelfReferences <code>TRUE</code> to never count links to self. * @param suppressSameSourceLinks <code>TRUE</code> to suppress hot links referenced only from one source. * @param targetGuide target guide to look for hot articles in. */ public void setSetup(String ignorePatterns, boolean dontCountSelfReferences, boolean suppressSameSourceLinks, IGuide targetGuide) { ignorePattern = StringUtils.isEmpty(ignorePatterns) ? null : Pattern.compile(StringUtils.keywordsToPattern(ignorePatterns)); this.dontCountSelfReferences = dontCountSelfReferences; this.suppressSameSourceLinks = suppressSameSourceLinks; this.targetGuide = targetGuide; review(); } /** * Scanner worker. */ private class Scanner extends SwingWorker<List<Engine.HotLink>, Integer> implements IProgressListener { /** * Invoked when background processing should start. * * @return the result. * * @throws Exception in case of any error. */ protected List<Engine.HotLink> doInBackground() throws Exception { return engine.scan(this); } @Override protected void done() { List<Engine.HotLink> links; try { links = get(); } catch (Exception e) { return; } processLinks(links); } /** * Invoked on progress change. * * @param percent percents [0 - 100]; */ public void onProgress(int percent) { setProgress(percent); } } private class FilterListener implements PropertyChangeListener { /** * Invoked when a filter property changes. * * @param evt event. */ public void propertyChange(PropertyChangeEvent evt) { review(); } } }