// 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: DomainEventMulticaster.java,v 1.14 2007/10/04 09:55:07 spyromus Exp $
//
package com.salas.bb.domain.utils;
import com.salas.bb.domain.*;
import com.salas.bb.domain.events.FeedRemovedEvent;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <p>Very fast and thread safe multicaster listener.</p>
*
* <p>There are several great things about this implementation. First of them is that objects
* of this type mimic usual listeners by implementing the same interface. The other is
* that they do not require synchronization when adding / removing other listeners to
* them or firing the events. It happens because the state is always final and if you
* have reference to some object of this type then it's guarantied to be the same forever.</p>
*
* <p>How it mutates then? When you add or remove some listener (the multicaster of this type
* or usual listener) the other branch on the tree is created and the whole tree is returned
* in result. When you remove something the tree gets reorganized and also returned in
* result.</p>
*
* <p>More detailed info may be found in JavaWorld's article
* <a href="http://www.javaworld.com/javaworld/jw-03-1999/jw-03-toolbox_p.html">
* The Observer pattern and mysteries of the AWTEventMulticaster</a>.</p>
*/
public class DomainEventMulticaster implements IDomainListener
{
private static final Logger LOG = Logger.getLogger(DomainEventMulticaster.class.getName());
protected final IDomainListener first, second;
/**
* Creates multicaster.
*
* @param aFirst one listener.
* @param aSecond the other listener.
*/
protected DomainEventMulticaster(IDomainListener aFirst, IDomainListener aSecond)
{
first = aFirst;
second = aSecond;
}
/**
* Adds the first listener to the second listener and returns the sum. If you will
* give only the one from listeners then it will be returned. If you will specify
* two listeners they will be added together by multicaster object.
*
* @param aFirst first listener.
* @param aSecond second listener.
*
* @return multicaster.
*/
public static IDomainListener add(IDomainListener aFirst, IDomainListener aSecond)
{
return aFirst == null
? aSecond
: aSecond == null
? aFirst
: new DomainEventMulticaster(aFirst, aSecond);
}
/**
* Removes listener from the listeners list and returns refreshed listeners list.
* Please note that the list may be both usual listener or multicaster. If it's usual
* listener then nothing really happens because we can remove something only from
* multicaster trees.
*
* @param aList list of listeners (multicaster).
* @param aListener listener to remove.
*
* @return updated multicaster.
*/
public static IDomainListener remove(IDomainListener aList, IDomainListener aListener)
{
if(aList == aListener || aList == null)
{
return null;
} else if (!(aList instanceof DomainEventMulticaster))
{
return aList;
} else return ((DomainEventMulticaster)aList).remove(aListener);
}
private IDomainListener remove(IDomainListener aListener)
{
if (aListener == first) return second;
if (aListener == second) return first;
IDomainListener a2 = remove(first, aListener);
IDomainListener b2 = remove(second, aListener);
return (a2 == first && b2 == second) ? this : add(a2, b2);
}
// ---------------------------------------------------------------------------------------------
// IDomainListener implementation
// ---------------------------------------------------------------------------------------------
/**
* Invoked when new guide has been added to the set.
*
* @param set guides set.
* @param guide added guide.
* @param lastInBatch <code>TRUE</code> when this is the last even in batch.
*/
public void guideAdded(GuidesSet set, IGuide guide, boolean lastInBatch)
{
first.guideAdded(set, guide, lastInBatch);
second.guideAdded(set, guide, lastInBatch);
}
/**
* Invoked when the guide has been moved to a new location in list.
*
* @param set guides set.
* @param guide guide which has been removed.
* @param oldIndex old guide index.
* @param newIndex new guide index.
*/
public void guideMoved(GuidesSet set, IGuide guide, int oldIndex, int newIndex)
{
first.guideMoved(set, guide, oldIndex, newIndex);
second.guideMoved(set, guide, oldIndex, newIndex);
}
/**
* Invoked when the guide has been removed from the set.
*
* @param set guides set.
* @param guide removed guide.
* @param index old guide index.
*/
public void guideRemoved(GuidesSet set, IGuide guide, int index)
{
first.guideRemoved(set, guide, index);
second.guideRemoved(set, guide, index);
}
/**
* Invoked when new feed has been added to the guide.
*
* @param guide parent guide.
* @param feed added feed.
*/
public void feedAdded(IGuide guide, IFeed feed)
{
first.feedAdded(guide, feed);
second.feedAdded(guide, feed);
}
/**
* Invoked when new feed has been added directly to this guide (not through the Reading List).
* In fact, it doesn't mean that the feed should appear in the guide if it's already there. This
* event will be followed by <code>feedAdded</code> event if this is the first addition of this
* feed (not visible yet) and will not, if the feed is already in the list.
*
* @param guide parent guide.
* @param feed added feed.
*/
public void feedLinkAdded(IGuide guide, IFeed feed)
{
first.feedLinkAdded(guide, feed);
second.feedLinkAdded(guide, feed);
}
/**
* Invoked when the feed has been removed from the guide.
*
* @param event feed removal event.
*/
public void feedRemoved(FeedRemovedEvent event)
{
first.feedRemoved(event);
second.feedRemoved(event);
}
/**
* Invoked when the feed has been removed directly from the feed. It has nothing to do with the
* visual representation of the guide because this feed can still be visible in the guide
* because of its presence in one or more associated reading lists. This even simply means that
* there's no direct connection between the guide and the feed.
*
* @param guide guide.
* @param feed removed feed.
*/
public void feedLinkRemoved(IGuide guide, IFeed feed)
{
first.feedLinkRemoved(guide, feed);
second.feedLinkRemoved(guide, feed);
}
/**
* Invoked when feed link property changes its value.
*
* @param guide source guide.
* @param feed feed, who's link property has changed.
* @param property property name.
* @param oldValue old value.
* @param newValue new value.
*/
public void feedLinkPropertyChanged(StandardGuide guide, IFeed feed, String property,
long oldValue, long newValue)
{
first.feedLinkPropertyChanged(guide, feed, property, oldValue, newValue);
second.feedLinkPropertyChanged(guide, feed, property, oldValue, newValue);
}
/**
* Invoked after new reading list is added to the guide.
*
* @param guide source guide.
* @param list reading list added.
*/
public void readingListAdded(IGuide guide, ReadingList list)
{
first.readingListAdded(guide, list);
second.readingListAdded(guide, list);
}
/**
* Invoked after reading list is removed from the guide.
*
* @param guide source guide.
* @param list reading list removed.
*/
public void readingListRemoved(IGuide guide, ReadingList list)
{
first.readingListRemoved(guide, list);
second.readingListRemoved(guide, list);
}
/**
* Invoked when a feed is moved from one position to another.
*
* @param guide source guide.
* @param feed feed moved.
* @param oldPosition old position.
* @param newPosition new position.
*/
public void feedRepositioned(IGuide guide, IFeed feed, int oldPosition, int newPosition)
{
first.feedRepositioned(guide, feed, oldPosition, newPosition);
second.feedRepositioned(guide, feed, oldPosition, newPosition);
}
/**
* Invoked when the property of the guide has been changed.
*
* @param guide guide owning the property.
* @param property property name.
* @param oldValue old property value.
* @param newValue new property value.
*/
public void propertyChanged(IGuide guide, String property, Object oldValue, Object newValue)
{
first.propertyChanged(guide, property, oldValue, newValue);
second.propertyChanged(guide, property, oldValue, newValue);
}
/**
* Invoked when new article has been added to the feed.
*
* @param feed feed.
* @param article article.
*/
public void articleAdded(IFeed feed, IArticle article)
{
first.articleAdded(feed, article);
second.articleAdded(feed, article);
}
/**
* Invoked when a search feed adds some article to its list.
*
* @param feed feed.
* @param article article.
*/
public void articleAddedToSearchFeed(SearchFeed feed, IArticle article)
{
try
{
first.articleAddedToSearchFeed(feed, article);
second.articleAddedToSearchFeed(feed, article);
} catch (Throwable e)
{
// Fallthrough
LOG.log(Level.WARNING, "RTE", e);
}
}
/**
* Invoked when the article has been removed from the feed.
*
* @param feed feed.
* @param article article.
*/
public void articleRemoved(IFeed feed, IArticle article)
{
first.articleRemoved(feed, article);
second.articleRemoved(feed, article);
}
/**
* Invoked when the property of the feed has been changed.
*
* @param feed feed.
* @param property property of the feed.
* @param oldValue old property value.
* @param newValue new property value.
*/
public void propertyChanged(IFeed feed, String property, Object oldValue, Object newValue)
{
first.propertyChanged(feed, property, oldValue, newValue);
second.propertyChanged(feed, property, oldValue, newValue);
}
/**
* 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)
{
first.propertyChanged(article, property, oldValue, newValue);
second.propertyChanged(article, property, oldValue, newValue);
}
/**
* Invoked when new feed has been added to the reading list.
*
* @param list reading list the feed was added to.
* @param feed added feed.
*/
public void feedAdded(ReadingList list, IFeed feed)
{
first.feedAdded(list, feed);
second.feedAdded(list, feed);
}
/**
* Invoked when the feed has been removed from the reading list.
*
* @param list reading list the feed was removed from.
* @param feed removed feed.
*/
public void feedRemoved(ReadingList list, IFeed feed)
{
first.feedRemoved(list, feed);
second.feedRemoved(list, feed);
}
/**
* Invoked when the property of the list has been changed.
*
* @param list list owning the property.
* @param property property name.
* @param oldValue old property value.
* @param newValue new property value.
*/
public void propertyChanged(ReadingList list, String property, Object oldValue, Object newValue)
{
first.propertyChanged(list, property, oldValue, newValue);
second.propertyChanged(list, property, oldValue, newValue);
}
}