Package com.salas.bb.domain

Source Code of com.salas.bb.domain.SearchFeed$NoDupArticleDateComparator

// 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: SearchFeed.java,v 1.55 2007/11/09 16:24:19 spyromus Exp $
//

package com.salas.bb.domain;

import com.salas.bb.domain.query.articles.Query;
import com.salas.bb.domain.utils.ArticleDateComparator;
import com.salas.bb.utils.i18n.Strings;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
* Search feed is a special kind of feeds which is using predefined query to to find
* the articles of interest.
*/
public class SearchFeed extends AbstractFeed
{
    private static final int MAXIMUM_ARTICLES_LIMIT = Integer.MAX_VALUE;
    private static final ArticleDateComparator articleDateComparator;

    /** Articles limit property. */
    public static final String PROP_ARTICLES_LIMIT = "articlesLimit";
    /** Name of query property. */
    public static final String PROP_QUERY = "query";
    public static final String PROP_DEDUP_ENABLED = "dedupEnabled";
    public static final String PROP_DEDUP_FROM = "dedupFrom";
    public static final String PROP_DEDUP_TO = "dedupTo";
    /** Fake property which is fired if the dedup properties were updated. */
    public static final String PROP_DEDUP_UPDATED = "dedupUpdated";

    private final List<IArticle>  articles;

    private String      baseTitle;
    private int         articlesLimit;
    private Query       query;
    /** TRUE when deduplication functionality is enabled. */
    private boolean     dedupEnabled;
    /** The first word index to look for the match. */
    private int         dedupFrom;
    /** The last word index to look for the match. */
    private int         dedupTo;

    private SearchFeed.ArticlesListener articlesListener;

    static
    {
        articleDateComparator = new NoDupArticleDateComparator();
    }

    /**
     * Creates search feed.
     */
    public SearchFeed()
    {
        // The LinkedList was replaced by the ArrayList because of EDT locks and
        // performance issues
        articles = new ArrayList<IArticle>();
        articlesListener = new ArticlesListener();

        super.setCustomViewModeEnabled(true);

        dedupEnabled = false;
        dedupFrom = 0;
        dedupTo = 0;
    }

    /**
     * Returns the Article at the specified index.
     *
     * @param index index of article in channel.
     *
     * @return article object.
     */
    public synchronized IArticle getArticleAt(int index)
    {
        return articles.get(index);
    }

    /**
     * Returns number of articles in channel.
     *
     * @return number of articles.
     */
    public synchronized int getArticlesCount()
    {
        return Math.min(articles.size(), articlesLimit);
    }

    /**
     * Returns number of articles this feed owns.
     *
     * @return number of articles.
     */
    public int getOwnArticlesCount()
    {
        // Search feed has no own articles
        return 0;
    }

    /**
     * Returns the list of all articles which are currently in the feed.
     *
     * @return all articles at this moment.
     */
    public IArticle[] getArticles()
    {
        int visibleArticlesCount;
        IArticle[] fullList;

        synchronized (this)
        {
            visibleArticlesCount = getArticlesCount();
            fullList = articles.toArray(new IArticle[articles.size()]);
        }

        // Leave only visible articles in the list.
        IArticle[] cropped = new IArticle[visibleArticlesCount];
        System.arraycopy(fullList, 0, cropped, 0, visibleArticlesCount);

        return cropped;
    }

    /**
     * Returns title of feed.
     *
     * @return title.
     */
    public String getTitle()
    {
        return baseTitle;
    }

    /**
     * Gets the base title.
     *
     * @return base title.
     */
    public String getBaseTitle()
    {
        return baseTitle;
    }

    /**
     * Sets the title of feed.
     *
     * @param aTitle feed title.
     */
    public void setBaseTitle(String aTitle)
    {
        String oldTitle = getTitle();
        baseTitle = aTitle;
        firePropertyChanged(IFeed.PROP_TITLE, oldTitle, getTitle());
    }

    /**
     * Returns articles limit.
     *
     * @return articles limit.
     */
    public int getArticlesLimit()
    {
        return articlesLimit;
    }

    /**
     * Sets the limit of articles in this feed. If the limit is bigger than
     * <code>MAXIMUM_ARTICLES_LIMIT</code> then it's assigned to it.
     *
     * @param anArticlesLimit limit.
     *
     * @throws IllegalArgumentException if limit is negative.
     *
     * @see #MAXIMUM_ARTICLES_LIMIT
     */
    public synchronized void setArticlesLimit(int anArticlesLimit)
    {
        if (anArticlesLimit < 0)
            throw new IllegalArgumentException(Strings.error("limit.should.be.non.negative"));
        if (anArticlesLimit > MAXIMUM_ARTICLES_LIMIT) anArticlesLimit = MAXIMUM_ARTICLES_LIMIT;

        int oldLimit = articlesLimit;
        articlesLimit = anArticlesLimit;

        if (isVisible(oldLimit))
        {
            int end = Math.min(articles.size(), articlesLimit);
            for (int i = oldLimit; i < end; i++)
            {
                fireArticleAdded(getArticleAt(i));
            }
        } else
        {
            int size = articles.size();
            for (int i = size - 1; i >= articlesLimit; i--)
            {
                fireArticleRemoved(getArticleAt(i));
            }
        }

        firePropertyChanged(PROP_ARTICLES_LIMIT, new Integer(oldLimit), new Integer(articlesLimit));
    }

    /**
     * Returns query.
     *
     * @return query.
     */
    public Query getQuery()
    {
        return query;
    }

    /**
     * Sets the new query.
     *
     * @param aQuery query to set.
     */
    public void setQuery(Query aQuery)
    {
        if (query == null || !query.equals(aQuery))
        {
            Query oldQuery = query;
            query = aQuery;

            firePropertyChanged(PROP_QUERY, oldQuery, query, true, false);
        }
    }

    /**
     * Returns simple match key, which can be used to detect similarity of feeds. For example, it's
     * XML URL for the direct feeds, query type + parameter for the query feeds, serialized search
     * criteria for the search feeds.
     *
     * @return match key.
     */
    public String getMatchKey()
    {
        return "SF" + (query == null ? null : query.serializeToString());
    }

    /**
     * Checks the article agains the query and adds it if the query returned success.
     * The article may be not added if its insertion index is going to be out of limit.
     *
     * @param anArticle article to check.
     *
     * @throws NullPointerException if the article isn't specified.
     */
    public synchronized void addArticleIfMatching(IArticle anArticle)
    {
        if (anArticle == null) throw new NullPointerException(Strings.error("unspecified.article"));

        if (query != null && query.match(anArticle) && !isDuplicate(anArticle))
        {
            // Collections.binarySearch can be used to find the index of potential
            // insertion of new item in the collection basing on its natural order
            // or order, reported by some comparator.
            int articleIndex = Collections.binarySearch(articles, anArticle, articleDateComparator);
            if (articleIndex < 0)
            {
                int insertionIndex = -articleIndex - 1;
                addArticle(anArticle, insertionIndex);
            }
        }
    }

    /**
     * Checks if an article is duplicate of some other article.
     *
     * @param anArticle article to check.
     *
     * @return <code>TRUE</code> if it is.
     */
    private boolean isDuplicate(IArticle anArticle)
    {
        return dedupEnabled && isDuplicate(anArticle, dedupFrom, dedupTo, articles);
    }

    private void addArticle(IArticle anArticle, int insertionIndex)
    {
        int unread = getUnreadArticlesCount();

        articles.add(insertionIndex, anArticle);
        anArticle.addListener(articlesListener);

        if (isVisible(insertionIndex))
        {
            fireArticleAdded(anArticle);

            if (articles.size() > articlesLimit) removeArticle(getArticleAt(articlesLimit));
        }

        int newUnread = getUnreadArticlesCount();
        if (unread != newUnread) firePropertyChanged(PROP_UNREAD_ARTICLES_COUNT,
            new Integer(unread), new Integer(getUnreadArticlesCount()));
    }

    private boolean isVisible(int index)
    {
        return index < articlesLimit;
    }

    /**
     * Removes article from the feed.
     *
     * @param anArticle article.
     */
    public synchronized void removeArticle(IArticle anArticle)
    {
        int unread = getUnreadArticlesCount();

        if (articles.remove(anArticle))
        {
            anArticle.removeListener(articlesListener);
            fireArticleRemoved(anArticle);

            int newUnread = getUnreadArticlesCount();
            if (unread != newUnread) firePropertyChanged(PROP_UNREAD_ARTICLES_COUNT,
                new Integer(unread), new Integer(newUnread));
        }
    }

    /**
     * Reviews all articles from given feed and removes if they are no longer matching.
     *
     * @param feed feed to which article should belong to be reviewed or <code>NULL</code> for any.
     */
    public synchronized void reviewArticlesTakenFrom(IFeed feed)
    {
        int count = articles.size();
        for (int i = 0; i < count; i++)
        {
            int index = count - i - 1;
            IArticle article = getArticleAt(index);
            if (feed == null || article.getFeed() == feed) reviewArticle(article);
        }
    }

    private void reviewArticle(IArticle aArticle)
    {
        if (!query.match(aArticle) || isDuplicate(aArticle))
        {
            int index = articles.indexOf(aArticle);
            removeArticle(aArticle);

// This piece of code makes another article from the matching appear in the list
// instead of deleted one.
            if (isVisible(index) && articles.size() >= articlesLimit)
            {
                fireArticleAdded(getArticleAt(articlesLimit - 1));
            }
        }
    }

    /**
     * Removes all listener registrations.
     */
    public void unregisterListeners()
    {
        for (IArticle article : articles) article.removeListener(articlesListener);
    }

    /**
     * Listens for changes in all articles, this feed is referring to.
     */
    private class ArticlesListener implements IArticleListener
    {
        /**
         * Invoked when the property of the article has been changed.
         *
         * @param article  article.
         * @param property property of the article.
         * @param oldValue old property value.
         * @param newValue new property value.
         */
        public void propertyChanged(IArticle article, String property, Object oldValue,
                                    Object newValue)
        {
            if (!AbstractArticle.PROP_ID.equals(property))
            {
                synchronized (SearchFeed.this)
                {
                    reviewArticle(article);
                }
            }

            if (IArticle.PROP_READ.equals(property))
            {
                boolean readNow = (Boolean)newValue;
                int unread = getUnreadArticlesCount();
                firePropertyChanged(PROP_UNREAD_ARTICLES_COUNT,
                    new Integer(readNow ? unread + 1 : unread - 1),
                    new Integer(unread));
            }
        }
    }

    /**
     * This comparator takes the dates order in account, but if the dates are the same
     * it checks the hash codes.
     */
    private static class NoDupArticleDateComparator extends ArticleDateComparator
    {
        public NoDupArticleDateComparator()
        {
            super(true);
        }

        @Override
        public int compare(IArticle o1, IArticle o2)
        {
            int result = super.compare(o1, o2);
            if (result == 0)
            {
                result = new Integer(o1.hashCode()).compareTo(o2.hashCode());
            }

            return result;
        }
    }


    // ------------------------------------------------------------------------
    // Duplicates Checking
    // ------------------------------------------------------------------------

    /**
     * Returns <code>TRUE</code> if remove duplicates is enabled.
     *
     * @return <code>TRUE</code> if remove duplicates is enabled.
     */
    public boolean isDedupEnabled()
    {
        return dedupEnabled;
    }

    /**
     * Sets remove duplicates flag.
     *
     * @param flag <code>TRUE</code> if remove duplicates is enabled.
     */
    public void setDedupEnabled(boolean flag)
    {
        boolean old = dedupEnabled;
        if (old == flag) return;
        dedupEnabled = flag;

        firePropertyChanged(PROP_DEDUP_ENABLED, old, flag, true, false);
    }

    /**
     * Returns the first word to look for duplicates.
     *
     * @return word number.
     */
    public int getDedupFrom()
    {
        return dedupFrom;
    }

    /**
     * Sets the first word to look for duplicates.
     *
     * @param word number
     */
    public void setDedupFrom(int word)
    {
        int old = dedupFrom;
        dedupFrom = word;

        firePropertyChanged(PROP_DEDUP_FROM, old, word, true, false);
    }

    /**
     * Returns the last word to look for duplicates.
     *
     * @return word number.
     */
    public int getDedupTo()
    {
        return dedupTo;
    }

    /**
     * Sets the last word to look for duplicates.
     *
     * @param word number
     */
    public void setDedupTo(int word)
    {
        int old = dedupTo;
        dedupTo = word;

        firePropertyChanged(PROP_DEDUP_TO, old, word, true, false);
    }

    /**
     * Updates all dedup properties at once and fires an additional event
     * if any of them have changed.
     *
     * @param enabled   <code>TRUE</code> to enable.
     * @param from      the index of the first word.
     * @param to        the index of the last word.
     */
    public void setDedupProperties(boolean enabled, int from, int to)
    {
        setDedupProperties(enabled, from, to, true);
    }

    /**
     * Updates all dedup properties at once and fires an additional event
     * if any of them have changed.
     *
     * @param enabled   <code>TRUE</code> to enable.
     * @param from      the index of the first word.
     * @param to        the index of the last word.
     * @param fireEvent <code>TRUE</code> to fire deduplication event. We may not need it
     *                  during the batch update, to save a trouble of rescanning feeds
     *                  more than once.
     */
    public void setDedupProperties(boolean enabled, int from, int to, boolean fireEvent)
    {
        boolean oldEnabled = isDedupEnabled();
        int oldFrom = getDedupFrom();
        int oldTo = getDedupTo();

        setDedupEnabled(enabled);
        setDedupFrom(from);
        setDedupTo(to);

        if (fireEvent &&
            (oldEnabled != isDedupEnabled() ||
            oldFrom != getDedupFrom() ||
            oldTo != getDedupTo()))
        {
            firePropertyChanged(PROP_DEDUP_UPDATED, false, true);
        }
    }
}
TOP

Related Classes of com.salas.bb.domain.SearchFeed$NoDupArticleDateComparator

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.