/*
* 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.DateTimeFormatters;
import de.huxhorn.lilith.data.access.AccessEvent;
import de.huxhorn.lilith.data.eventsource.SourceIdentifier;
import de.huxhorn.lilith.data.logging.LoggingEvent;
import de.huxhorn.lilith.engine.FileBufferFactory;
import de.huxhorn.lilith.engine.LogFileFactory;
import de.huxhorn.sulky.buffers.Buffer;
import de.huxhorn.sulky.formatting.HumanReadable;
import de.huxhorn.sulky.swing.KeyStrokes;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.Serializable;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.border.EtchedBorder;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class OpenPreviousDialog
extends JDialog
{
private static final long serialVersionUID = 8731011406364451059L;
private final Logger logger = LoggerFactory.getLogger(OpenPreviousDialog.class);
private enum EventType
{
LOGGING("Logging"), ACCESS("Access");
private String typeName;
EventType(String typeName)
{
this.typeName = typeName;
}
public String getTypeName()
{
return typeName;
}
}
private static final String[] EMPTY_STRING_ARRAY = new String[]{};
private static final SourceIdentifierWrapper[] EMPTY_SECONDARY_ARRAY = new SourceIdentifierWrapper[]{};
private MainFrame mainFrame;
private OpenAction openAction;
private JTabbedPane tabbedPane;
private OpenPreviousPanel<LoggingEvent> loggingPanel;
private OpenPreviousPanel<AccessEvent> accessPanel;
public OpenPreviousDialog(MainFrame owner)
{
super(owner, "Open previous log…");
this.mainFrame = owner;
createUI();
}
private void createUI()
{
openAction = new OpenAction();
CancelAction cancelAction = new CancelAction();
tabbedPane = new JTabbedPane();
loggingPanel = new OpenPreviousPanel<>(mainFrame.getLoggingFileBufferFactory(), EventType.LOGGING);
accessPanel = new OpenPreviousPanel<>(mainFrame.getAccessFileBufferFactory(), EventType.ACCESS);
tabbedPane.add(loggingPanel.getPanelName(), loggingPanel);
tabbedPane.add(accessPanel.getPanelName(), accessPanel);
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonPanel.add(new JButton(openAction));
buttonPanel.add(new JButton(cancelAction));
JPanel contentPane = new JPanel(new BorderLayout());
KeyStrokes.registerCommand(contentPane, openAction, "OPEN_ACTION");
KeyStrokes.registerCommand(contentPane, cancelAction, "CANCEL_ACTION");
contentPane.add(tabbedPane, BorderLayout.CENTER);
contentPane.add(buttonPanel, BorderLayout.SOUTH);
setContentPane(contentPane);
tabbedPane.addChangeListener(new TabChangeListener());
}
public void setVisible(boolean b)
{
if(b)
{
initUI();
}
super.setVisible(b);
}
private void initUI()
{
loggingPanel.initUI();
accessPanel.initUI();
initOpenAction();
}
private void initOpenAction()
{
Component comp = tabbedPane.getSelectedComponent();
if(comp instanceof OpenPreviousPanel)
{
OpenPreviousPanel panel = (OpenPreviousPanel) comp;
SourceIdentifierWrapper selectedSource = panel.getSelectedSourceWrapper();
openAction.setEnabled(selectedSource != null);
}
}
public void openSelection()
{
Component comp = tabbedPane.getSelectedComponent();
if(comp instanceof OpenPreviousPanel)
{
OpenPreviousPanel panel = (OpenPreviousPanel) comp;
SourceIdentifierWrapper selectedSource = panel.getSelectedSourceWrapper();
if(selectedSource != null)
{
if(panel.getEventType() == EventType.LOGGING)
{
mainFrame.openPreviousLogging(selectedSource.getSourceIdentifier());
}
else
{
mainFrame.openPreviousAccess(selectedSource.getSourceIdentifier());
}
}
}
OpenPreviousDialog.this.setVisible(false);
}
private class TabChangeListener
implements ChangeListener
{
public void stateChanged(ChangeEvent e)
{
if(logger.isDebugEnabled()) logger.debug("stateChanged");
initOpenAction();
}
}
private class OpenAction
extends AbstractAction
{
private static final long serialVersionUID = -7076284393995744935L;
OpenAction()
{
super("Open");
KeyStroke accelerator = LilithKeyStrokes.getKeyStroke(LilithKeyStrokes.ENTER);
putValue(Action.ACCELERATOR_KEY, accelerator);
}
public void actionPerformed(ActionEvent e)
{
openSelection();
OpenPreviousDialog.this.setVisible(false);
}
}
private class CancelAction
extends AbstractAction
{
private static final long serialVersionUID = -3717298306270939316L;
CancelAction()
{
super("Cancel");
KeyStroke accelerator = LilithKeyStrokes.getKeyStroke(LilithKeyStrokes.ESCAPE);
putValue(Action.ACCELERATOR_KEY, accelerator);
}
public void actionPerformed(ActionEvent e)
{
OpenPreviousDialog.this.setVisible(false);
}
}
private class OpenPreviousPanel<T extends Serializable>
extends JPanel
{
private static final long serialVersionUID = 1635486188020609000L;
private List<List<SourceIdentifierWrapper>> secondaries;
private JList<String> primaryList;
private JList<SourceIdentifierWrapper> secondaryList;
private JTextArea infoArea;
private DecimalFormat eventCountFormat;
private final FileBufferFactory<T> fileBufferFactory;
private final LogFileFactory logFileFactory;
private EventType eventType;
private SourceIdentifierWrapper selectedSourceWrapper;
OpenPreviousPanel(FileBufferFactory<T> fileBufferFactory, EventType eventType)
{
this.fileBufferFactory = fileBufferFactory;
this.logFileFactory = fileBufferFactory.getLogFileFactory();
this.eventType = eventType;
eventCountFormat = new DecimalFormat("#,###", new DecimalFormatSymbols(Locale.US));
this.createUI();
}
public SourceIdentifierWrapper getSelectedSourceWrapper()
{
return selectedSourceWrapper;
}
public String getPanelName()
{
return eventType.getTypeName();
}
public EventType getEventType()
{
return eventType;
}
private void setSelectedSourceWrapper(SourceIdentifierWrapper selected)
{
if(logger.isDebugEnabled()) logger.debug("Selected source: {}", selected);
this.selectedSourceWrapper = selected;
initOpenAction();
updateInfoArea();
}
private void createUI()
{
primaryList = new JList<>();
secondaryList = new JList<>();
primaryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
primaryList.addListSelectionListener(new PrimaryListSelectionListener());
secondaryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
secondaryList.addListSelectionListener(new SecondaryListSelectionListener());
secondaryList.addMouseListener(new SecondaryMouseListener());
JScrollPane primaryPane = new JScrollPane(primaryList);
primaryPane.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED), "Primary"));
primaryPane.setPreferredSize(new Dimension(200, 300));
JScrollPane secondaryPane = new JScrollPane(secondaryList);
secondaryPane.setPreferredSize(new Dimension(300, 300));
secondaryPane.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED), "Secondary"));
JSplitPane filePane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, primaryPane, secondaryPane);
setLayout(new BorderLayout());
add(filePane, BorderLayout.CENTER);
JPanel infoPanel = new JPanel(new GridLayout(1, 1));
infoArea = new JTextArea(3, 40);
infoArea.setEditable(false);
infoPanel.add(infoArea);
infoPanel.setBorder(new TitledBorder(new EtchedBorder(EtchedBorder.LOWERED), "Log Information"));
add(infoPanel, BorderLayout.SOUTH);
}
public void initUI()
{
List<SourceIdentifier> inactiveLogs = mainFrame.collectInactiveLogs(logFileFactory);
ApplicationPreferences applicationPreferences = mainFrame.getApplicationPreferences();
Map<String, String> sourceNames = null;
if(applicationPreferences != null)
{
sourceNames = applicationPreferences.getSourceNames();
}
SortedMap<String, List<SourceIdentifierWrapper>> inactiveMap = new TreeMap<>();
for(SourceIdentifier current : inactiveLogs)
{
String primary = ViewActions.getPrimarySourceTitle(current.getIdentifier(), sourceNames, false);
List<SourceIdentifierWrapper> sourceList = inactiveMap.get(primary);
if(sourceList == null)
{
sourceList = new ArrayList<>();
inactiveMap.put(primary, sourceList);
}
long numberOfEvents = logFileFactory.getNumberOfEvents(current);
long sizeOnDisk = logFileFactory.getSizeOnDisk(current);
File dataFile = logFileFactory.getDataFile(current);
long lastModified = dataFile.lastModified();
Buffer<?> buffer=fileBufferFactory.createBuffer(current);
String applicationName=ViewActions.resolveApplicationName(buffer);
SourceIdentifierWrapper wrapper = new SourceIdentifierWrapper(current, sizeOnDisk, lastModified, numberOfEvents, applicationName);
sourceList.add(wrapper);
}
int primaryCount = inactiveMap.size();
if(primaryCount > 0)
{
ArrayList<String> primaries = new ArrayList<>(primaryCount);
secondaries = new ArrayList<>(primaryCount);
for(Map.Entry<String, List<SourceIdentifierWrapper>> current : inactiveMap.entrySet())
{
primaries.add(current.getKey());
List<SourceIdentifierWrapper> value = current.getValue();
Collections.sort(value);
secondaries.add(value);
}
primaryList.setListData(primaries.toArray(new String[primaries.size()]));
SourceIdentifierWrapper[] currentSecondary = EMPTY_SECONDARY_ARRAY;
if(!secondaries.isEmpty())
{
List<SourceIdentifierWrapper> zero = secondaries.get(0);
if(!zero.isEmpty())
{
currentSecondary = zero.toArray(new SourceIdentifierWrapper[zero.size()]);
}
}
secondaryList.setListData(currentSecondary);
primaryList.setSelectedIndex(0);
}
else
{
primaryList.setListData(EMPTY_STRING_ARRAY);
secondaryList.setListData(EMPTY_SECONDARY_ARRAY);
}
}
private String getLogInfo(SourceIdentifierWrapper selectedSource) {
if (selectedSource == null) {
return "";
}
ApplicationPreferences applicationPreferences = mainFrame.getApplicationPreferences();
Map<String, String> sourceNames = null;
if (applicationPreferences != null) {
sourceNames = applicationPreferences.getSourceNames();
}
StringBuilder result = new StringBuilder();
SourceIdentifier sourceIdentifier = selectedSource.getSourceIdentifier();
result.append(ViewActions.getPrimarySourceTitle(sourceIdentifier.getIdentifier(), sourceNames, true));
String secondary = sourceIdentifier.getSecondaryIdentifier();
if (secondary != null) {
result.append(" - ").append(sourceIdentifier.getSecondaryIdentifier());
}
result.append("\n");
String applicationName = selectedSource.getApplicationName();
if(applicationName != null && !"".equals(applicationName))
{
result.append("Application: ").append(applicationName);
}
result.append('\n')
.append("Number of events: ")
.append(eventCountFormat.format(selectedSource.getNumberOfEvents())).append("\n")
.append("Size: ")
.append(HumanReadable.getHumanReadableSize(selectedSource.getSizeOnDisk(), true, false))
.append("bytes\n")
.append("Timestamp: ")
.append(DateTimeFormatters.DATETIME_IN_SYSTEM_ZONE_SPACE.format(Instant.ofEpochMilli(selectedSource.getLastModified())));
return result.toString();
}
public void updateInfoArea()
{
infoArea.setText(getLogInfo(selectedSourceWrapper));
}
private class PrimaryListSelectionListener
implements ListSelectionListener
{
public void valueChanged(ListSelectionEvent e)
{
JList source = (JList) e.getSource();
int selectedIndex = source.getSelectedIndex();
if(selectedIndex >= 0 && selectedIndex < secondaries.size())
{
List<SourceIdentifierWrapper> sources = secondaries.get(selectedIndex);
secondaryList.setListData(sources.toArray(new SourceIdentifierWrapper[sources.size()]));
secondaryList.setSelectedIndex(0);
}
else
{
secondaryList.setListData(EMPTY_SECONDARY_ARRAY);
}
}
}
private class SecondaryListSelectionListener
implements ListSelectionListener
{
public void valueChanged(ListSelectionEvent e)
{
SourceIdentifierWrapper selected = secondaryList.getSelectedValue();
int selectedIndex = secondaryList.getSelectedIndex();
if(selectedIndex != -1)
{
Rectangle selectRect = secondaryList.getCellBounds(selectedIndex, selectedIndex);
if(selectRect != null)
{
secondaryList.scrollRectToVisible(selectRect);
}
}
setSelectedSourceWrapper(selected);
}
}
private class SecondaryMouseListener
extends MouseAdapter
{
public void mouseClicked(MouseEvent e)
{
if(e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() > 1)
{
Component component = secondaryList.getComponentAt(e.getPoint());
if(component != null)
{
// double-clicked on actual content... it's already selected...
openSelection();
}
}
}
}
}
private class SourceIdentifierWrapper
implements Comparable<SourceIdentifierWrapper>
{
private final SourceIdentifier sourceIdentifier;
private final long sizeOnDisk;
private final long lastModified;
private final long numberOfEvents;
private final String applicationName;
SourceIdentifierWrapper(SourceIdentifier sourceIdentifier, long sizeOnDisk, long lastModified, long numberOfEvents, String applicationName)
{
this.sourceIdentifier = sourceIdentifier;
this.sizeOnDisk = sizeOnDisk;
this.lastModified = lastModified;
this.numberOfEvents = numberOfEvents;
this.applicationName = applicationName;
}
public SourceIdentifier getSourceIdentifier()
{
return sourceIdentifier;
}
public long getLastModified()
{
return lastModified;
}
public long getSizeOnDisk()
{
return sizeOnDisk;
}
public long getNumberOfEvents()
{
return numberOfEvents;
}
public String getApplicationName()
{
return applicationName;
}
@SuppressWarnings("NullableProblems")
@Override
public int compareTo(SourceIdentifierWrapper other)
{
if(other == null)
{
throw new NullPointerException("other must not be null!");
}
if(lastModified == other.lastModified)
{
return 0;
}
if(lastModified < other.lastModified)
{
return 1;
}
return -1;
}
@Override
public String toString()
{
if(applicationName != null && !"".equals(applicationName))
{
return applicationName;
}
String secondary = sourceIdentifier.getSecondaryIdentifier();
if(secondary != null)
{
return secondary;
}
return "";
}
}
}