fr.jmmc.jmcs.logging.LogbackGui.java Source code

Java tutorial

Introduction

Here is the source code for fr.jmmc.jmcs.logging.LogbackGui.java

Source

/*******************************************************************************
 *                 jMCS project ( http://www.jmmc.fr/dev/jmcs )
 *******************************************************************************
 * Copyright (c) 2013, CNRS. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the CNRS nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL CNRS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
package fr.jmmc.jmcs.logging;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import fr.jmmc.jmcs.App;
import fr.jmmc.jmcs.data.app.ApplicationDescription;
import fr.jmmc.jmcs.gui.util.WindowUtils;
import fr.jmmc.jmcs.util.StringUtils;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.Enumeration;
import javax.swing.JFrame;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import org.apache.commons.lang.SystemUtils;
import org.slf4j.LoggerFactory;

/**
 * This class provides two major functionalities:
 * - application log viewer
 * - one simple  log GUI : Logger hierarchy browser / Level editor and displays current logs
 * 
 * This class is dedicated to Slf4j @see http://www.slf4j.org/ (MIT License) using Logback @see http://logback.qos.ch/ (EPL v1.0 / LGPL 2.1).
 * 
 * @author Laurent BOURGES.
 */
public final class LogbackGui extends javax.swing.JPanel implements TreeSelectionListener, ActionListener {

    /** default serial UID for Serializable interface */
    private static final long serialVersionUID = 1L;
    /** undefined level */
    private static final String UNDEFINED_LEVEL = "UNDEFINED";
    /** Root Logger */
    private static final Logger _rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
    /** Class logger */
    private static final Logger _logger = (Logger) LoggerFactory.getLogger(LogbackGui.class.getName());
    /** Single instance GUI Frame */
    private static JFrame _guiFrameSingleton = null;
    /** Single instance GUI */
    private static LogbackGui _guiSingleton = null;

    /**
     * Show the logging utility and displays the application log
     */
    public static void showLogConsole() {
        showLogConsoleForLogger(LoggingService.JMMC_APP_LOG);
    }

    /**
     * Show the logging utility and displays the log corresponding to the given logger path
     * @param loggerPath logger path
     */
    public static void showLogConsoleForLogger(final String loggerPath) {
        showWindow(App.getFrame(), ApplicationDescription.getInstance().getProgramName() + " Log Console",
                loggerPath);
    }

    /**
     * Display the logger editor
     * @param parent parent frame used to center this window (null means center on screen)
     * @param name name of the editor frame
     * @param loggerPath logger path
     */
    public static void showWindow(final JFrame parent, final String name, final String loggerPath) {
        if (_guiFrameSingleton != null) {
            _guiFrameSingleton.toFront();

            // ensure window is visible (not iconified):
            if (_guiFrameSingleton.getState() == Frame.ICONIFIED) {
                _guiFrameSingleton.setState(Frame.NORMAL);
            }

            // force the frame to be visible and bring it to front
            _guiFrameSingleton.setVisible(true);
            _guiFrameSingleton.toFront();
        } else {
            final String frameName = (name != null) ? name : "Log Console";

            // Create Gui:
            _guiSingleton = new LogbackGui();

            // 1. Create the frame
            _guiFrameSingleton = new JFrame(frameName) {
                /** default serial UID for Serializable interface */
                private static final long serialVersionUID = 1L;

                /**
                 * Free any resource or reference to this instance :
                 * stop the Swing timer if started
                 */
                @Override
                public void dispose() {
                    // free LogbackGui resources:
                    _guiSingleton.onDispose();

                    // free singletons:
                    _guiSingleton = null;
                    _guiFrameSingleton = null;

                    // dispose Frame :
                    super.dispose();
                }
            };

            final Dimension dim = new Dimension(700, 500);
            _guiFrameSingleton.setMinimumSize(dim);
            _guiFrameSingleton.addComponentListener(new ComponentResizeAdapter(dim));

            // 2. Optional: What happens when the frame closes ?
            _guiFrameSingleton.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

            // 3. Create components and put them in the frame
            _guiFrameSingleton.add(_guiSingleton);

            // 4. Size the frame.
            _guiFrameSingleton.pack();

            // Center it :
            WindowUtils.centerOnMainScreen(_guiFrameSingleton);
            WindowUtils.setClosingKeyboardShortcuts(_guiFrameSingleton);

            // 5. Show it and waits until frame is not visible or disposed :
            _guiFrameSingleton.setVisible(true);
        }

        // Select the log tab:
        _guiSingleton.selectLogTab(loggerPath);
    }

    // Members
    /** current edited logger */
    private Logger _currentLogger = null;
    /** flag to enable / disable the automatic update of the logger when any swing component changes */
    private boolean _doAutoUpdateLogger = true;

    /** Creates new form LogbackGui */
    private LogbackGui() {
        initComponents();

        postInit();
    }

    /**
     * Initialize the Swing components
     */
    private void postInit() {

        if (SystemUtils.IS_OS_MAC_OSX) {
            jPanelConf.setOpaque(false);
        }

        // add log panel automatically:
        int tabIndex = 0;
        for (AppenderLogMapper mapper : LoggingService.getInstance().getLogMappers()) {
            jTabbedPane.insertTab(mapper.getDisplayName(), null, new LogPanel(mapper.getLoggerPath()), null,
                    tabIndex++);
        }

        generateTree();

        // tree selection listener :
        jTreeLoggers.addTreeSelectionListener(this);

        // add property change listener to editable fields :
        // level (combo box) :
        jComboBoxLevel.addActionListener(this);

        // display root logger information:
        processLoggerSelection(_rootLogger);
    }

    /**
     * Free any resource (timer) to this instance
     */
    private void onDispose() {
        _logger.debug("onDispose: {}", this);

        // free log panel resources (timers):
        for (Component com : jTabbedPane.getComponents()) {
            if (com instanceof LogPanel) {
                final LogPanel logPanel = (LogPanel) com;
                logPanel.onDispose();
            }
        }
    }

    /**
     * Select the log tab corresponding to the given logger path 
     * @param loggerPath logger path
     */
    private void selectLogTab(final String loggerPath) {
        if (loggerPath != null) {
            for (Component com : jTabbedPane.getComponents()) {
                if (com instanceof LogPanel) {
                    final LogPanel logPanel = (LogPanel) com;
                    if (logPanel.getLoggerPath().equals(loggerPath)) {
                        jTabbedPane.setSelectedComponent(logPanel);
                        return;
                    }
                }
            }
        }
        jTabbedPane.setSelectedIndex(0);
    }

    /* Tree related methods */
    /**
     * Return the custom GenericJTree
     * @return GenericJTree
     */
    @SuppressWarnings("unchecked")
    private GenericJTree<Logger> getTreeLoggers() {
        return (GenericJTree<Logger>) jTreeLoggers;
    }

    /**
     * Generate the tree from the current edited list of Loggers
     */
    private void generateTree() {

        visitJulLoggers();

        final GenericJTree<Logger> treeLoggers = getTreeLoggers();

        final LoggerContext loggerContext = _rootLogger.getLoggerContext();

        final DefaultMutableTreeNode rootNode = treeLoggers.getRootNode();

        // remove complete hierarchy:
        rootNode.removeAllChildren();

        // update the root node with the root logger (Logger[ROOT]):
        rootNode.setUserObject(_rootLogger);

        int pos;
        String path;
        DefaultMutableTreeNode parentNode;

        for (Logger logger : loggerContext.getLoggerList()) {

            // skip root logger
            if (logger != _rootLogger) {

                pos = logger.getName().lastIndexOf('.');

                if (pos == -1) {
                    // no path
                    path = null;
                    parentNode = null;

                } else {
                    path = logger.getName().substring(0, pos);

                    parentNode = treeLoggers.findTreeNode(loggerContext.getLogger(path));
                }

                if (parentNode == null) {
                    parentNode = rootNode;
                }

                if (parentNode != null) {
                    treeLoggers.addNode(parentNode, logger);
                }
            }
        }

        // fire node structure changed :
        treeLoggers.fireNodeChanged(rootNode);

        // select root node
        treeLoggers.selectPath(new TreePath(rootNode.getPath()));
    }

    /**
     * Process the tree selection events
     * @param e tree selection event
     */
    @Override
    public void valueChanged(final TreeSelectionEvent e) {
        final DefaultMutableTreeNode currentNode = getTreeLoggers().getLastSelectedNode();

        if (currentNode != null) {
            /* retrieve the node that was selected */
            final Object userObject = currentNode.getUserObject();

            if (userObject instanceof Logger) {
                processLoggerSelection((Logger) userObject);
            }
        }
    }

    /**
     * Update the UI when a Logger is selected in the Logger tree
     * @param logger selected Logger
     */
    private void processLoggerSelection(final Logger logger) {

        // update the current Logger :
        _currentLogger = logger;

        // disable the automatic update Logger :
        final boolean prevAutoUpdateLogger = setAutoUpdateLogger(false);
        try {

            // note : setText() / setValue() methods fire a property change event :

            // name :
            jTextFieldName.setText(logger.getName());

            // Level :
            jComboBoxLevel
                    .setSelectedItem((logger.getLevel() != null) ? logger.getLevel().toString() : UNDEFINED_LEVEL);

            // effective level :
            jTextFieldEffectiveLevel.setText(logger.getEffectiveLevel().toString());

            // additivity :
            jRadioButtonAdditivityOn.setSelected(logger.isAdditive());

        } finally {
            // restore the automatic update logger :
            setAutoUpdateLogger(prevAutoUpdateLogger);
        }
    }

    /**
     * Process any comboBox change event (level) to update Logger's state
     * @param ae action event
     */
    @Override
    public void actionPerformed(final ActionEvent ae) {
        if (ae.getSource() == jComboBoxLevel) {
            if (_doAutoUpdateLogger) {
                if (_currentLogger != null) {
                    final Level level = Level.toLevel((String) jComboBoxLevel.getSelectedItem(), null);

                    // intercept known bug in JUL-to-SLF4J:
                    try {
                        _currentLogger.setLevel(level);
                    } catch (RuntimeException re) {
                        _logger.info("setLevel failure:", re);
                    }

                    _logger.warn("Updated level for Logger [{}] to [{}]", _currentLogger.getName(),
                            _currentLogger.getLevel());

                    // update form:
                    processLoggerSelection(_currentLogger);
                }
            }
        }
    }

    /**
     * Dump current state of all loggers using the root logger in warning level
     */
    private void dumpLoggers() {
        if (_logger.isWarnEnabled()) {
            _logger.warn("Logger Hierarchy:");

            for (Logger logger : _rootLogger.getLoggerContext().getLoggerList()) {
                _logger.warn("Logger[{}] level [{}], effective [{}], additivity [{}]", new Object[] {
                        logger.getName(), logger.getLevel(), logger.getEffectiveLevel(), logger.isAdditive() });
            }
        }
    }

    /**
     * Enable / Disable the automatic update of the logger when any swing component changes.
     * Return its previous value.
     *
     * Typical use is as following :
     * // disable the automatic update logger :
     * final boolean prevAutoUpdateLogger = setAutoUpdateLogger(false);
     * try {
     *   // operations ...
     *
     * } finally {
     *   // restore the automatic update logger :
     *   setAutoUpdateLogger(prevAutoUpdateLogger);
     * }
     *
     * @param value new value
     * @return previous value
     */
    private boolean setAutoUpdateLogger(final boolean value) {
        // first backup the state of the automatic update target :
        final boolean previous = _doAutoUpdateLogger;

        // then change its state :
        _doAutoUpdateLogger = value;

        // return previous state :
        return previous;
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        buttonGroupAdditivity = new javax.swing.ButtonGroup();
        jTabbedPane = new javax.swing.JTabbedPane();
        jPanelConf = new javax.swing.JPanel();
        jPanelConfButtons = new javax.swing.JPanel();
        jButtonExpand = new javax.swing.JButton();
        jButtonCollapse = new javax.swing.JButton();
        jButtonRefreshLoggers = new javax.swing.JButton();
        jButtonDumpLoggers = new javax.swing.JButton();
        jScrollPaneLoggerTree = new javax.swing.JScrollPane();
        jTreeLoggers = createLoggerJTree();
        jPanelLoggerInfo = new javax.swing.JPanel();
        jLabelInfo = new javax.swing.JLabel();
        jLabelName = new javax.swing.JLabel();
        jTextFieldName = new javax.swing.JTextField();
        jLabelLevel = new javax.swing.JLabel();
        jTextFieldEffectiveLevel = new javax.swing.JTextField();
        jLabelEffectiveLevel = new javax.swing.JLabel();
        jComboBoxLevel = new javax.swing.JComboBox();
        jLabelAdditivity = new javax.swing.JLabel();
        jRadioButtonAdditivityOn = new javax.swing.JRadioButton();
        jRadioButtonAdditivityOff = new javax.swing.JRadioButton();

        setLayout(new java.awt.BorderLayout());

        jPanelConf.setLayout(new java.awt.GridBagLayout());

        jPanelConfButtons.setOpaque(false);
        jPanelConfButtons.setLayout(new java.awt.FlowLayout(java.awt.FlowLayout.CENTER, 10, 0));

        jButtonExpand.setText("Expand");
        jButtonExpand.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonExpandActionPerformed(evt);
            }
        });
        jPanelConfButtons.add(jButtonExpand);

        jButtonCollapse.setText("Collapse");
        jButtonCollapse.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonCollapseActionPerformed(evt);
            }
        });
        jPanelConfButtons.add(jButtonCollapse);

        jButtonRefreshLoggers.setText("Refresh");
        jButtonRefreshLoggers.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonRefreshLoggersActionPerformed(evt);
            }
        });
        jPanelConfButtons.add(jButtonRefreshLoggers);

        jButtonDumpLoggers.setText("Dump");
        jButtonDumpLoggers.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                jButtonDumpLoggersActionPerformed(evt);
            }
        });
        jPanelConfButtons.add(jButtonDumpLoggers);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.insets = new java.awt.Insets(4, 3, 0, 3);
        jPanelConf.add(jPanelConfButtons, gridBagConstraints);

        jScrollPaneLoggerTree.setOpaque(false);

        javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("Loggers");
        jTreeLoggers.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
        jTreeLoggers.setVisibleRowCount(5);
        jScrollPaneLoggerTree.setViewportView(jTreeLoggers);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 0.5;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        jPanelConf.add(jScrollPaneLoggerTree, gridBagConstraints);

        jPanelLoggerInfo.setOpaque(false);
        jPanelLoggerInfo.setLayout(new java.awt.GridBagLayout());

        jLabelInfo.setText("Logger information:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.gridwidth = 3;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        jPanelLoggerInfo.add(jLabelInfo, gridBagConstraints);

        jLabelName.setText("Name:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 0, 4);
        jPanelLoggerInfo.add(jLabelName, gridBagConstraints);

        jTextFieldName.setEditable(false);
        jTextFieldName.setText("name");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.gridwidth = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 4, 0);
        jPanelLoggerInfo.add(jTextFieldName, gridBagConstraints);

        jLabelLevel.setText("Level:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 0, 4);
        jPanelLoggerInfo.add(jLabelLevel, gridBagConstraints);

        jTextFieldEffectiveLevel.setColumns(10);
        jTextFieldEffectiveLevel.setEditable(false);
        jTextFieldEffectiveLevel.setText("level");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 4, 0);
        jPanelLoggerInfo.add(jTextFieldEffectiveLevel, gridBagConstraints);

        jLabelEffectiveLevel.setText("Effective level:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 0, 4);
        jPanelLoggerInfo.add(jLabelEffectiveLevel, gridBagConstraints);

        jComboBoxLevel.setModel(new javax.swing.DefaultComboBoxModel(
                new String[] { "UNDEFINED", "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "ALL" }));
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 3;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.WEST;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 4, 0);
        jPanelLoggerInfo.add(jComboBoxLevel, gridBagConstraints);

        jLabelAdditivity.setText("Additivity:");
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 0, 4);
        jPanelLoggerInfo.add(jLabelAdditivity, gridBagConstraints);

        buttonGroupAdditivity.add(jRadioButtonAdditivityOn);
        jRadioButtonAdditivityOn.setText("true");
        jRadioButtonAdditivityOn.setEnabled(false);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 0, 0);
        jPanelLoggerInfo.add(jRadioButtonAdditivityOn, gridBagConstraints);

        buttonGroupAdditivity.add(jRadioButtonAdditivityOff);
        jRadioButtonAdditivityOff.setText("false");
        jRadioButtonAdditivityOff.setEnabled(false);
        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 2;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.insets = new java.awt.Insets(4, 0, 0, 0);
        jPanelLoggerInfo.add(jRadioButtonAdditivityOff, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 0.5;
        gridBagConstraints.weighty = 1.0;
        gridBagConstraints.insets = new java.awt.Insets(4, 4, 4, 4);
        jPanelConf.add(jPanelLoggerInfo, gridBagConstraints);

        jTabbedPane.addTab("Configuration", jPanelConf);

        add(jTabbedPane, java.awt.BorderLayout.CENTER);
    }// </editor-fold>//GEN-END:initComponents

    private void jButtonRefreshLoggersActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonRefreshLoggersActionPerformed
        generateTree();
    }//GEN-LAST:event_jButtonRefreshLoggersActionPerformed

    private void jButtonExpandActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonExpandActionPerformed
        final DefaultMutableTreeNode currentNode = getTreeLoggers().findTreeNode(_currentLogger);
        getTreeLoggers().expandAll(new TreePath(currentNode.getPath()), true,
                (currentNode == getTreeLoggers().getRootNode()) ? false : true);
    }//GEN-LAST:event_jButtonExpandActionPerformed

    private void jButtonCollapseActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonCollapseActionPerformed
        final DefaultMutableTreeNode currentNode = getTreeLoggers().findTreeNode(_currentLogger);
        getTreeLoggers().expandAll(new TreePath(currentNode.getPath()), false,
                (currentNode == getTreeLoggers().getRootNode()) ? false : true);
    }//GEN-LAST:event_jButtonCollapseActionPerformed

    private void jButtonDumpLoggersActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jButtonDumpLoggersActionPerformed
        dumpLoggers();
    }//GEN-LAST:event_jButtonDumpLoggersActionPerformed
     // Variables declaration - do not modify//GEN-BEGIN:variables

    private javax.swing.ButtonGroup buttonGroupAdditivity;
    private javax.swing.JButton jButtonCollapse;
    private javax.swing.JButton jButtonDumpLoggers;
    private javax.swing.JButton jButtonExpand;
    private javax.swing.JButton jButtonRefreshLoggers;
    private javax.swing.JComboBox jComboBoxLevel;
    private javax.swing.JLabel jLabelAdditivity;
    private javax.swing.JLabel jLabelEffectiveLevel;
    private javax.swing.JLabel jLabelInfo;
    private javax.swing.JLabel jLabelLevel;
    private javax.swing.JLabel jLabelName;
    private javax.swing.JPanel jPanelConf;
    private javax.swing.JPanel jPanelConfButtons;
    private javax.swing.JPanel jPanelLoggerInfo;
    private javax.swing.JRadioButton jRadioButtonAdditivityOff;
    private javax.swing.JRadioButton jRadioButtonAdditivityOn;
    private javax.swing.JScrollPane jScrollPaneLoggerTree;
    private javax.swing.JTabbedPane jTabbedPane;
    private javax.swing.JTextField jTextFieldEffectiveLevel;
    private javax.swing.JTextField jTextFieldName;
    private javax.swing.JTree jTreeLoggers;
    // End of variables declaration//GEN-END:variables

    /**
     * Create a new JTree dedicated to Logger instances
     * @return Jtree instance
     */
    private static JTree createLoggerJTree() {
        return new GenericJTree<Logger>(Logger.class) {
            /** default serial UID for Serializable interface */
            private static final long serialVersionUID = 1;

            /**
             * Convert a non-null value object to string
             * @param userObject user object to convert
             * @return string representation of the user object
             */
            @Override
            protected String convertUserObjectToString(final Logger userObject) {
                final int pos = userObject.getName().lastIndexOf('.');

                if (pos != -1) {
                    return userObject.getName().substring(pos + 1);
                }

                return userObject.getName();
            }
        };
    }

    /**
     * This custom JTree implementation provides several utility methods to manipulate
     * DefaultMutableTreeNode and visual representation of nodes
     * @param <E> type of the user object 
     * 
     * Note: code copied from @see fr.jmmc.jmcs.gui.GenericJTree to let this class be self consistent
     */
    private static abstract class GenericJTree<E> extends JTree {

        /** default serial UID for Serializable interface */
        private static final long serialVersionUID = 1;
        /** Class logger */
        protected static final Logger logger = (Logger) LoggerFactory.getLogger(GenericJTree.class.getName());
        /* members */
        /** class corresponding to <E> generic type */
        private final Class<E> _classType;

        /**
         * Public constructor changing default values : SINGLE_TREE_SELECTION
         * 
         * @param classType class corresponding to <E> generic type
         */
        protected GenericJTree(final Class<E> classType) {
            super(new DefaultMutableTreeNode("GenericJTree"), false);

            _classType = classType;

            // single tree selection :
            getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        }

        /**
         * Return the tree model
         * @return tree model
         */
        protected final DefaultTreeModel getTreeModel() {
            return ((DefaultTreeModel) getModel());
        }

        /**
         * Create a new node using the given user object and add it to the given parent node
         * @param parentNode parent node
         * @param userObject user object to create the new node
         * @return new created node
         */
        protected final DefaultMutableTreeNode addNode(final DefaultMutableTreeNode parentNode,
                final E userObject) {
            final DefaultMutableTreeNode modelNode = new DefaultMutableTreeNode(userObject);

            parentNode.add(modelNode);

            return modelNode;
        }

        /**
         * Create a new node using the given user object and add it to the given parent node
         * and Fire node structure changed on the parent node
         * @param parentNode parent node
         * @param userObject user object to create the new node
         */
        protected final void addNodeAndRefresh(final DefaultMutableTreeNode parentNode, final E userObject) {
            final DefaultMutableTreeNode newNode = addNode(parentNode, userObject);

            // fire node structure changed :
            fireNodeChanged(parentNode);

            // Select the new node = model :
            selectPath(new TreePath(newNode.getPath()));
        }

        /**
         * Remove the given current node from the parent node
         * and Fire node structure changed on the parent node
         * @param parentNode parent node
         * @param currentNode node to remove
         */
        protected final void removeNodeAndRefresh(final DefaultMutableTreeNode parentNode,
                final DefaultMutableTreeNode currentNode) {
            removeNodeAndRefresh(parentNode, currentNode, true);
        }

        /**
         * Remove the given current node from the parent node
         * and Fire node structure changed on the parent node
         * @param parentNode parent node
         * @param currentNode node to remove
         * @param doSelectParent flag to indicate to select the parent node once the node removed
         */
        protected final void removeNodeAndRefresh(final DefaultMutableTreeNode parentNode,
                final DefaultMutableTreeNode currentNode, final boolean doSelectParent) {
            parentNode.remove(currentNode);

            // fire node structure changed :
            fireNodeChanged(parentNode);

            if (doSelectParent) {
                // Select the parent node = target :
                selectPath(new TreePath(parentNode.getPath()));
            }
        }

        /**
         * Fire node structure changed on the given tree node
         * @param node changed tree node
         */
        public final void fireNodeChanged(final TreeNode node) {
            // fire node structure changed :
            getTreeModel().nodeStructureChanged(node);
        }

        /**
         * Return the root node
         * @return root node
         */
        protected final DefaultMutableTreeNode getRootNode() {
            return (DefaultMutableTreeNode) getTreeModel().getRoot();
        }

        /**
         * Return the parent node of the given node
         * @param node node to use
         * @return parent node
         */
        protected final DefaultMutableTreeNode getParentNode(final DefaultMutableTreeNode node) {
            return (DefaultMutableTreeNode) node.getParent();
        }

        /**
         * Return the node corresponding to the last selected path in the tree
         * @return node or null
         */
        protected final DefaultMutableTreeNode getLastSelectedNode() {
            return (DefaultMutableTreeNode) getLastSelectedPathComponent();
        }

        /**
         * Find the first tree node having the given user object
         * @param userObject user object to locate in the tree
         * @return tree node or null
         */
        protected final DefaultMutableTreeNode findTreeNode(final E userObject) {
            return findTreeNode(getRootNode(), userObject);
        }

        /**
         * Find the first tree node having the given user object recursively
         *
         * @param node current node to traverse
         * @param userObject user object to locate in the tree
         * @return tree node or null
         */
        protected static DefaultMutableTreeNode findTreeNode(final DefaultMutableTreeNode node,
                final Object userObject) {
            if (node.getUserObject() == userObject) {
                return node;
            }

            final int size = node.getChildCount();
            if (size > 0) {
                DefaultMutableTreeNode result = null;

                DefaultMutableTreeNode childNode;
                for (int i = 0; i < size; i++) {
                    childNode = (DefaultMutableTreeNode) node.getChildAt(i);

                    result = findTreeNode(childNode, userObject);
                    if (result != null) {
                        return result;
                    }
                }
            }
            return null;
        }

        /**
         * Select the first child node
         * @param rootNode root node
         */
        protected final void selectFirstChildNode(final DefaultMutableTreeNode rootNode) {
            if (rootNode.isLeaf()) {
                return;
            }

            // first child = target :
            final DefaultMutableTreeNode firstChild = (DefaultMutableTreeNode) rootNode.getFirstChild();
            selectPath(new TreePath(firstChild.getPath()));

            // expand node if there is at least one child node :
            if (!firstChild.isLeaf()) {
                final DefaultMutableTreeNode secondChild = (DefaultMutableTreeNode) firstChild.getFirstChild();

                scrollPathToVisible(new TreePath(secondChild.getPath()));
            }
        }

        /**
         * Change the selected path in the tree
         * This will send a selection event changed that will refresh the UI
         *
         * @param path tree path
         */
        protected final void selectPath(final TreePath path) {
            setSelectionPath(path);
            scrollPathToVisible(path);
        }

        /**
         * Expand or collapse all nodes in the tree
         * @param expand true to expand all or collapse all
         */
        protected void expandAll(final boolean expand) {
            // Traverse tree from root
            expandAll(new TreePath(getRootNode()), expand, false);
        }

        /**
         * Expand or collapse all nodes starting from the given parent (path)
         * @param parent parent path
         * @param expand true to expand all or collapse all
         * @param process flag to process this parent node (useful for root node)
         */
        protected void expandAll(final TreePath parent, final boolean expand, final boolean process) {
            // Traverse children
            final TreeNode node = (TreeNode) parent.getLastPathComponent();
            if (node.getChildCount() >= 0) {
                TreePath path;
                for (Enumeration<?> e = node.children(); e.hasMoreElements();) {
                    path = parent.pathByAddingChild(e.nextElement());

                    // recursive call:
                    expandAll(path, expand, true);
                }
            }

            if (process) {
                // Expansion or collapse must be done bottom-up
                if (expand) {
                    expandPath(parent);
                } else {
                    collapsePath(parent);
                }
            }
        }

        /**
         * Called by the renderers to convert the specified value to
         * text. This implementation returns <code>value.toString</code>, ignoring
         * all other arguments. To control the conversion, subclass this
         * method and use any of the arguments you need.
         *
         * @param value the <code>Object</code> to convert to text
         * @param selected true if the node is selected
         * @param expanded true if the node is expanded
         * @param leaf  true if the node is a leaf node
         * @param row  an integer specifying the node's display row, where 0 is
         *             the first row in the display
         * @param hasFocus true if the node has the focus
         * @return the <code>String</code> representation of the node's value
         */
        @Override
        @SuppressWarnings("unchecked")
        public final String convertValueToText(final Object value, final boolean selected, final boolean expanded,
                final boolean leaf, final int row, final boolean hasFocus) {

            if (value != null) {
                String sValue = null;

                if (value instanceof DefaultMutableTreeNode) {
                    final DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
                    final Object userObject = node.getUserObject();

                    if (userObject != null) {
                        sValue = convertObjectToString(userObject);
                    }

                } else {
                    logger.error("unsupported class type = {}", value.getClass());

                    sValue = value.toString();
                }

                if (sValue != null) {
                    return sValue;
                }

            }
            return "";
        }

        /**
         * Convert a non-null value object to string
         * @param userObject user object to convert
         * @return string representation of the user object
         */
        @SuppressWarnings("unchecked")
        private String convertObjectToString(final Object userObject) {
            // Check first for string (root node and default tree model):
            if (userObject instanceof String) {
                return userObject.toString();
            }
            // Check if the class type matches (exact class comparison):
            if (_classType == null || _classType.isAssignableFrom(userObject.getClass())) {
                return convertUserObjectToString((E) userObject);
            }
            return toString(userObject);
        }

        /**
         * Default toString() conversion
         * @param userObject user object to convert
         * @return string representation of the user object
         */
        protected final String toString(final Object userObject) {
            if (!(userObject instanceof String)) {
                logger.warn("Unsupported class type = {}", userObject.getClass());
            }
            // String representation :
            return userObject.toString();
        }

        /**
         * Convert a non-null value object to string
         * @param userObject user object to convert
         * @return string representation of the user object
         */
        protected abstract String convertUserObjectToString(final E userObject);
    }

    /**
     * Component adapter to force a resizable component to have a minimal dimension
     * 
     * Note: code copied from @see fr.jmmc.jmcs.gui.ComponentResizeAdapter to let this class be self consistent
     */
    private static final class ComponentResizeAdapter extends ComponentAdapter {

        /** minimal dimension to respect */
        private final Dimension _dim;

        /**
         * Constructor with a given minimal dimension
         * @param dim minimal dimension
         */
        protected ComponentResizeAdapter(final Dimension dim) {
            _dim = dim;
        }

        /**
         * Invoked when the component's size changes.
         * This override method checks that the new size is greater than the minimal dimension
         * @param e event to process
         */
        @Override
        public void componentResized(final ComponentEvent e) {
            final Component c = e.getComponent();
            final Dimension d = c.getSize();
            int w = d.width;
            if (w < _dim.width) {
                w = _dim.width;
            }
            int h = d.height;
            if (h < _dim.height) {
                h = _dim.height;
            }

            c.setSize(w, h);
        }
    }

    /**
     * Resolve all existing J.U.L Logger as logback Loggers to see them in the configuration
     */
    private void visitJulLoggers() {
        _logger.debug("visitJulLoggers: start");

        java.util.logging.LogManager julManager = java.util.logging.LogManager.getLogManager();

        for (Enumeration<String> e = julManager.getLoggerNames(); e.hasMoreElements();) {
            final String loggerName = e.nextElement();

            if (!StringUtils.isEmpty(loggerName)) {
                _logger.debug("JUL logger: {}", loggerName);

                // Resolve Logback logger:
                LoggerFactory.getLogger(loggerName);
            }
        }

        _logger.debug("visitJulLoggers: end");
    }
}