package com.limegroup.gnutella.gui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.PatternLayout; import org.apache.log4j.WriterAppender; import org.apache.log4j.spi.LoggerRepository; import com.limegroup.gnutella.ErrorService; import com.limegroup.gnutella.bugs.LocalClientInfo; import com.limegroup.gnutella.gui.themes.ThemeFileHandler; import com.limegroup.gnutella.gui.themes.ThemeMediator; import com.limegroup.gnutella.gui.themes.ThemeObserver; import com.limegroup.gnutella.settings.ConsoleSettings; import com.limegroup.gnutella.util.CommonUtils; /** * A Console for log/any output */ public class Console extends JPanel implements ThemeObserver { private final int idealSize; private final int maxExcess; private JScrollPane scrollPane; private JTextArea output; private JButton apply; private JButton clear; private JButton save; private JComboBox loggerComboBox; private JComboBox levelComboBox; private boolean scroll = true; private boolean altCtrlDown = false; public Console() { idealSize = ConsoleSettings.CONSOLE_IDEAL_SIZE.getValue(); maxExcess = ConsoleSettings.CONSOLE_MAX_EXCESS.getValue(); output = new JTextArea(); output.setEditable(false); scrollPane = new JScrollPane(output); scrollPane.getVerticalScrollBar().addAdjustmentListener( new AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent e) { if (e.getValueIsAdjusting()) { scroll = false; } else { scroll = true; } } }); loggerComboBox = new JComboBox(new LoggerComboBoxModel()); levelComboBox = new JComboBox(new LevelComboBoxModel()); loggerComboBox.setAutoscrolls(true); loggerComboBox.setMaximumRowCount(20); loggerComboBox.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent evt) { selectLoggerLevel(); } }); loggerComboBox.addPopupMenuListener(new PopupMenuListener() { public void popupMenuWillBecomeVisible(PopupMenuEvent evt) { refreshLoggers(); } public void popupMenuCanceled(PopupMenuEvent evt) {} public void popupMenuWillBecomeInvisible(PopupMenuEvent evt) {} }); levelComboBox.setAutoscrolls(true); apply = new JButton(GUIMediator.getStringResource("GENERAL_APPLY_BUTTON_LABEL")); apply.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { applyLevel(); } }); clear = new JButton(GUIMediator.getStringResource("GENERAL_CLEAR_BUTTON_LABEL")); clear.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { clear(); } }); save = new JButton(GUIMediator.getStringResource("CONSOLE_SAVE_BUTTON_LABEL")); save.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { save(); } }); // Developers can press and hold Alt+Ctrl while clicking // on Save to get the current stack traces. if (CommonUtils.isJava15OrLater()) { KeyListener keyListener = new KeyAdapter() { public void keyPressed(KeyEvent e) { altCtrlDown = e.isAltDown() && e.isControlDown(); } // Note: if the user is holding Alt+Ctrl while // switching to a different Tab this will never // get called! We need a second Listener! public void keyReleased(KeyEvent e) { altCtrlDown = false; } }; // Install the listener on all components addKeyListener(keyListener); scrollPane.addKeyListener(keyListener); output.addKeyListener(keyListener); loggerComboBox.addKeyListener(keyListener); levelComboBox.addKeyListener(keyListener); apply.addKeyListener(keyListener); clear.addKeyListener(keyListener); save.addKeyListener(keyListener); // Reset the flag if this Tab gets invisible addComponentListener(new ComponentAdapter() { public void componentHidden(ComponentEvent e) { altCtrlDown = false; } }); } setLayout(new BorderLayout()); add(BorderLayout.CENTER, scrollPane); JPanel controlsPanel = new JPanel(); controlsPanel.setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 2; gbc.weighty = 0; gbc.fill = GridBagConstraints.HORIZONTAL; controlsPanel.add(loggerComboBox, gbc); gbc.gridx = 1; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.fill = GridBagConstraints.HORIZONTAL; controlsPanel.add(levelComboBox, gbc); gbc.gridx = 2; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.fill = GridBagConstraints.HORIZONTAL; controlsPanel.add(apply, gbc); gbc.gridx = 3; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.fill = GridBagConstraints.HORIZONTAL; controlsPanel.add(clear, gbc); gbc.gridx = 4; gbc.gridy = 0; gbc.gridwidth = 1; gbc.gridheight = 1; gbc.weightx = 0; gbc.weighty = 0; gbc.fill = GridBagConstraints.HORIZONTAL; controlsPanel.add(save, gbc); add(BorderLayout.SOUTH, controlsPanel); updateTheme(); ThemeMediator.addThemeObserver(this); refreshLoggers(); attachLogs(); } /** * */ private void attachLogs() { WriterAppender append = new WriterAppender(new PatternLayout( ConsoleSettings.CONSOLE_PATTERN_LAYOUT.getValue()), new ConsoleWriter()); LogManager.getRootLogger().addAppender(append); } /** * Rebuilds the Logger ComboBox */ private void refreshLoggers() { LoggerRepository repository = LogManager.getLoggerRepository(); Enumeration currentLoggers = repository.getCurrentLoggers(); LoggerComboBoxModel loggerModel = (LoggerComboBoxModel) loggerComboBox.getModel(); int loggerIndex = loggerComboBox.getSelectedIndex(); LoggerNode currentLogger = (loggerIndex >= 0) ? loggerModel.getLogger(loggerIndex) : null; /* * Step 1: Create a Tree of Packages and Classes */ ArrayList pkgList = new ArrayList(); HashMap pkgMap = new HashMap(); while (currentLoggers.hasMoreElements()) { Logger lggr = (Logger)currentLoggers.nextElement(); String pkg = PackageNode.getPackage(lggr); PackageNode node = (PackageNode)pkgMap.get(pkg); if (node == null) { node = new PackageNode(pkg); pkgMap.put(pkg, node); pkgList.add(node); } node.add(lggr); } /* * Step 2: Sort the Packages by name */ Collections.sort(pkgList, new Comparator() { public int compare(Object o1, Object o2) { return ((PackageNode) o1).getName().compareTo(((PackageNode) o2).getName()); } }); /* * Step 3: Turn the Tree into a flat List of * * Package * Class * Class * Package * Class * ... */ loggerIndex = -1; ArrayList nodes = new ArrayList(); for(Iterator it = pkgList.iterator(); it.hasNext(); ) { PackageNode pkgNode = (PackageNode)it.next(); pkgNode.sort(); nodes.add(pkgNode); if (loggerIndex == -1 && pkgNode.equals(currentLogger)) { loggerIndex = nodes.size()-1; } for(Iterator it2 = pkgNode.getNodes().iterator(); it2.hasNext(); ) { ClassNode classNode = (ClassNode)it2.next(); nodes.add(classNode); if (loggerIndex == -1 && classNode.equals(currentLogger)) { loggerIndex = nodes.size()-1; } } } loggerModel.refreshLoggers(nodes); boolean empty = nodes.isEmpty(); loggerComboBox.setEnabled(!empty); levelComboBox.setEnabled(!empty); apply.setEnabled(!empty); if (!empty) { loggerComboBox.setSelectedIndex(loggerIndex >= 0 ? loggerIndex : 0); selectLoggerLevel(); } } /** * Selects the Level of the currently selected Logger */ private void selectLoggerLevel() { LoggerComboBoxModel loggerModel = (LoggerComboBoxModel) loggerComboBox.getModel(); LevelComboBoxModel levelModel = (LevelComboBoxModel) levelComboBox.getModel(); int loggerIndex = loggerComboBox.getSelectedIndex(); if (loggerIndex < 0) return; Level level = getLevel(loggerModel.getLogger(loggerIndex)); levelModel.setSelectedItem(level); } /** * Applies the currently selected logging level */ private void applyLevel() { LoggerComboBoxModel loggerModel = (LoggerComboBoxModel) loggerComboBox.getModel(); LevelComboBoxModel levelModel = (LevelComboBoxModel) levelComboBox.getModel(); int loggerIndex = loggerComboBox.getSelectedIndex(); if (loggerIndex < 0) return; LoggerNode logger = loggerModel.getLogger(loggerIndex); Level currentLevel = getLevel(logger); int levelIndex = levelComboBox.getSelectedIndex(); Level newLevel = (levelIndex > 0) ? levelModel.getLevel(levelIndex) : null; if (!currentLevel.equals(newLevel)) { logger.setLevel(newLevel); loggerComboBox.setSelectedIndex(loggerIndex); // update the ComboxBox (the text) loggerModel.updateIndex(loggerIndex); } } /** * Appends text to the console. * * @param text * The text to be appended */ public void appendText(final String text) { if (!output.isEnabled()) { return; } SwingUtilities.invokeLater(new Runnable() { public void run() { output.append(text); int excess = output.getDocument().getLength() - idealSize; if (excess >= maxExcess) { output.replaceRange("", 0, excess); } if (scroll) output.setCaretPosition(output.getText().length()); } }); } /** * Clears the console. */ public void clear() { output.setText(null); } /** * Saves the current Console output and the stack traces of * all active Threads if available */ public void save() { try { output.setEnabled(altCtrlDown); String log = output.getText().trim(); String traces = CommonUtils.getAllStackTraces(); if (log.length() == 0 && traces.length() == 0) { return; } if (altCtrlDown) { StringBuffer buffer = new StringBuffer(); buffer.append("-- BEGIN STACK TRACES --\n"); buffer.append(traces.length() > 0 ? traces : "NONE"); buffer.append("\n-- END STACK TRACES --\n"); appendText(buffer.toString()); } else { StringBuffer buffer = new StringBuffer(); buffer.append(new Date()).append("\n\n"); Exception e = new Exception() { public void printStackTrace(PrintWriter out) { /* PRINT NOTHING */ } }; LocalClientInfo info = new LocalClientInfo(e, Thread.currentThread().getName(), "Console Log", false); buffer.append(info.toBugReport()); buffer.append("-- BEGIN STACK TRACES --\n"); buffer.append(traces.length() > 0 ? traces : "NONE"); buffer.append("\n-- END STACK TRACES --\n"); buffer.append("\n-- BEGIN LOG --\n"); buffer.append(log.length() > 0 ? log : "NONE"); buffer.append("\n-- END LOG --\n"); File file = FileChooserHandler.getSaveAsFile(GUIMediator.getAppFrame(), "CONSOLE_DIALOG_SAVE_TITLE", new File("limewire-log.txt")); if (file == null) { return; } BufferedWriter out = new BufferedWriter(new FileWriter(file)); out.write(buffer.toString()); out.close(); } } catch (IOException err) { ErrorService.error(err); } finally { output.setEnabled(true); } } /** * Updates the appearance of this panel based on the current theme. */ public void updateTheme() { Color tableColor = ThemeFileHandler.TABLE_BACKGROUND_COLOR.getValue(); scrollPane.getViewport().setBackground(tableColor); } /** * Returns Level.OFF instead of null if logging is turned off */ private static final Level getLevel(LoggerNode logger) { Level level = logger.getLevel(); if (level == null) level = Level.OFF; return level; } private final class ConsoleWriter extends Writer { private StringBuffer buffer = new StringBuffer(); public void write(char[] cbuf, int off, int len) { buffer.append(cbuf, off, len); } public void close() { buffer = null; } public void flush() { Console.this.appendText(buffer.toString()); buffer.setLength(0); } } /** * Logger ComboBox model */ private static class LoggerComboBoxModel extends DefaultComboBoxModel { private static final String SPACER = " "; private List nodes = Collections.EMPTY_LIST; private void updateIndex(int index) { fireContentsChanged(this, index, index); } private void refreshLoggers(List nodes) { this.nodes = nodes; fireContentsChanged(this, 0, nodes.size()); } public int getSize() { return nodes.size(); } private LoggerNode getLogger(int index) { return (LoggerNode)nodes.get(index); } public Object getElementAt(int index) { LoggerNode logger = getLogger(index); Level level = getLevel(logger); if (level.equals(Level.OFF)) { if (logger.isLeaf()) { return SPACER + logger.getName(); } else { return logger.getName(); } } else { if (logger.isLeaf()) { return SPACER + logger.getName() + " [" + level + "]"; } else { return logger.getName(); } } } } /** * Logging level ComboBox model */ private class LevelComboBoxModel extends DefaultComboBoxModel { private final Level[] levels = new Level[] { Level.OFF, Level.ALL, Level.DEBUG, Level.ERROR, Level.FATAL, Level.INFO, Level.WARN }; public int getSize() { return levels.length; } private Level getLevel(int index) { return levels[index]; } public Object getElementAt(int index) { return getLevel(index).toString(); } } /** * A interface to build a very simple Tree of * Packages and Classes */ private interface LoggerNode { boolean isLeaf(); Level getLevel(); void setLevel(Level level); String getName(); } private static class PackageNode implements LoggerNode { private String pkg; private ArrayList classNodes = new ArrayList(); private PackageNode(String pkg) { this.pkg = pkg; } public void add(Logger logger) { classNodes.add(new ClassNode(this, logger)); } public Level getLevel() { return Level.OFF; } public void setLevel(Level level) { for(int i = classNodes.size()-1; i >= 0; i--) { ((ClassNode)classNodes.get(i)).setLevel(level); } } public boolean isLeaf() { return false; } public void sort() { Collections.sort(classNodes, new Comparator() { public int compare(Object o1, Object o2) { return ((ClassNode) o1).getName().compareTo(((ClassNode) o2).getName()); } }); } public List getNodes() { return classNodes; } public int hashCode() { return pkg.hashCode(); } public boolean equals(Object o) { if (!(o instanceof PackageNode)) { return false; } return pkg.equals(((PackageNode)o).pkg); } public String getName() { return pkg; } public String toString() { return getName(); } private static String getPackage(Logger logger) { String name = logger.getName(); return name.substring(0, name.lastIndexOf('.')) + ".*"; } } private static class ClassNode implements LoggerNode { private PackageNode parent; private Logger logger; private ClassNode(PackageNode parent, Logger logger) { this.parent = parent; this.logger = logger; } public PackageNode getParent() { return parent; } public Logger getLogger() { return logger; } public Level getLevel() { return logger.getLevel(); } public void setLevel(Level level) { logger.setLevel(level); } public boolean isLeaf() { return true; } public String getName() { return logger.getName(); } public int hashCode() { return getName().hashCode(); } public boolean equals(Object o) { if (!(o instanceof ClassNode)) { return false; } return getName().equals(((ClassNode)o).getName()); } public String toString() { return getName(); } } }