/*
* 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.preferences;
import de.huxhorn.lilith.swing.LilithActionId;
import de.huxhorn.lilith.swing.MainFrame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.SortedMap;
import java.util.TreeMap;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class TroubleshootingPanel
extends JPanel
{
private static final long serialVersionUID = 5589305263321629687L;
private static final String LILITH_LOGS_PLACEHOLDER = "##LilithLogsPlaceholder##";
private static final String WINDOW_MENU_PLACEHOLDER = "##WindowMenuPlaceholder##";
private final Logger logger = LoggerFactory.getLogger(TroubleshootingPanel.class);
private PreferencesDialog preferencesDialog;
TroubleshootingPanel(PreferencesDialog preferencesDialog)
{
this.preferencesDialog = preferencesDialog;
createUI();
}
private void createUI()
{
setLayout(new GridBagLayout());
JPanel buttonPanel = new JPanel();
buttonPanel.add(new JButton(new InitDetailsViewAction()));
buttonPanel.add(new JButton(new InitExampleConditionScriptsAction()));
buttonPanel.add(new JButton(new InitExampleClipboardFormatterScriptsAction()));
buttonPanel.add(new JButton(new DeleteAllLogsAction()));
buttonPanel.add(new JButton(new CopySystemPropertiesAction()));
buttonPanel.add(new JButton(new CopyThreadsAction()));
buttonPanel.add(new JButton(new GarbageCollectionAction()));
JPanel messagePanel = new JPanel(new GridLayout(1,1));
JLabel messageField = new JLabel();
messageField.setText(resolveMessage());
messagePanel.add(messageField);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 1;
gbc.weighty = 1;
gbc.fill = GridBagConstraints.BOTH;
gbc.anchor = GridBagConstraints.NORTH;
add(buttonPanel, gbc);
gbc.gridy = 1;
gbc.anchor = GridBagConstraints.SOUTH;
messagePanel.setBorder(new EmptyBorder(10,10,10,10));
add(messagePanel, gbc);
}
static String resolveMessage()
{
final Logger logger = LoggerFactory.getLogger(TroubleshootingPanel.class);
try
{
InputStream messageStream = TroubleshootingPanel.class.getResourceAsStream("/dependencies.message");
if(messageStream == null)
{
if(logger.isErrorEnabled()) logger.error("Failed to get resource dependencies.message!");
}
else
{
String message = IOUtils.toString(messageStream, StandardCharsets.UTF_8);
message = message.replace(WINDOW_MENU_PLACEHOLDER, LilithActionId.WINDOW.getText());
message = message.replace(LILITH_LOGS_PLACEHOLDER, LilithActionId.VIEW_LILITH_LOGS.getText());
return message;
}
}
catch (IOException e)
{
if(logger.isErrorEnabled()) logger.error("Failed to load dependencies.message!", e);
}
return null;
}
public class InitDetailsViewAction
extends AbstractAction
{
private static final long serialVersionUID = 8374235720899930441L;
InitDetailsViewAction()
{
super("Reinitialize details view files.");
}
public void actionPerformed(ActionEvent actionEvent)
{
preferencesDialog.reinitializeDetailsViewFiles();
}
}
public class InitExampleConditionScriptsAction
extends AbstractAction
{
private static final long serialVersionUID = -4197531497673863904L;
InitExampleConditionScriptsAction()
{
super("Reinitialize example groovy conditions.");
}
public void actionPerformed(ActionEvent actionEvent)
{
preferencesDialog.reinitializeGroovyConditions();
}
}
public class InitExampleClipboardFormatterScriptsAction
extends AbstractAction
{
private static final long serialVersionUID = -4197531497673863904L;
InitExampleClipboardFormatterScriptsAction()
{
super("Reinitialize example groovy clipboard formatters.");
}
public void actionPerformed(ActionEvent actionEvent)
{
preferencesDialog.reinitializeGroovyClipboardFormatters();
}
}
public class DeleteAllLogsAction
extends AbstractAction
{
private static final long serialVersionUID = 5218712842261152334L;
DeleteAllLogsAction()
{
super("Delete *all* logs.");
}
public void actionPerformed(ActionEvent actionEvent)
{
preferencesDialog.deleteAllLogs();
}
}
public class CopySystemPropertiesAction
extends AbstractAction
{
private static final long serialVersionUID = -2375370123070284280L;
CopySystemPropertiesAction()
{
super("Copy properties");
putValue(SHORT_DESCRIPTION, "Copy system properties to the clipboard.");
}
public void actionPerformed(ActionEvent actionEvent)
{
Properties props = System.getProperties();
SortedMap<String, String> sortedProps = new TreeMap<>();
Enumeration<?> keys = props.propertyNames();
while(keys.hasMoreElements())
{
String current = (String) keys.nextElement();
String value = props.getProperty(current);
if("line.separator".equals(current))
{
value = value.replace("\n", "\\n");
value = value.replace("\r", "\\r");
}
sortedProps.put(current, value);
}
StringBuilder builder = new StringBuilder();
for(Map.Entry<String, String> current : sortedProps.entrySet())
{
builder.append(current.getKey()).append("=").append(current.getValue()).append("\n");
}
MainFrame.copyText(builder.toString());
}
}
public class CopyThreadsAction
extends AbstractAction
{
private static final long serialVersionUID = -2375370123070284280L;
CopyThreadsAction()
{
super("Copy threads");
putValue(SHORT_DESCRIPTION, "Copy the stacktraces of all threads to the clipboard.");
}
public void actionPerformed(ActionEvent actionEvent)
{
Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
StringBuilder builder = new StringBuilder();
Map<ThreadGroup, List<ThreadHolder>> threadGroupMapping = new Hashtable<>();
List<ThreadHolder> nullList = new ArrayList<>();
for(Map.Entry<Thread, StackTraceElement[]> current : allStackTraces.entrySet())
{
Thread key = current.getKey();
StackTraceElement[] value = current.getValue();
ThreadHolder holder = new ThreadHolder(key, value);
ThreadGroup group = key.getThreadGroup();
if(group == null)
{
nullList.add(holder);
}
else
{
List<ThreadHolder> list = threadGroupMapping.get(group);
if(list == null)
{
list = new ArrayList<>();
threadGroupMapping.put(group, list);
}
list.add(holder);
}
}
ThreadGroup rootGroup = null;
Map<ThreadGroup, List<ThreadGroup>> threadGroups = new Hashtable<>();
for(Map.Entry<ThreadGroup, List<ThreadHolder>> current : threadGroupMapping.entrySet())
{
ThreadGroup key = current.getKey();
ThreadGroup root = addGroup(key, threadGroups);
if(rootGroup == null)
{
rootGroup = root;
}
else if(rootGroup != root)
{
if(logger.isErrorEnabled()) logger.error("root={}, rootGroup={}", root, rootGroup);
}
}
if(rootGroup == null)
{
if(logger.isErrorEnabled()) logger.error("Couldn't resolve root ThreadGroup!");
return;
}
appendGroup(0, builder, rootGroup, threadGroups, threadGroupMapping);
if(nullList.size() > 0)
{
builder.append("no group:\n");
for(ThreadHolder current : nullList)
{
appendThread(1, builder, current);
}
}
MainFrame.copyText(builder.toString());
}
private void appendGroup(int indent, StringBuilder builder, ThreadGroup group, Map<ThreadGroup, List<ThreadGroup>> threadGroups, Map<ThreadGroup, List<ThreadHolder>> threadGroupMapping)
{
String indentStr = createIndent(indent);
builder.append(indentStr).append("ThreadGroup[name='").append(group.getName()).append("'" + ", daemon=")
.append(group.isDaemon()).append(", destroyed=").append(group.isDestroyed()).append(", maxPriority=")
.append(group.getMaxPriority()).append("]\n");
List<ThreadGroup> groups = threadGroups.get(group);
if(groups != null && groups.size() > 0)
{
builder.append(indentStr).append("groups = {\n");
groups.sort(ThreadGroupComparator.INSTANCE);
for(ThreadGroup current : groups)
{
appendGroup(indent + 1, builder, current, threadGroups, threadGroupMapping);
}
builder.append(indentStr).append("}\n");
}
List<ThreadHolder> threads = threadGroupMapping.get(group);
if(threads != null && threads.size() > 0)
{
builder.append(indentStr).append("threads = {\n");
Collections.sort(threads);
for(ThreadHolder current : threads)
{
appendThread(indent + 1, builder, current);
}
builder.append(indentStr).append("}\n");
}
}
private void appendThread(int indent, StringBuilder builder, ThreadHolder threadHolder)
{
String indentStr = createIndent(indent);
Thread t = threadHolder.getThread();
StackTraceElement[] ste = threadHolder.getStackTraceElements();
builder.append(indentStr).append("Thread[name=").append(t.getName()).append(", id=").append(t.getId())
.append(", priority=").append(t.getPriority()).append(", state=").append(t.getState())
.append(", daemon=").append(t.isDaemon()).append(", alive=").append(t.isAlive())
.append(", interrupted=").append(t.isInterrupted()).append("]\n");
appendStackTraceElements(indent + 1, builder, ste);
}
private void appendStackTraceElements(int indent, StringBuilder builder, StackTraceElement[] stackTraceElements)
{
String indentStr = createIndent(indent);
for(StackTraceElement current : stackTraceElements)
{
builder.append(indentStr).append("at ").append(current).append("\n");
}
}
private String createIndent(int indent)
{
StringBuilder result = new StringBuilder();
for(int i = 0; i < indent; i++)
{
result.append("\t");
}
return result.toString();
}
private ThreadGroup addGroup(ThreadGroup group, Map<ThreadGroup, List<ThreadGroup>> threadGroups)
{
ThreadGroup parentGroup = group.getParent();
if(parentGroup == null)
{
return group; // root
}
List<ThreadGroup> list = threadGroups.get(parentGroup);
if(list == null)
{
list = new ArrayList<>();
threadGroups.put(parentGroup, list);
}
if(!list.contains(group))
{
list.add(group);
}
return addGroup(parentGroup, threadGroups);
}
}
private static class ThreadGroupComparator
implements Comparator<ThreadGroup>
{
static final Comparator<ThreadGroup> INSTANCE = new ThreadGroupComparator();
public int compare(ThreadGroup o1, ThreadGroup o2)
{
if(o1 == o2)
{
return 0;
}
if(o1 == null)
{
return -1;
}
if(o2 == null)
{
return 1;
}
String name = o1.getName();
String otherName = o2.getName();
//noinspection StringEquality
if(name == otherName)
{
return 0;
}
if(name == null)
{
return -1;
}
if(otherName == null)
{
return 1;
}
return name.compareTo(otherName);
}
}
private static class ThreadHolder
implements Comparable<ThreadHolder>
{
private final Thread thread;
private final StackTraceElement[] stackTraceElements;
private ThreadHolder(Thread thread, StackTraceElement[] stackTraceElements)
{
this.thread = thread;
this.stackTraceElements = stackTraceElements;
}
public Thread getThread()
{
return thread;
}
StackTraceElement[] getStackTraceElements()
{
return stackTraceElements;
}
public boolean equals(Object o)
{
if(this == o) return true;
if(o == null || getClass() != o.getClass()) return false;
ThreadHolder that = (ThreadHolder) o;
return !(thread != null ? !thread.equals(that.thread) : that.thread != null);
}
public int hashCode()
{
return (thread != null ? thread.hashCode() : 0);
}
@SuppressWarnings("NullableProblems")
public int compareTo(ThreadHolder other)
{
if(other == null)
{
throw new NullPointerException("other must not be null!");
}
if(thread == other.thread)
{
return 0;
}
if(thread == null)
{
return -1;
}
if(other.thread == null)
{
return 1;
}
String name = thread.getName();
String otherName = other.thread.getName();
//noinspection StringEquality
if(name == otherName)
{
return 0;
}
// thread name is never null
return name.compareTo(otherName);
}
}
public class GarbageCollectionAction
extends AbstractAction
{
private static final long serialVersionUID = -4636919088257143096L;
GarbageCollectionAction()
{
super("Execute GC");
putValue(SHORT_DESCRIPTION, "Execute garbage collection.");
}
public void actionPerformed(ActionEvent actionEvent)
{
System.gc();
}
}
}