// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 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: Engine.java,v 1.9 2007/11/01 13:01:40 spyromus Exp $
//
package com.salas.bb.whatshot;
import com.salas.bb.domain.*;
import com.salas.bb.utils.Constants;
import com.salas.bb.utils.DateUtils;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
/**
* The main engine of the What's Hot feature.
*/
public class Engine
{
/** Max number of percents. */
private static final double MAX_PERCENTS = 100.0;
/** The list of filter criteria. */
private static final List<IHotLinkCriteria> FILTER_CRITERIA = new LinkedList<IHotLinkCriteria>();
/** Guides set to operate. */
private final GuidesSet set;
/**
* Creates what's hot engine for the set.
*
* @param set set.
*/
public Engine(GuidesSet set)
{
this.set = set;
}
/**
* Scans the guides set.
*
* @return reults.
*/
public List<HotLink> scan()
{
return scan(null);
}
/**
* Scans the guides set.
*
* @param listener progress listener.
*
* @return reults.
*/
public List<HotLink> scan(IProgressListener listener)
{
Result res = new Result();
FeedsList fl = set.getFeedsList();
List<IFeed> feeds = fl.getFeeds();
double perc = MAX_PERCENTS / feeds.size();
int i = 0;
for (IFeed feed : feeds)
{
if (feed instanceof DataFeed) scan(feed, res);
i++;
if (listener != null) listener.onProgress((int)(perc * i));
}
return filter(res.getHotLinks());
}
private void scan(IFeed feed, Result result)
{
IArticle[] articles = feed.getArticles();
for (IArticle article : articles)
{
Date pubdate = article.getPublicationDate();
if (!DateUtils.olderThan(pubdate, Constants.MILLIS_IN_DAY * 7)) scan(article, result);
}
}
private void scan(IArticle article, Result result)
{
Collection<String> links = article.getLinks();
for (String link : links)
{
result.register(link, article);
}
}
/**
* Result has hot links.
*/
public static class Result extends HashMap<String, HotLink>
{
/**
* Registers an article within a hot link.
*
* @param urlS URL.
* @param article article.
*/
public void register(String urlS, IArticle article)
{
urlS = urlS.trim().toLowerCase();
urlS = urlS.replaceAll("/+$", "");
try
{
// Create a real URL using the base
URL base = article.getLink();
URL url = new URL(base, urlS);
urlS = url.toString();
// See if it's already there in the cache
HotLink hl = get(urlS);
if (hl == null)
{
hl = new HotLink(url);
put(urlS, hl);
}
hl.add(article);
} catch (MalformedURLException e)
{
// Skipping
}
}
public List<HotLink> getHotLinks()
{
Collection<HotLink> hls = values();
List<HotLink> hlsl = new ArrayList<HotLink>(hls);
Collections.sort(hlsl);
return hlsl;
}
}
// ------------------------------------------------------------------------
// Filtering
// ------------------------------------------------------------------------
/**
* Clears a filter criteria.
*/
static void clearFilterCriteria()
{
FILTER_CRITERIA.clear();
}
/**
* Adds a filtering criteria.
*
* @param criteria criteria.
*/
public static void addFilterCriteria(IHotLinkCriteria criteria)
{
FILTER_CRITERIA.add(criteria);
}
/**
* Returns <code>TRUE</code> if the link matches any criteria.
*
* @param link link.
*
* @return <code>TRUE</code> if the link matches any criteria.
*/
static boolean matchesFilters(HotLink link)
{
for (IHotLinkCriteria criteria : FILTER_CRITERIA)
{
if (criteria.matches(link)) return true;
}
return false;
}
/**
* Removes all links that match the custom filters from the list.
*
* @param links links.
*
* @return updated list.
*/
private List<HotLink> filter(List<HotLink> links)
{
List<HotLink> toRemove = new LinkedList<HotLink>();
for (HotLink link : links)
{
if (matchesFilters(link)) toRemove.add(link);
}
links.removeAll(toRemove);
return links;
}
// ------------------------------------------------------------------------
// Supplementary classes
// ------------------------------------------------------------------------
/**
* Hot link has articles.
*/
public static class HotLink extends ArrayList<IArticle> implements Comparable
{
private final URL link;
private String pageTitle;
private int cumulativeFeedRating;
private int numberOfFeedRatings;
/**
* Creates a hotlink object.
*
* @param link a link.
*/
public HotLink(URL link)
{
this.link = link;
cumulativeFeedRating = 0;
}
public URL getLink()
{
return link;
}
@Override
public boolean add(IArticle article)
{
boolean added = false;
IFeed feed = article.getFeed();
if (!contains(article) && feed != null)
{
added = super.add(article);
int rating = feed.getRating();
if (rating > -1)
{
cumulativeFeedRating += rating + 1;
numberOfFeedRatings++;
}
}
return added;
}
/**
* Sets the title of the corresponding page.
*
* @param pageTitle page title.
*/
public void setPageTitle(String pageTitle)
{
this.pageTitle = pageTitle;
}
/**
* Returns the title of the link to display.
*
* @return title.
*/
public String getTitle()
{
return pageTitle == null ? link.toString() : pageTitle;
}
/**
* Returns the average rating of feeds involved.
*
* @return average rating.
*/
private double getAverateRating()
{
return numberOfFeedRatings == 0 ? 0 : cumulativeFeedRating / numberOfFeedRatings;
}
/**
* Compares two hot link objects.
*
* @param o second object.
*
* @return <code>TRUE</code> if equal.
*/
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
HotLink hotLink = (HotLink)o;
return link.toString().equals(hotLink.link.toString());
}
/**
* Returns the hash code.
*
* @return code.
*/
public int hashCode()
{
int result = super.hashCode();
result = 31 * result + link.toString().hashCode();
return result;
}
/**
* Compares this object with the specified object for order. Returns a negative integer, zero, or a positive
* integer as this object is less than, equal to, or greater than the specified object.<p>
*
* @param o the Object to be compared.
*
* @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater than
* the specified object.
*
* @throws ClassCastException if the specified object's type prevents it from being compared to this Object.
*/
public int compareTo(Object o)
{
if (getClass() != o.getClass()) throw new ClassCastException();
HotLink that = (HotLink)o;
Integer thisCount = size();
Integer thatCount = that.size();
int res = thatCount.compareTo(thisCount);
if (res == 0)
{
Double thisRating = getAverateRating();
Double thatRating = that.getAverateRating();
res = thatRating.compareTo(thisRating);
if (res == 0) res = getLink().toString().compareToIgnoreCase(that.getLink().toString());
}
return res;
}
}
}