// 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: AbstractGuide.java,v 1.34 2007/04/17 07:03:23 spyromus Exp $
//
package com.salas.bb.domain;
import com.salas.bb.domain.events.FeedRemovedEvent;
import com.salas.bb.utils.CommonUtils;
import com.salas.bb.utils.i18n.Strings;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* <p>Abstract implementation of <code>IGuide</code> inerface. This implementation
* takes care of work with listeners and exposes the methods to fire all events
* defined in these listeners.</p>
*
* <p>This implementation also provides the services to store title and icon key.
* If sub-classes do not agree with that, they can override appropriate methods and
* control the titles and icons themselves.</p>
*
* <p>Some default values for holded feeds are being stored in the guides. This
* abstract implementation holds: update period, automatic feeds discovery flag value.</p>
*/
public abstract class AbstractGuide implements IGuide
{
/** ID of the guide in database. It equals to (-1) by default. */
private long id;
/** The listeners of the guide. */
protected final List<IGuideListener> listeners;
/** Title of the guide. */
private String title;
/** Icon key association with the guide. */
private String iconKey;
/**
* Flag of publishing state. When it's set and the publishing title is set,
* publishing happens.
*/
private boolean publishingEnabled;
/** The title of the guide when published. */
private String publishingTitle;
/** The tags associated with this guide when published. */
private String publishingTags;
/** The flag of publishing state. When set, shows that the publication is public. */
private boolean publishingPublic;
/** The URL of the published guide. */
private String publishingURL;
/** The time of last publishing. */
private long lastPublishingTime;
/** The rating necessary to publish a feed. */
private int publishingRating;
/**
* Automatical feed links discovery flag. When this flag is set the links
* from new articles of all contained feed will be automatically passed to
* discovery service.
*/
private boolean autoFeedsDiscovery;
/**
* Time time indicates the last time when the last of the properties involved in
* synchronization routines was updated.
*/
private long lastUpdateTime;
/** The flag showing whether the notifications are enabled or not. */
private boolean notificationsAllowed;
/** The flag showing whether this guide is accessible from mobile. */
private boolean mobile;
/**
* Creates the guide.
*/
public AbstractGuide()
{
listeners = new CopyOnWriteArrayList<IGuideListener>();
autoFeedsDiscovery = false;
id = -1;
publishingEnabled = false;
publishingPublic = false;
lastPublishingTime = -1;
lastUpdateTime = -1;
publishingRating = 0;
notificationsAllowed = true;
mobile = false;
}
/**
* Returns title of the guide.
*
* @return title.
*/
public String getTitle()
{
return title;
}
/**
* Sets new title of the guide.
*
* @param aTitle title.
*/
public void setTitle(String aTitle)
{
String oldTitle = title;
title = aTitle;
firePropertyChanged(PROP_TITLE, oldTitle, title);
}
/**
* Returns key of associated icon.
*
* @return icon key.
*/
public String getIconKey()
{
return iconKey;
}
/**
* Changes the key of icon.
*
* @param key new icon key.
*/
public void setIconKey(String key)
{
String oldKey = iconKey;
iconKey = key;
firePropertyChanged(PROP_ICON_KEY, oldKey, iconKey, true);
}
/**
* Returns the time of last properties update. This time is necessary for the synchronization
* engine to learn what object is newer.
*
* @return the time or <code>-1</code> if not updated yet.
*/
public long getLastUpdateTime()
{
return lastUpdateTime;
}
/**
* Sets the time of last properties update. When the user changes some property this time is set
* automatically. This method is necessary for persistence layer to init the object with what is
* currently in the database.
*
* @param time time.
*/
public void setLastUpdateTime(long time)
{
long oldValue = lastUpdateTime;
lastUpdateTime = time;
firePropertyChanged(PROP_LAST_UPDATE_TIME, oldValue, time);
}
/** Removes every reading list and feed associated with this guide. */
public void removeChildren()
{
remove(getFeeds());
}
/**
* Returns TRUE if automatic new articles scanning is enabled.
*
* @return TRUE if automatic new articles scanning is enabled.
*/
public boolean isAutoFeedsDiscovery()
{
return autoFeedsDiscovery;
}
/**
* Sets the value of <code>autoFeedsDiscovery</code> flag. When set all links
* from new articles in contained feed will be passed to discovery engine
* automatically.
*
* @param value TRUE for auto-discovery.
*/
public void setAutoFeedsDiscovery(boolean value)
{
boolean oldValue = autoFeedsDiscovery;
autoFeedsDiscovery = value;
firePropertyChanged(PROP_AUTO_FEEDS_DISCOVERY, oldValue, value, true);
}
/**
* Returns <code>TRUE</code> if this guide is mobile.
*
* @return <code>TRUE</code> if this guide is mobile.
*/
public boolean isMobile()
{
return mobile;
}
/**
* Sets the mobility state of the guide.
*
* @param value <code>TRUE</code> to make it mobile.
*/
public void setMobile(boolean value)
{
boolean oldValue = mobile;
mobile = value;
firePropertyChanged(PROP_MOBILE, oldValue, value, true);
}
/**
* Returns all feeds (data and virtual) having the specified XML URL.
*
* @param xmlURL XML URL.
* @param includeDisabled <code>TRUE</code> to include disabled feeds.
*
* @return collection of feeds.
*
* @throws NullPointerException if URL is not specified.
*/
public synchronized Collection<NetworkFeed> findFeedsByXmlURL(URL xmlURL, boolean includeDisabled)
{
if (xmlURL == null) throw new NullPointerException(Strings.error("unspecified.url"));
String xmlURLS = xmlURL.toString();
List<NetworkFeed> feeds = new ArrayList<NetworkFeed>();
// Spin through all feeds and try to find all with similar URL
int count = getFeedsCount();
for (int i = 0; i < count; i++)
{
IFeed feed = getFeedAt(i);
// Only network feeds have XML URL
if (feed instanceof NetworkFeed)
{
NetworkFeed nfeed = (NetworkFeed)feed;
URL feedURL = nfeed.getXmlURL();
if (feedURL != null && feedURL.toString().equalsIgnoreCase(xmlURLS))
{
if (includeDisabled || !(feed instanceof DirectFeed) ||
!((DirectFeed)feed).isDisabled())
{
feeds.add(nfeed);
}
}
}
}
return feeds;
}
/**
* Returns the read status of this guide. The status depends on the statuses of contained
* feeds.
*/
public synchronized boolean isRead()
{
// TODO replace this with caching of read state based on events from feeds.
boolean read = true;
int count = getFeedsCount();
for (int i = 0; read && i < count; i++)
{
read = getFeedAt(i).isRead();
}
return read;
}
/**
* Marks whole guide as read/unread depending on the argument. Iterates through all feeds and
* makes them read/unread.
*/
public synchronized void setRead(boolean read)
{
int count = getFeedsCount();
for (int i = 0; i < count; i++)
{
getFeedAt(i).setRead(read);
}
}
/**
* Adds guide listener.
*
* @param listener listener.
*
* @throws NullPointerException if listener isn't specified.
*/
public void addListener(IGuideListener listener)
{
if (listener == null) throw new NullPointerException(Strings.error("unspecified.listener"));
if (!listeners.contains(listener)) listeners.add(listener);
}
/**
* Removes guide listener.
*
* @param listener listener.
*
* @throws NullPointerException if listener isn't specified.
*/
public void removeListener(IGuideListener listener)
{
if (listener == null) throw new NullPointerException(Strings.error("unspecified.listener"));
listeners.remove(listener);
}
/**
* Fires <code>feedAdded</code> event.
*
* @param feed feed that was added.
*
* @throws NullPointerException if feed isn't specified.
*/
protected void fireFeedAdded(IFeed feed)
{
if (feed == null) throw new NullPointerException(Strings.error("unspecified.feed"));
for (IGuideListener listener : listeners) listener.feedAdded(this, feed);
}
/**
* Fires <code>feedLinkAdded</code> event.
*
* @param feed feed that was added.
*
* @throws NullPointerException if feed isn't specified.
*/
protected void fireFeedLinkAdded(IFeed feed)
{
if (feed == null) throw new NullPointerException(Strings.error("unspecified.feed"));
for (IGuideListener listener : listeners) listener.feedLinkAdded(this, feed);
}
/**
* Fires <code>feedRemoved</code> event.
*
* @param feed feed that was removed.
* @param index index of removed feed.
* @param lastInBatch <code>TRUE</code> if this removal was last in batch.
*
* @throws NullPointerException if feed isn't specified.
*/
protected void fireFeedRemoved(IFeed feed, int index, boolean lastInBatch)
{
if (feed == null) throw new NullPointerException(Strings.error("unspecified.feed"));
FeedRemovedEvent event = null;
for (IGuideListener listener : listeners)
{
if (event == null) event = new FeedRemovedEvent(this, feed, index, lastInBatch);
listener.feedRemoved(event);
}
}
/**
* Fires <code>feedRepositioned</code> event.
*
* @param feed feed that was moved.
* @param oldIndex old index of the feed.
* @param newIndex new index of the feed.
*
* @throws NullPointerException if feed isn't specified.
*/
protected void fireFeedRepositioned(IFeed feed, int oldIndex, int newIndex)
{
if (feed == null) throw new NullPointerException(Strings.error("unspecified.feed"));
for (IGuideListener listener : listeners) listener.feedRepositioned(this, feed, oldIndex, newIndex);
}
/**
* Fires <code>feedRemoved</code> event.
*
* @param feed feed that was removed.
*
* @throws NullPointerException if feed isn't specified.
*/
protected void fireFeedLinkRemoved(IFeed feed)
{
if (feed == null) throw new NullPointerException(Strings.error("unspecified.feed"));
for (IGuideListener listener : listeners) listener.feedLinkRemoved(this, feed);
}
/**
* Fires <code>propertyChanged</code> event.
*
* @param property property name.
* @param oldValue old value.
* @param newValue new value.
*
* @throws NullPointerException if property name isn't specified.
*/
protected void firePropertyChanged(String property, Object oldValue, Object newValue)
{
firePropertyChanged(property, oldValue, newValue, false);
}
/**
* Fires <code>propertyChanged</code> event.
*
* @param property property name.
* @param oldValue old value.
* @param newValue new value.
* @param syncProperty <code>TRUE</code> when the property is involved in sync.
*
* @throws NullPointerException if property name isn't specified.
*/
protected void firePropertyChanged(String property, Object oldValue, Object newValue,
boolean syncProperty)
{
if (property == null) throw new NullPointerException(Strings.error("unspecified.property"));
if (!CommonUtils.areDifferent(oldValue, newValue)) return;
if (syncProperty) setLastUpdateTime(System.currentTimeMillis());
for (IGuideListener listener : listeners) listener.propertyChanged(this, property, oldValue, newValue);
}
/**
* Returns title of the guide as it's string representation.
*
* @return string representation.
*/
public String toString()
{
// The title is necessary to be returned here because some
// comboboxes use this method to get the text for the items.
return getTitle();
}
/**
* Returns ID of the guide. This ID is used by persistence layer to identify record in
* database.
*
* @return ID of the guide.
*/
public long getID()
{
return id;
}
/**
* Sets the ID of the guide.
*
* @param aId ID of the guide.
*/
public void setID(long aId)
{
id = aId;
}
/**
* Cleans all contained data feeds.
*/
public void clean()
{
IFeed[] feeds = getFeeds();
for (IFeed feed : feeds) if (feed instanceof DataFeed) ((DataFeed)feed).clean();
}
// ---------------------------------------------------------------------------------------------
// Publishing
// ---------------------------------------------------------------------------------------------
/**
* Returns <code>TRUE</code> if publishing for this guide is enabled.
*
* @return <code>TRUE</code> if publishing for this guide is enabled.
*/
public boolean isPublishingEnabled()
{
return publishingEnabled;
}
/**
* Set <code>TRUE</code> to enable publishing of this guide. Note that the publishing may still
* not work if the publishing title is not set.
*
* @param enabled <code>TRUE</code> to enable.
*/
public void setPublishingEnabled(boolean enabled)
{
boolean oldValue = publishingEnabled;
publishingEnabled = enabled;
firePropertyChanged(PROP_PUBLISHING_ENABLED, oldValue, enabled, true);
}
/**
* Returns <code>TRUE</code> if the published guide will be visible to everyone.
*
* @return <code>TRUE</code> if the published guide will be visible to everyone.
*/
public boolean isPublishingPublic()
{
return publishingPublic;
}
/**
* Sets the state of publication. When <code>TRUE</code> the guide will be
* visible to everyone when published.
*
* @param flag <code>TRUE</code> to make it public.
*/
public void setPublishingPublic(boolean flag)
{
boolean oldValue = publishingPublic;
publishingPublic = flag;
firePropertyChanged(PROP_PUBLISHING_PUBLIC, oldValue, flag, true);
}
/**
* Returns the list of tags entered for this guide to be published.
*
* @return the list of tags.
*/
public String getPublishingTags()
{
return publishingTags;
}
/**
* Sets the tags used when publishing.
*
* @param tags tags.
*/
public void setPublishingTags(String tags)
{
String oldValue = publishingTags;
publishingTags = tags;
firePropertyChanged(PROP_PUBLISHING_TAGS, oldValue, tags, true);
}
/**
* Returns the title of this guide when published.
*
* @return the title.
*/
public String getPublishingTitle()
{
return publishingTitle;
}
/**
* Sets the title used when publishing.
*
* @param title the title.
*/
public void setPublishingTitle(String title)
{
String oldValue = publishingTitle;
publishingTitle = title;
firePropertyChanged(PROP_PUBLISHING_TITLE, oldValue, title, true);
}
/**
* Returns the URL which is assigned to the published guide.
*
* @return the URL which is assigned to the published guide.
*/
public String getPublishingURL()
{
return publishingURL;
}
/**
* Sets the URL which is assigned to the published guide.
*
* @param url the URL which is assigned to the published guide.
*/
public void setPublishingURL(String url)
{
String oldValue = publishingURL;
publishingURL = url;
firePropertyChanged(PROP_PUBLISHING_URL, oldValue, url);
}
/**
* Returns the time of last publishing.
*
* @return the time or <code>-1</code> if never happened.
*/
public long getLastPublishingTime()
{
return lastPublishingTime;
}
/**
* Sets the time of last publishing.
*
* @param time the time.
*/
public void setLastPublishingTime(long time)
{
long oldValue = lastPublishingTime;
lastPublishingTime = time;
firePropertyChanged(PROP_LAST_PUBLISHING_TIME, oldValue, time);
}
/**
* Return minimum rating necessary to publish feeds.
*
* @return minimum rating.
*/
public int getPublishingRating()
{
return publishingRating;
}
/**
* Sets minimum rating necessary to publish feeds.
*
* @param rating minimum rating.
*/
public void setPublishingRating(int rating)
{
int oldVal = publishingRating;
publishingRating = rating;
firePropertyChanged(PROP_PUBLISHING_RATING, oldVal, rating, true);
}
/**
* Compares this guide with the other guide object.
*
* @param o other guide.
*
* @return TRUE if equivalenrt.
*/
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final AbstractGuide that = (AbstractGuide)o;
if (autoFeedsDiscovery != that.autoFeedsDiscovery) return false;
if (iconKey != null ? !iconKey.equals(that.iconKey) : that.iconKey != null) return false;
return !(title != null ? !title.equals(that.title) : that.title != null);
}
/**
* Returns hash code of this guide object.
*
* @return hash code.
*/
public int hashCode()
{
return title == null ? 0 : title.hashCode();
}
/**
* Returns <code>TRUE</code> if notifications about new articles and feeds in this guide
* are enabled.
*
* @return <code>TRUE</code> if notifications about new articles and feeds in this guide
* are enabled.
*/
public boolean isNotificationsAllowed()
{
return notificationsAllowed;
}
/**
* Sets the state of notifications flag.
*
* @param flag <code>TRUE</code> to enable notifications of changes.
*/
public void setNotificationsAllowed(boolean flag)
{
boolean oldValue = notificationsAllowed;
notificationsAllowed = flag;
firePropertyChanged(PROP_NOTIFICATIONS_ALLOWED, oldValue, flag, true);
}
/**
* Returns the mask of a feed meta-classes.
*
* @return mask.
*/
public int getClassesMask()
{
return GuideClassifier.classify(this);
}
}