/***********************************************************************************
*
* Copyright (c) 2014 Kamil Baczkowicz
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
*
* Kamil Baczkowicz - initial API and implementation and/or initial documentation
*
*/
package pl.baczkowicz.spy.ui.storage;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.baczkowicz.spy.formatting.FormattingManager;
import pl.baczkowicz.spy.messages.FormattedMessage;
import pl.baczkowicz.spy.ui.events.queuable.EventQueueManager;
import pl.baczkowicz.spy.ui.events.queuable.ui.BrowseReceivedMessageEvent;
import pl.baczkowicz.spy.ui.events.queuable.ui.TopicSummaryNewMessageEvent;
import pl.baczkowicz.spy.ui.events.queuable.ui.TopicSummaryRemovedMessageEvent;
/**
* The top level message store, handling received messages.
*
* TODO: need to rationalise the BasicMessageStore, FilteredMessageStore and
* MessageStore interface. There are 3 message lists, two in FilteredStore and
* one in BasicMessageStore - probably only need two.
*/
public class ManagedMessageStoreWithFiltering<T extends FormattedMessage> extends BasicMessageStoreWithSummary<T>
{
final static Logger logger = LoggerFactory.getLogger(ManagedMessageStoreWithFiltering.class);
/** All topics this store knows about. */
private final Set<String> allTopics = new HashSet<String>();
private FilteredMessageStore<T> filteredStore;
//protected final EventManager eventManager;
/** Stores events for the UI to be updated. */
protected final EventQueueManager<T> uiEventQueue;
public ManagedMessageStoreWithFiltering(final String name, final int minMessagesPerTopic, final int preferredSize, final int maxSize,
final EventQueueManager<T> uiEventQueue, /*final EventManager eventManager, */final FormattingManager formattingManager,
final int maxPayloadLength)
{
super(name, preferredSize, maxSize, maxPayloadLength, formattingManager);
this.uiEventQueue = uiEventQueue;
//this.eventManager = eventManager;
this.filteredStore = new FilteredMessageStore<T>(super.getMessageList(), preferredSize, maxSize, name,
messageFormat, formattingManager, maxPayloadLength);
// Set up message store garbage collectors
this.filteredStore.setMessageStoreGarbageCollector(
new MessageStoreGarbageCollector<T>(this, filteredStore.getFilteredMessages(), uiEventQueue, minMessagesPerTopic, false, true));
this.setMessageStoreGarbageCollector(
new MessageStoreGarbageCollector<T>(this, super.getMessageList(), uiEventQueue, minMessagesPerTopic, true, false));
new Thread(this.filteredStore.getMessageStoreGarbageCollector()).start();
new Thread(this.getMessageStoreGarbageCollector()).start();
}
/**
* Stores the received message and triggers UI updates. The following
* updates are queued as UI events so that the JavaFX thread is not swamped
* with hundreds or thousands of requests to do Platform.runLater().
*
* @param message Received message
*/
public void messageReceived(final T message)
{
// 0. Format the message with the currently selected formatter
formattingManager.formatMessage(message, getFormatter());
// Record the current state of topics
final boolean allTopicsShown = !browsingFiltersEnabled();
final boolean topicAlreadyExists = allTopics.contains(message.getTopic());
// Start processing the received message...
// 1. Store the topic for the received message
allTopics.add(message.getTopic());
// 2. Add the message to 'all messages' store - oldest could be removed if the store has reached its max size
final T removed = storeMessage(message);
// 3. Add it to the filtered store if:
// - message is not filtered out
// - all messages are shown or the topic is already on the list
if (!filteredStore.filterMessage(message, true) && (allTopicsShown || filteredStore.getBrowsedTopics().contains(message.getTopic())))
{
filteredStore.getFilteredMessages().add(message);
// Message browsing update
uiEventQueue.add(this, new BrowseReceivedMessageEvent<T>(filteredStore.getFilteredMessages(), message));
}
// 4. If the topic doesn't exist yet, add it (e.g. all shown but this is the first message for this topic)
if (allTopicsShown && !topicAlreadyExists)
{
// This doesn't need to trigger 'show first' or sth because the following two UI events should refresh the screen
filteredStore.applyTopicFilter(message.getTopic(), false);
}
// 5. Summary table update - required are: removed message, new message, and whether to show the topic
if (removed != null)
{
uiEventQueue.add(this, new TopicSummaryRemovedMessageEvent<T>(super.getMessageList(), removed));
}
uiEventQueue.add(this, new TopicSummaryNewMessageEvent<T>(super.getMessageList(), message, allTopicsShown && !topicAlreadyExists));
}
@Override
public List<T> getMessages()
{
return filteredStore.getFilteredMessages().getMessages();
}
@Override
public MessageListWithObservableTopicSummary<T> getMessageList()
{
return filteredStore.getFilteredMessages();
}
public MessageListWithObservableTopicSummary<T> getNonFilteredMessageList()
{
return super.getMessageList();
}
public FilteredMessageStore<T> getFilteredMessageStore()
{
return filteredStore;
}
@Override
public boolean browsingFiltersEnabled()
{
return filteredStore.getBrowsedTopics().size() != allTopics.size();
}
@Override
public boolean messageFiltersEnabled()
{
return filteredStore.messageFiltersEnabled();
}
public Collection<String> getAllTopics()
{
return Collections.unmodifiableCollection(allTopics);
}
@Override
public void clear()
{
super.clear();
allTopics.clear();
filteredStore.removeAllTopicFilters();
}
public void setAllShowValues(final boolean show)
{
if (show)
{
filteredStore.addAllTopicFilters();
}
else
{
filteredStore.removeAllTopicFilters();
}
super.getMessageList().getTopicSummary().setAllShowValues(show);
}
public void setShowValues(final boolean show, final Collection<String> topics)
{
synchronized (topics)
{
if (show)
{
filteredStore.applyTopicFilters(topics, true);
}
else
{
filteredStore.removeTopicFilters(topics);
}
super.getMessageList().getTopicSummary().setShowValues(topics, show);
}
}
public void toggleAllShowValues()
{
toggleShowValues(allTopics);
}
public void toggleShowValues(final Collection<String> topics)
{
final Set<String> topicsToAdd = new HashSet<>();
final Set<String> topicsToRemove = new HashSet<>();
synchronized (topics)
{
for (final String topic : topics)
{
if (filteredStore.getBrowsedTopics().contains(topic))
{
topicsToRemove.add(topic);
}
else
{
topicsToAdd.add(topic);
}
}
filteredStore.removeTopicFilters(topicsToRemove);
filteredStore.applyTopicFilters(topicsToAdd, true);
super.getMessageList().getTopicSummary().toggleShowValues(topics);
}
}
public void setShowValue(final String topic, final boolean show)
{
filteredStore.updateTopicFilter(topic, show);
super.getMessageList().getTopicSummary().setShowValue(topic, show);
}
public EventQueueManager<T> getUiEventQueue()
{
return this.uiEventQueue;
}
public FormattingManager getFormattingManager()
{
return formattingManager;
}
public void cleanUp()
{
this.getMessageStoreGarbageCollector().setRunning(false);
this.filteredStore.getMessageStoreGarbageCollector().setRunning(false);
}
}