/*
* Lilith - a log event viewer.
* Copyright (C) 2007-2017 Joern Huxhorn
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*/
package de.huxhorn.lilith.swing;
import de.huxhorn.lilith.data.eventsource.EventWrapper;
import de.huxhorn.lilith.engine.EventSource;
import de.huxhorn.lilith.engine.impl.EventSourceImpl;
import de.huxhorn.lilith.swing.callables.CallableMetaData;
import de.huxhorn.sulky.buffers.Buffer;
import de.huxhorn.sulky.buffers.DisposeOperation;
import de.huxhorn.sulky.buffers.Flush;
import de.huxhorn.sulky.buffers.FlushOperation;
import de.huxhorn.sulky.buffers.filtering.FilteringBuffer;
import de.huxhorn.sulky.buffers.filtering.FilteringCallable;
import de.huxhorn.sulky.conditions.Condition;
import de.huxhorn.sulky.tasks.ProgressingCallable;
import de.huxhorn.sulky.tasks.Task;
import de.huxhorn.sulky.tasks.TaskListener;
import de.huxhorn.sulky.tasks.TaskManager;
import java.awt.Component;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class ViewContainer<T extends Serializable>
extends JPanel
implements DisposeOperation, FlushOperation
{
private static final long serialVersionUID = 4834209079953596930L;
// TODO: property change instead of change?
static final String SELECTED_EVENT_PROPERTY_NAME = "selectedEvent";
private final List<ChangeListener> changeListeners = new LinkedList<>();
private final MainFrame mainFrame;
private final EventSource<T> eventSource;
private EventWrapperViewPanel<T> defaultView;
private TaskManager<Long> taskManager;
private Map<Callable<Long>, EventWrapperViewPanel<T>> filterMapping;
private FilterTaskListener filterTaskListener;
private ProgressGlassPane progressPanel;
private Component prevGlassPane;
private boolean searching;
private ProgressingCallable<Long> updateCallable;
public ViewContainer(MainFrame mainFrame, EventSource<T> eventSource)
{
this.mainFrame = Objects.requireNonNull(mainFrame, "mainFrame must not be null!");
this.eventSource = Objects.requireNonNull(eventSource, "eventSource must not be null!");
taskManager = mainFrame.getLongWorkManager();
progressPanel = new ProgressGlassPane();
filterMapping = new HashMap<>();
filterTaskListener = new FilterTaskListener();
taskManager.addTaskListener(filterTaskListener);
this.defaultView = createViewPanel(eventSource);
defaultView.addPropertyChangeListener(evt -> {
if (EventWrapperViewPanel.STATE_PROPERTY.equals(evt.getPropertyName()))
{
updateContainerIcon();
}
});
}
public EventSource<T> getEventSource()
{
return eventSource;
}
public MainFrame getMainFrame()
{
return mainFrame;
}
protected abstract EventWrapperViewPanel<T> createViewPanel(EventSource<T> eventSource);
public abstract EventWrapperViewPanel<T> getViewAt(int index);
public abstract EventWrapperViewPanel<T> getSelectedView();
public abstract void addView(EventWrapperViewPanel<T> view);
public abstract void removeView(EventWrapperViewPanel<T> view, boolean dispose);
public abstract void showDefaultView();
public abstract Class getWrappedClass();
EventWrapperViewPanel<T> getDefaultView()
{
return defaultView;
}
void setScrollingSmoothly(boolean scrollingSmoothly)
{
defaultView.setScrollingSmoothly(scrollingSmoothly);
for (EventWrapperViewPanel<T> viewPanel : filterMapping.values())
{
viewPanel.setScrollingSmoothly(scrollingSmoothly);
}
}
LoggingViewState getState()
{
if(defaultView != null)
{
return defaultView.getState();
}
return null;
}
public void dispose()
{
taskManager.removeTaskListener(filterTaskListener);
cancelUpdateTask();
}
public void flush()
{
for(int i=0;i<getViewCount();i++)
{
Flush.flush(getViewAt(i));
}
}
public void applyCondition(Condition condition, ActionEvent e)
{
if(condition == null)
{
// simply do nothing
return;
}
EventWrapperViewPanel<T> selectedView = getSelectedView();
if(selectedView == null)
{
// simply do nothing
return;
}
Condition previousCondition = selectedView.getBufferCondition();
Condition filter = selectedView.getCombinedCondition(condition);
if (filter == null || filter.equals(previousCondition))
{
return;
}
ApplicationPreferences applicationPreferences = getMainFrame().getApplicationPreferences();
if(applicationPreferences.isReplacingOnApply(e))
{
replaceFilteredView(selectedView, filter);
}
else
{
addFilteredView(selectedView, filter);
}
}
void addFilteredView(EventWrapperViewPanel<T> original, Condition filter)
{
Buffer<EventWrapper<T>> originalBuffer = original.getSourceBuffer();
FilteringBuffer<EventWrapper<T>> filteredBuffer = new FilteringBuffer<>(originalBuffer, filter);
FilteringCallable<EventWrapper<T>> callable = new FilteringCallable<>(filteredBuffer, 500);
EventSource<T> originalEventSource = original.getEventSource();
Map<String, String> metaData = CallableMetaData.createFilteringMetaData(filter, originalEventSource);
EventSourceImpl<T> newEventSource = new EventSourceImpl<>(originalEventSource.getSourceIdentifier(), filteredBuffer, filter, originalEventSource.isGlobal());
EventWrapperViewPanel<T> newViewPanel = createViewPanel(newEventSource);
filterMapping.put(callable, newViewPanel);
addView(newViewPanel);
taskManager.startTask(callable, "Filtering", createFilteringMessage(metaData), metaData);
}
void replaceFilteredView(EventWrapperViewPanel<T> original, Condition filter)
{
EventSource<T> eventSource = original.getEventSource();
Buffer<EventWrapper<T>> buffer = eventSource.getBuffer();
if (buffer instanceof FilteringBuffer)
{
// replace
Callable<Long> found = null;
for (Map.Entry<Callable<Long>, EventWrapperViewPanel<T>> current : filterMapping.entrySet())
{
if (current.getValue() == original)
{
found = current.getKey();
break;
}
}
if (found != null)
{
// remove previous and cancel the task
filterMapping.remove(found);
Task<Long> task = taskManager.getTaskByCallable(found);
if (task != null)
{
task.getFuture().cancel(true);
}
// create new EventSource
Buffer<EventWrapper<T>> originalBuffer = original.getSourceBuffer();
FilteringBuffer<EventWrapper<T>> filteredBuffer = new FilteringBuffer<>(originalBuffer, filter);
FilteringCallable<EventWrapper<T>> callable = new FilteringCallable<>(filteredBuffer, 500);
EventSource<T> originalEventSource = original.getEventSource();
Map<String, String> metaData = CallableMetaData.createFilteringMetaData(filter, originalEventSource);
EventSourceImpl<T> newEventSource = new EventSourceImpl<>(originalEventSource.getSourceIdentifier(), filteredBuffer, filter, originalEventSource.isGlobal());
original.setEventSource(newEventSource);
// restore mapping of original view, this time with the new callable
filterMapping.put(callable, original);
// start the new task.
taskManager.startTask(callable, "Filtering", createFilteringMessage(metaData), metaData);
}
}
else
{
// create new
addFilteredView(original, filter);
}
}
private static String createFilteringMessage(Map<String, String> metaData)
{
return "Filtering " + metaData.get(CallableMetaData.FIND_TASK_META_SOURCE_IDENTIFIER) + ".\n\n" +
metaData.get(CallableMetaData.FIND_TASK_META_CONDITION);
}
public ViewWindow resolveViewWindow()
{
Container parent = getParent();
while (parent != null && !(parent instanceof ViewWindow))
{
parent = parent.getParent();
}
return (ViewWindow) parent;
}
private void updateContainerIcon()
{
ViewWindow window = resolveViewWindow();
if (window instanceof JFrame)
{
JFrame frame = (JFrame) window;
updateFrameIcon(frame);
}
else if (window instanceof JInternalFrame)
{
JInternalFrame frame = (JInternalFrame) window;
updateInternalFrameIcon(frame);
}
}
private void updateFrameIcon(JFrame frame)
{
frame.setIconImages(Icons.resolveFrameIconImages(defaultView.getState(), false));
}
private void updateInternalFrameIcon(JInternalFrame internalFrame)
{
internalFrame.setFrameIcon(Icons.resolveFrameIcon(defaultView.getState(), false));
internalFrame.repaint(); // Apple L&F Bug workaround
}
public void addNotify()
{
super.addNotify();
updateContainerIcon();
}
void addChangeListener(ChangeListener listener)
{
boolean changed = false;
synchronized (changeListeners)
{
if (!changeListeners.contains(listener))
{
changeListeners.add(listener);
changed = true;
}
}
if (changed)
{
fireChange();
}
}
void removeChangeListener(ChangeListener listener)
{
boolean changed = false;
synchronized (changeListeners)
{
if (changeListeners.contains(listener))
{
changeListeners.remove(listener);
changed = true;
}
}
if (changed)
{
fireChange();
}
}
public void fireChange()
{
ArrayList<ChangeListener> clone;
synchronized (changeListeners)
{
clone = new ArrayList<>(changeListeners);
}
ChangeEvent event = new ChangeEvent(this);
for (ChangeListener listener : clone)
{
listener.stateChanged(event);
}
}
public void setPreviousSearchStrings(List<String> previousSearchStrings)
{
for(int i=0;i<getViewCount();i++)
{
EventWrapperViewPanel<T> view = getViewAt(i);
view.setPreviousSearchStrings(previousSearchStrings);
}
}
public void setConditionNames(List<String> conditionNames)
{
for(int i=0;i<getViewCount();i++)
{
EventWrapperViewPanel<T> view = getViewAt(i);
view.setConditionNames(conditionNames);
}
}
public abstract void updateViewScale(double scale);
public abstract void setShowingStatusBar(boolean showingStatusBar);
void setUpdateCallable(ProgressingCallable<Long> updateCallable)
{
cancelUpdateTask();
this.updateCallable=updateCallable;
if(this.updateCallable != null)
{
taskManager.startTask(this.updateCallable, "Updating: "+this.updateCallable);
getDefaultView().setState(LoggingViewState.UPDATING_FILE);
}
}
private void cancelUpdateTask()
{
if(this.updateCallable != null)
{
Task<Long> task = taskManager.getTaskByCallable(this.updateCallable);
if(task != null)
{
task.getFuture().cancel(true);
}
this.updateCallable=null;
getDefaultView().setState(LoggingViewState.STALE_FILE);
}
}
class FilterTaskListener
implements TaskListener<Long>
{
private final Logger logger = LoggerFactory.getLogger(FilterTaskListener.class);
public void taskCreated(Task<Long> longTask)
{
}
public void executionFailed(Task<Long> task, ExecutionException exception)
{
EventWrapperViewPanel<T> view = filterMapping.get(task.getCallable());
if (view != null)
{
if (logger.isInfoEnabled()) logger.info("Filter execution failed!", exception);
finished(view);
}
if(task.getCallable() == ViewContainer.this.updateCallable)
{
cancelUpdateTask();
}
}
public void executionFinished(Task<Long> task, Long result)
{
EventWrapperViewPanel<T> view = filterMapping.get(task.getCallable());
if (view != null)
{
if (logger.isInfoEnabled()) logger.info("Filter execution finished: {}!", result);
finished(view);
}
if(task.getCallable() == ViewContainer.this.updateCallable)
{
cancelUpdateTask();
}
}
public void executionCanceled(Task<Long> task)
{
EventWrapperViewPanel<T> view = filterMapping.get(task.getCallable());
if (view != null)
{
if (logger.isInfoEnabled()) logger.info("Filter execution canceled.");
finished(view);
}
if(task.getCallable() == ViewContainer.this.updateCallable)
{
cancelUpdateTask();
}
}
public void progressUpdated(Task<Long> task, int progress)
{
}
private void finished(EventWrapperViewPanel<T> view)
{
if (logger.isDebugEnabled()) logger.debug("Executing FilterTaskListener.finished().");
removeView(view, true);
}
}
public abstract void closeCurrentFilter();
public abstract void closeOtherFilters();
public abstract void closeAllFilters();
public abstract int getViewCount();
public abstract void setViewIndex(int newView);
public abstract int getViewIndex();
boolean isSearching()
{
return searching;
}
void cancelSearching()
{
progressPanel.getFindCancelAction().actionPerformed(null);
}
void hideSearchPanel()
{
if(searching)
{
searching = false;
ViewWindow window = resolveViewWindow();
if(window != null && prevGlassPane != null)
{
window.setGlassPane(prevGlassPane);
prevGlassPane = null;
fireChange();
}
}
}
void showSearchPanel(Task<Long> task)
{
if(task != null)
{
searching = true;
progressPanel.setProgress(0);
progressPanel.getFindCancelAction().setTask(task);
ViewWindow window = resolveViewWindow();
if(window != null)
{
prevGlassPane = window.getGlassPane();
window.setGlassPane(progressPanel);
progressPanel.setVisible(true);
}
fireChange();
}
}
ProgressGlassPane getProgressPanel()
{
return progressPanel;
}
public abstract EventWrapper<T> getSelectedEvent();
public abstract void updateViews();
public abstract void scrollToEvent();
}