View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17
    18  package org.apache.log4j.chainsaw;
    19
    20  import org.apache.log4j.*;
    21  import org.apache.log4j.chainsaw.color.RuleColorizer;
    22  import org.apache.log4j.chainsaw.dnd.FileDnDTarget;
    23  import org.apache.log4j.chainsaw.help.HelpManager;
    24  import org.apache.log4j.chainsaw.help.Tutorial;
    25  import org.apache.log4j.chainsaw.helper.SwingHelper;
    26  import org.apache.log4j.chainsaw.icons.ChainsawIcons;
    27  import org.apache.log4j.chainsaw.icons.LineIconFactory;
    28  import org.apache.log4j.chainsaw.messages.MessageCenter;
    29  import org.apache.log4j.chainsaw.osx.OSXIntegration;
    30  import org.apache.log4j.chainsaw.plugins.PluginClassLoaderFactory;
    31  import org.apache.log4j.chainsaw.prefs.*;
    32  import org.apache.log4j.chainsaw.receivers.ReceiversHelper;
    33  import org.apache.log4j.chainsaw.receivers.ReceiversPanel;
    34  import org.apache.log4j.chainsaw.vfs.VFSLogFilePatternReceiver;
    35  import org.apache.log4j.net.SocketNodeEventListener;
    36  import org.apache.log4j.plugins.*;
    37  import org.apache.log4j.rewrite.PropertyRewritePolicy;
    38  import org.apache.log4j.rewrite.RewriteAppender;
    39  import org.apache.log4j.rule.ExpressionRule;
    40  import org.apache.log4j.rule.Rule;
    41  import org.apache.log4j.spi.Decoder;
    42  import org.apache.log4j.spi.LoggerRepository;
    43  import org.apache.log4j.spi.LoggerRepositoryEx;
    44  import org.apache.log4j.spi.LoggingEvent;
    45  import org.apache.log4j.xml.DOMConfigurator;
    46  import org.apache.log4j.xml.XMLDecoder;
    47
    48  import javax.swing.*;
    49  import javax.swing.event.ChangeEvent;
    50  import javax.swing.event.ChangeListener;
    51  import javax.swing.event.EventListenerList;
    52  import javax.swing.event.HyperlinkEvent;
    53  import java.awt.*;
    54  import java.awt.event.*;
    55  import java.beans.PropertyChangeListener;
    56  import java.io.File;
    57  import java.io.IOException;
    58  import java.lang.reflect.Method;
    59  import java.net.MalformedURLException;
    60  import java.net.URISyntaxException;
    61  import java.net.URL;
    62  import java.security.*;
    63  import java.util.*;
    64  import java.util.List;
    65
    66
    67  /**
    68   * The main entry point for Chainsaw, this class represents the first frame
    69   * that is used to display a Welcome panel, and any other panels that are
    70   * generated because Logging Events are streamed via a Receiver, or other
    71   * mechanism.
    72   * <p>
    73   * NOTE: Some of Chainsaw's application initialization should be performed prior
    74   * to activating receivers and the logging framework used to perform self-logging.
    75   * <p>
    76   * DELAY as much as possible the logging framework initialization process,
    77   * currently initialized by the creation of a ChainsawAppenderHandler.
    78   *
    79   * @author Scott Deboy &lt;sdeboy@apache.org&gt;
    80   * @author Paul Smith  &lt;psmith@apache.org&gt;
    81   */
    82  public class LogUI extends JFrame implements ChainsawViewer, SettingsListener {
    83      private static final String MAIN_WINDOW_HEIGHT = "main.window.height";
    84      private static final String MAIN_WINDOW_WIDTH = "main.window.width";
    85      private static final String MAIN_WINDOW_Y = "main.window.y";
    86      private static final String MAIN_WINDOW_X = "main.window.x";
    87      private static ChainsawSplash splash;
    88      private static final double DEFAULT_MAIN_RECEIVER_SPLIT_LOCATION = 0.85d;
    89      private final JFrame preferencesFrame = new JFrame();
    90      private boolean noReceiversDefined;
    91      private ReceiversPanel receiversPanel;
    92      private ChainsawTabbedPane tabbedPane;
    93      private JToolBar toolbar;
    94      private ChainsawStatusBar statusBar;
    95      private ApplicationPreferenceModel applicationPreferenceModel;
    96      private ApplicationPreferenceModelPanel applicationPreferenceModelPanel;
    97      private final Map tableModelMap = new HashMap();
    98      private final Map tableMap = new HashMap();
    99      private final List<String> filterableColumns = new ArrayList<>();
    100     private final Map<String, Component> panelMap = new HashMap<>();
    101     ChainsawAppenderHandler handler;
    102     private ChainsawToolBarAndMenus tbms;
    103     private ChainsawAbout aboutBox;
    104     private final SettingsManager sm = SettingsManager.getInstance();
    105     private final JFrame tutorialFrame = new JFrame("Chainsaw Tutorial");
    106     private JSplitPane mainReceiverSplitPane;
    107     private double lastMainReceiverSplitLocation = DEFAULT_MAIN_RECEIVER_SPLIT_LOCATION;
    108     private final List<LogPanel> identifierPanels = new ArrayList<>();
    109     private int dividerSize;
    110     private int cyclicBufferSize;
    111     private static Logger logger;
    112     private static String configurationURLAppArg;
    113
    114     /**
    115      * Set to true, if and only if the GUI has completed it's full
    116      * initialization. Any logging events that come in must wait until this is
    117      * true, and if it is false, should wait on the initializationLock object
    118      * until notified.
    119      */
    120     private boolean isGUIFullyInitialized = false;
    121     private final Object initializationLock = new Object();
    122
    123     /**
    124      * The shutdownAction is called when the user requests to exit Chainsaw, and
    125      * by default this exits the VM, but a developer may replace this action with
    126      * something that better suits their needs
    127      */
    128     private Action shutdownAction = null;
    129
    130     /**
    131      * Clients can register a ShutdownListener to be notified when the user has
    132      * requested Chainsaw to exit.
    133      */
    134     private EventListenerList shutdownListenerList = new EventListenerList();
    135     private WelcomePanel welcomePanel;
    136
    137     private static final Object repositorySelectorGuard = new Object();
    138     private static final LoggerRepositoryExImpl repositoryExImpl = new LoggerRepositoryExImpl(LogManager.getLoggerRepository());
    139
    140     private PluginRegistry pluginRegistry;
    141     //map of tab names to rulecolorizers
    142     private Map<String, RuleColorizer> allColorizers = new HashMap<>();
    143     private ReceiverConfigurationPanel receiverConfigurationPanel = new ReceiverConfigurationPanel();
    144
    145     /**
    146      * Constructor which builds up all the visual elements of the frame including
    147      * the Menu bar
    148      */
    149     public LogUI() {
    150         super("Chainsaw");
    151         setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    152
    153         if (ChainsawIcons.WINDOW_ICON != null) {
    154             setIconImage(new ImageIcon(ChainsawIcons.WINDOW_ICON).getImage());
    155         }
    156     }
    157
    158     private static final void showSplash(Frame owner) {
    159         splash = new ChainsawSplash(owner);
    160         SwingHelper.centerOnScreen(splash);
    161         splash.setVisible(true);
    162     }
    163
    164     private static final void removeSplash() {
    165         if (splash != null) {
    166             splash.setVisible(false);
    167             splash.dispose();
    168         }
    169     }
    170
    171     /**
    172      * Registers a ShutdownListener with this calss so that it can be notified
    173      * when the user has requested that Chainsaw exit.
    174      *
    175      * @param l
    176      */
    177     public void addShutdownListener(ShutdownListener l) {
    178         shutdownListenerList.add(ShutdownListener.class, l);
    179     }
    180
    181     /**
    182      * Removes the registered ShutdownListener so that the listener will not be
    183      * notified on a shutdown.
    184      *
    185      * @param l
    186      */
    187     public void removeShutdownListener(ShutdownListener l) {
    188         shutdownListenerList.remove(ShutdownListener.class, l);
    189     }
    190
    191     /**
    192      * Starts Chainsaw by attaching a new instance to the Log4J main root Logger
    193      * via a ChainsawAppender, and activates itself
    194      *
    195      * @param args
    196      */
    197     public static void main(String[] args) {
    198         if (args.length > 0) {
    199             configurationURLAppArg = args[0];
    200         }
    201
    202         if (OSXIntegration.IS_OSX) {
    203             System.setProperty("apple.laf.useScreenMenuBar", "true");
    204         }
    205
    206
    207         LogManager.setRepositorySelector(() -> repositoryExImpl, repositorySelectorGuard);
    208
    209         final ApplicationPreferenceModel model = new ApplicationPreferenceModel();
    210
    211         SettingsManager.getInstance().configure(new ApplicationPreferenceModelSaver(model));
    212         //if a configuration URL param was provided, set the configuration URL field to null
    213         if (configurationURLAppArg != null) {
    214             model.setBypassConfigurationURL(configurationURLAppArg);
    215         }
    216
    217         EventQueue.invokeLater(() -> {
    218             String lookAndFeelClassName = model.getLookAndFeelClassName();
    219             if (lookAndFeelClassName == null || lookAndFeelClassName.trim().equals("")) {
    220                 String osName = System.getProperty("os.name");
    221                 if (osName.toLowerCase(Locale.ENGLISH).startsWith("mac")) {
    222                     //no need to assign look and feel
    223                 } else if (osName.toLowerCase(Locale.ENGLISH).startsWith("windows")) {
    224                     lookAndFeelClassName = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
    225                     model.setLookAndFeelClassName(lookAndFeelClassName);
    226                 } else if (osName.toLowerCase(Locale.ENGLISH).startsWith("linux")) {
    227                     lookAndFeelClassName = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
    228                     model.setLookAndFeelClassName(lookAndFeelClassName);
    229                 }
    230             }
    231
    232             if (lookAndFeelClassName != null && !(lookAndFeelClassName.trim().equals(""))) {
    233                 loadLookAndFeelUsingPluginClassLoader(lookAndFeelClassName);
    234             }
    235             createChainsawGUI(model, null);
    236         });
    237     }
    238
    239     /**
    240      * Creates, activates, and then shows the Chainsaw GUI, optionally showing
    241      * the splash screen, and using the passed shutdown action when the user
    242      * requests to exit the application (if null, then Chainsaw will exit the vm)
    243      *
    244      * @param model
    245      * @param newShutdownAction DOCUMENT ME!
    246      */
    247     public static void createChainsawGUI(
    248         ApplicationPreferenceModel model, Action newShutdownAction) {
    249
    250         if (model.isOkToRemoveSecurityManager()) {
    251             MessageCenter
    252                 .getInstance()
    253                 .addMessage(
    254                     "User has authorised removal of Java Security Manager via preferences");
    255             System.setSecurityManager(null);
    256             // this SHOULD set the Policy/Permission stuff for any
    257             // code loaded from our custom classloader.
    258             // crossing fingers...
    259             Policy.setPolicy(new Policy() {
    260
    261                 public void refresh() {
    262                 }
    263
    264                 public PermissionCollection getPermissions(CodeSource codesource) {
    265                     Permissions perms = new Permissions();
    266                     perms.add(new AllPermission());
    267                     return (perms);
    268                 }
    269             });
    270         }
    271
    272         final LogUI logUI = new LogUI();
    273         logUI.applicationPreferenceModel = model;
    274
    275         if (model.isShowSplash()) {
    276             showSplash(logUI);
    277         }
    278         logUI.cyclicBufferSize = model.getCyclicBufferSize();
    279         logUI.pluginRegistry = repositoryExImpl.getPluginRegistry();
    280
    281         logUI.handler = new ChainsawAppenderHandler();
    282         logUI.handler.addEventBatchListener(logUI.new NewTabEventBatchReceiver());
    283
    284         /**
    285          * TODO until we work out how JoranConfigurator might be able to have
    286          * configurable class loader, if at all.  For now we temporarily replace the
    287          * TCCL so that Plugins that need access to resources in
    288          * the Plugins directory can find them (this is particularly
    289          * important for the Web start version of Chainsaw
    290          */
    291         //configuration initialized here
    292         logUI.ensureChainsawAppenderHandlerAdded();
    293         logger = LogManager.getLogger(LogUI.class);
    294
    295         //set hostname, application and group properties which will cause Chainsaw and other apache-generated
    296         //logging events to route (by default) to a tab named 'chainsaw-log'
    297         PropertyRewritePolicy policy = new PropertyRewritePolicy();
    298         policy.setProperties("hostname=chainsaw,application=log,group=chainsaw");
    299
    300         RewriteAppender rewriteAppender = new RewriteAppender();
    301         rewriteAppender.setRewritePolicy(policy);
    302
    303         Enumeration appenders = Logger.getLogger("org.apache").getAllAppenders();
    304         if (!appenders.hasMoreElements()) {
    305             appenders = Logger.getRootLogger().getAllAppenders();
    306         }
    307         while (appenders.hasMoreElements()) {
    308             Appender nextAppender = (Appender) appenders.nextElement();
    309             rewriteAppender.addAppender(nextAppender);
    310         }
    311         Logger.getLogger("org.apache").removeAllAppenders();
    312         Logger.getLogger("org.apache").addAppender(rewriteAppender);
    313         Logger.getLogger("org.apache").setAdditivity(false);
    314
    315         //commons-vfs uses httpclient for http filesystem support, route this to the chainsaw-log tab as well
    316         appenders = Logger.getLogger("httpclient").getAllAppenders();
    317         if (!appenders.hasMoreElements()) {
    318             appenders = Logger.getRootLogger().getAllAppenders();
    319         }
    320         while (appenders.hasMoreElements()) {
    321             Appender nextAppender = (Appender) appenders.nextElement();
    322             rewriteAppender.addAppender(nextAppender);
    323         }
    324         Logger.getLogger("httpclient").removeAllAppenders();
    325         Logger.getLogger("httpclient").addAppender(rewriteAppender);
    326         Logger.getLogger("httpclient").setAdditivity(false);
    327
    328         //set the commons.vfs.cache logger to info, since it can contain password information
    329         Logger.getLogger("org.apache.commons.vfs.cache").setLevel(Level.INFO);
    330
    331         Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
    332             e.printStackTrace();
    333             logger.error("Uncaught exception in thread " + t, e);
    334         });
    335
    336         String config = configurationURLAppArg;
    337         if (config != null) {
    338             logger.info("Command-line configuration arg provided (overriding auto-configuration URL) - using: " + config);
    339         } else {
    340             config = model.getConfigurationURL();
    341         }
    342
    343         if (config != null && (!config.trim().equals(""))) {
    344             config = config.trim();
    345             try {
    346                 URL configURL = new URL(config);
    347                 logger.info("Using '" + config + "' for auto-configuration");
    348                 logUI.loadConfigurationUsingPluginClassLoader(configURL);
    349             } catch (MalformedURLException e) {
    350                 logger.error("Initial configuration - failed to convert config string to url", e);
    351             } catch (IOException e) {
    352                 logger.error("Unable to access auto-configuration URL: " + config);
    353             }
    354         }
    355
    356         //register a listener to load the configuration when it changes (avoid having to restart Chainsaw when applying a new configuration)
    357         //this doesn't remove receivers from receivers panel, it just triggers DOMConfigurator.configure.
    358         model.addPropertyChangeListener("configurationURL", evt -> {
    359             String newConfiguration = evt.getNewValue().toString();
    360             if (newConfiguration != null && !(newConfiguration.trim().equals(""))) {
    361                 newConfiguration = newConfiguration.trim();
    362                 try {
    363                     logger.info("loading updated configuration: " + newConfiguration);
    364                     URL newConfigurationURL = new URL(newConfiguration);
    365                     File file = new File(newConfigurationURL.toURI());
    366                     if (file.exists()) {
    367                         logUI.loadConfigurationUsingPluginClassLoader(newConfigurationURL);
    368                     } else {
    369                         logger.info("Updated configuration but file does not exist");
    370                     }
    371                 } catch (MalformedURLException | URISyntaxException e) {
    372                     logger.error("Updated configuration - failed to convert config string to URL", e);
    373                 }
    374             }
    375         });
    376
    377         LogManager.getRootLogger().setLevel(Level.TRACE);
    378         EventQueue.invokeLater(logUI::activateViewer);
    379
    380         logger.info("SecurityManager is now: " + System.getSecurityManager());
    381
    382         if (newShutdownAction != null) {
    383             logUI.setShutdownAction(newShutdownAction);
    384         } else {
    385             logUI.setShutdownAction(
    386                 new AbstractAction() {
    387                     public void actionPerformed(ActionEvent e) {
    388                         System.exit(0);
    389                     }
    390                 });
    391         }
    392     }
    393
    394     /**
    395      * Allow Chainsaw v2 to be ran in-process (configured as a ChainsawAppender)
    396      * NOTE: Closing Chainsaw will NOT stop the application generating the events.
    397      *
    398      * @param appender
    399      */
    400     public void activateViewer(ChainsawAppender appender) {
    401
    402         if (OSXIntegration.IS_OSX) {
    403             System.setProperty("apple.laf.useScreenMenuBar", "true");
    404         }
    405
    406         LogManager.setRepositorySelector(() -> repositoryExImpl, repositorySelectorGuard);
    407
    408         //if Chainsaw is launched as an appender, ensure the root logger level is TRACE
    409         LogManager.getRootLogger().setLevel(Level.TRACE);
    410
    411         final ApplicationPreferenceModel model = new ApplicationPreferenceModel();
    412         SettingsManager.getInstance().configure(new ApplicationPreferenceModelSaver(model));
    413
    414         cyclicBufferSize = model.getCyclicBufferSize();
    415
    416         handler = new ChainsawAppenderHandler(appender);
    417         handler.addEventBatchListener(new NewTabEventBatchReceiver());
    418
    419         logger = LogManager.getLogger(LogUI.class);
    420
    421         setShutdownAction(
    422             new AbstractAction() {
    423                 public void actionPerformed(ActionEvent e) {
    424                 }
    425             });
    426
    427         applicationPreferenceModel = new ApplicationPreferenceModel();
    428
    429         SettingsManager.getInstance().configure(new ApplicationPreferenceModelSaver(model));
    430
    431         EventQueue.invokeLater(() -> {
    432             loadLookAndFeelUsingPluginClassLoader(model.getLookAndFeelClassName());
    433             createChainsawGUI(model, null);
    434             getApplicationPreferenceModel().apply(model);
    435             activateViewer();
    436         });
    437     }
    438
    439     /**
    440      * Initialises the menu's and toolbars, but does not actually create any of
    441      * the main panel components.
    442      */
    443     private void initGUI() {
    444
    445         setupHelpSystem();
    446         statusBar = new ChainsawStatusBar(this);
    447         setupReceiverPanel();
    448
    449         setToolBarAndMenus(new ChainsawToolBarAndMenus(this));
    450         toolbar = getToolBarAndMenus().getToolbar();
    451         setJMenuBar(getToolBarAndMenus().getMenubar());
    452
    453         setTabbedPane(new ChainsawTabbedPane());
    454         getSettingsManager().addSettingsListener(getTabbedPane());
    455         getSettingsManager().configure(getTabbedPane());
    456
    457         /**
    458          * This adds Drag & Drop capability to Chainsaw
    459          */
    460         FileDnDTarget dnDTarget = new FileDnDTarget(tabbedPane);
    461         dnDTarget.addPropertyChangeListener("fileList", evt -> {
    462             final List fileList = (List) evt.getNewValue();
    463
    464             Thread thread = new Thread(() -> {
    465                 logger.debug("Loading files: " + fileList);
    466                 for (Object aFileList : fileList) {
    467                     File file = (File) aFileList;
    468                     final Decoder decoder = new XMLDecoder();
    469                     try {
    470                         getStatusBar().setMessage("Loading " + file.getAbsolutePath() + "...");
    471                         FileLoadAction.importURL(handler, decoder, file
    472                             .getName(), file.toURI().toURL());
    473                     } catch (Exception e) {
    474                         String errorMsg = "Failed to import a file";
    475                         logger.error(errorMsg, e);
    476                         getStatusBar().setMessage(errorMsg);
    477                     }
    478                 }
    479
    480             });
    481
    482             thread.setPriority(Thread.MIN_PRIORITY);
    483             thread.start();
    484
    485         });
    486
    487         applicationPreferenceModelPanel = new ApplicationPreferenceModelPanel(applicationPreferenceModel);
    488
    489         applicationPreferenceModelPanel.setOkCancelActionListener(
    490             e -> preferencesFrame.setVisible(false));
    491         KeyStroke escape = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
    492         Action closeAction = new AbstractAction() {
    493             public void actionPerformed(ActionEvent e) {
    494                 preferencesFrame.setVisible(false);
    495             }
    496         };
    497         preferencesFrame.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escape, "ESCAPE");
    498         preferencesFrame.getRootPane().
    499             getActionMap().put("ESCAPE", closeAction);
    500
    501         OSXIntegration.init(this);
    502
    503     }
    504
    505     private void initPlugins(PluginRegistry pluginRegistry) {
    506         pluginRegistry.addPluginListener(
    507             new PluginListener() {
    508                 public void pluginStarted(PluginEvent e) {
    509                     if (e.getPlugin() instanceof JComponent) {
    510                         JComponent plugin = (JComponent) e.getPlugin();
    511                         getTabbedPane().addANewTab(plugin.getName(), plugin, null, null);
    512                         getPanelMap().put(plugin.getName(), plugin);
    513                     }
    514                 }
    515
    516                 public void pluginStopped(PluginEvent e) {
    517                     //TODO remove the plugin from the gui
    518                 }
    519             });
    520
    521         // TODO this should all be in a config file
    522 //    ChainsawCentral cc = new ChainsawCentral();
    523 //    pluginRegistry.addPlugin(cc);
    524 //    cc.activateOptions();
    525
    526         try {
    527             Class<? extends Plugin> pluginClass = Class.forName("org.apache.log4j.chainsaw.zeroconf.ZeroConfPlugin").asSubclass(Plugin.class);
    528             Plugin plugin = pluginClass.newInstance();
    529             pluginRegistry.addPlugin(plugin);
    530             plugin.activateOptions();
    531             MessageCenter.getInstance().getLogger().info("Looks like ZeroConf is available... WooHoo!");
    532         } catch (Throwable e) {
    533             MessageCenter.getInstance().getLogger().error("Doesn't look like ZeroConf is available", e);
    534         }
    535     }
    536
    537     private void setupReceiverPanel() {
    538         receiversPanel = new ReceiversPanel();
    539         receiversPanel.addPropertyChangeListener(
    540             "visible",
    541             evt -> getApplicationPreferenceModel().setReceivers(
    542                 (Boolean) evt.getNewValue()));
    543     }
    544
    545     /**
    546      * Initialises the Help system and the WelcomePanel
    547      */
    548     private void setupHelpSystem() {
    549         welcomePanel = new WelcomePanel();
    550
    551         JToolBar tb = welcomePanel.getToolbar();
    552
    553
    554         tb.add(
    555             new SmallButton(
    556                 new AbstractAction("Tutorial", new ImageIcon(ChainsawIcons.HELP)) {
    557                     public void actionPerformed(ActionEvent e) {
    558                         setupTutorial();
    559                     }
    560                 }));
    561         tb.addSeparator();
    562
    563         final Action exampleConfigAction =
    564             new AbstractAction("View example Receiver configuration") {
    565                 public void actionPerformed(ActionEvent e) {
    566                     HelpManager.getInstance().setHelpURL(
    567                         ChainsawConstants.EXAMPLE_CONFIG_URL);
    568                 }
    569             };
    570
    571         exampleConfigAction.putValue(
    572             Action.SHORT_DESCRIPTION,
    573             "Displays an example Log4j configuration file with several Receivers defined.");
    574
    575         JButton exampleButton = new SmallButton(exampleConfigAction);
    576         tb.add(exampleButton);
    577
    578         tb.add(Box.createHorizontalGlue());
    579
    580         /**
    581          * Setup a listener on the HelpURL property and automatically change the WelcomePages URL
    582          * to it.
    583          */
    584         HelpManager.getInstance().addPropertyChangeListener(
    585             "helpURL",
    586             evt -> {
    587                 URL newURL = (URL) evt.getNewValue();
    588
    589                 if (newURL != null) {
    590                     welcomePanel.setURL(newURL);
    591                     ensureWelcomePanelVisible();
    592                 }
    593             });
    594     }
    595
    596     private void ensureWelcomePanelVisible() {
    597         // ensure that the Welcome Panel is made visible
    598         if (!getTabbedPane().containsWelcomePanel()) {
    599             addWelcomePanel();
    600         }
    601         getTabbedPane().setSelectedComponent(welcomePanel);
    602     }
    603
    604     /**
    605      * Given the load event, configures the size/location of the main window etc
    606      * etc.
    607      *
    608      * @param event DOCUMENT ME!
    609      */
    610     public void loadSettings(LoadSettingsEvent event) {
    611         setLocation(
    612             event.asInt(LogUI.MAIN_WINDOW_X), event.asInt(LogUI.MAIN_WINDOW_Y));
    613         int width = event.asInt(LogUI.MAIN_WINDOW_WIDTH);
    614         int height = event.asInt(LogUI.MAIN_WINDOW_HEIGHT);
    615         if (width == -1 && height == -1) {
    616             width = Toolkit.getDefaultToolkit().getScreenSize().width;
    617             height = Toolkit.getDefaultToolkit().getScreenSize().height;
    618             setSize(width, height);
    619             setExtendedState(getExtendedState() | MAXIMIZED_BOTH);
    620         } else {
    621             setSize(width, height);
    622         }
    623
    624         getToolBarAndMenus().stateChange();
    625         RuleColorizer colorizer = new RuleColorizer();
    626         colorizer.loadColorSettings(ChainsawConstants.DEFAULT_COLOR_RULE_NAME);
    627         allColorizers.put(ChainsawConstants.DEFAULT_COLOR_RULE_NAME, colorizer);
    628         if (event.getSetting("SavedConfig.logFormat") != null) {
    629             receiverConfigurationPanel.getModel().setLogFormat(event.getSetting("SavedConfig.logFormat"));
    630         }
    631     }
    632
    633     /**
    634      * Ensures the location/size of the main window is stored with the settings
    635      *
    636      * @param event DOCUMENT ME!
    637      */
    638     public void saveSettings(SaveSettingsEvent event) {
    639         event.saveSetting(LogUI.MAIN_WINDOW_X, (int) getLocation().getX());
    640         event.saveSetting(LogUI.MAIN_WINDOW_Y, (int) getLocation().getY());
    641
    642         event.saveSetting(LogUI.MAIN_WINDOW_WIDTH, getWidth());
    643         event.saveSetting(LogUI.MAIN_WINDOW_HEIGHT, getHeight());
    644         RuleColorizer colorizer = allColorizers.get(ChainsawConstants.DEFAULT_COLOR_RULE_NAME);
    645         colorizer.saveColorSettings(ChainsawConstants.DEFAULT_COLOR_RULE_NAME);
    646         if (receiverConfigurationPanel.getModel().getLogFormat() != null) {
    647             event.saveSetting("SavedConfig.logFormat", receiverConfigurationPanel.getModel().getLogFormat());
    648         }
    649     }
    650
    651     /**
    652      * Activates itself as a viewer by configuring Size, and location of itself,
    653      * and configures the default Tabbed Pane elements with the correct layout,
    654      * table columns, and sets itself viewable.
    655      */
    656     public void activateViewer() {
    657         LoggerRepository repo = LogManager.getLoggerRepository();
    658         if (repo instanceof LoggerRepositoryEx) {
    659             this.pluginRegistry = ((LoggerRepositoryEx) repo).getPluginRegistry();
    660         }
    661         initGUI();
    662
    663         initPrefModelListeners();
    664
    665         /**
    666          * We add a simple appender to the MessageCenter logger
    667          * so that each message is displayed in the Status bar
    668          */
    669         MessageCenter.getInstance().getLogger().addAppender(
    670             new AppenderSkeleton() {
    671                 protected void append(LoggingEvent event) {
    672                     getStatusBar().setMessage(event.getMessage().toString());
    673                 }
    674
    675                 public void close() {
    676                 }
    677
    678                 public boolean requiresLayout() {
    679                     return false;
    680                 }
    681             });
    682
    683
    684         initSocketConnectionListener();
    685
    686         if (pluginRegistry.getPlugins(Receiver.class).size() == 0) {
    687             noReceiversDefined = true;
    688         }
    689
    690         getFilterableColumns().add(ChainsawConstants.LEVEL_COL_NAME);
    691         getFilterableColumns().add(ChainsawConstants.LOGGER_COL_NAME);
    692         getFilterableColumns().add(ChainsawConstants.THREAD_COL_NAME);
    693         getFilterableColumns().add(ChainsawConstants.NDC_COL_NAME);
    694         getFilterableColumns().add(ChainsawConstants.PROPERTIES_COL_NAME);
    695         getFilterableColumns().add(ChainsawConstants.CLASS_COL_NAME);
    696         getFilterableColumns().add(ChainsawConstants.METHOD_COL_NAME);
    697         getFilterableColumns().add(ChainsawConstants.FILE_COL_NAME);
    698         getFilterableColumns().add(ChainsawConstants.NONE_COL_NAME);
    699
    700         JPanel panePanel = new JPanel();
    701         panePanel.setLayout(new BorderLayout(2, 2));
    702
    703         getContentPane().setLayout(new BorderLayout());
    704
    705         getTabbedPane().addChangeListener(getToolBarAndMenus());
    706         getTabbedPane().addChangeListener(e -> {
    707             LogPanel thisLogPanel = getCurrentLogPanel();
    708             if (thisLogPanel != null) {
    709                 thisLogPanel.updateStatusBar();
    710             }
    711         });
    712
    713         KeyStroke ksRight =
    714             KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    715         KeyStroke ksLeft =
    716             KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    717         KeyStroke ksGotoLine =
    718             KeyStroke.getKeyStroke(KeyEvent.VK_G, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask());
    719
    720         getTabbedPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
    721             ksRight, "MoveRight");
    722         getTabbedPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
    723             ksLeft, "MoveLeft");
    724         getTabbedPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
    725             ksGotoLine, "GotoLine");
    726
    727         Action moveRight =
    728             new AbstractAction() {
    729                 public void actionPerformed(ActionEvent e) {
    730                     int temp = getTabbedPane().getSelectedIndex();
    731                     ++temp;
    732
    733                     if (temp != getTabbedPane().getTabCount()) {
    734                         getTabbedPane().setSelectedTab(temp);
    735                     }
    736                 }
    737             };
    738
    739         Action moveLeft =
    740             new AbstractAction() {
    741                 public void actionPerformed(ActionEvent e) {
    742                     int temp = getTabbedPane().getSelectedIndex();
    743                     --temp;
    744
    745                     if (temp > -1) {
    746                         getTabbedPane().setSelectedTab(temp);
    747                     }
    748                 }
    749             };
    750
    751         Action gotoLine =
    752             new AbstractAction() {
    753                 public void actionPerformed(ActionEvent e) {
    754                     String inputLine = JOptionPane.showInputDialog(LogUI.this, "Enter the line number to go:", "Goto Line", JOptionPane.PLAIN_MESSAGE);
    755                     try {
    756                         int lineNumber = Integer.parseInt(inputLine);
    757                         int row = getCurrentLogPanel().setSelectedEvent(lineNumber);
    758                         if (row == -1) {
    759                             JOptionPane.showMessageDialog(LogUI.this, "You have entered an invalid line number", "Error", JOptionPane.ERROR_MESSAGE);
    760                         }
    761                     } catch (NumberFormatException nfe) {
    762                         JOptionPane.showMessageDialog(LogUI.this, "You have entered an invalid line number", "Error", JOptionPane.ERROR_MESSAGE);
    763                     }
    764                 }
    765             };
    766
    767
    768         getTabbedPane().getActionMap().put("MoveRight", moveRight);
    769         getTabbedPane().getActionMap().put("MoveLeft", moveLeft);
    770         getTabbedPane().getActionMap().put("GotoLine", gotoLine);
    771
    772         /**
    773          * We listen for double clicks, and auto-undock currently selected Tab if
    774          * the mouse event location matches the currently selected tab
    775          */
    776         getTabbedPane().addMouseListener(
    777             new MouseAdapter() {
    778                 public void mouseClicked(MouseEvent e) {
    779                     super.mouseClicked(e);
    780
    781                     if (
    782                         (e.getClickCount() > 1)
    783                             && ((e.getModifiers() & InputEvent.BUTTON1_MASK) > 0)) {
    784                         int tabIndex = getTabbedPane().getSelectedIndex();
    785
    786                         if (
    787                             (tabIndex != -1)
    788                                 && (tabIndex == getTabbedPane().getSelectedIndex())) {
    789                             LogPanel logPanel = getCurrentLogPanel();
    790
    791                             if (logPanel != null) {
    792                                 logPanel.undock();
    793                             }
    794                         }
    795                     }
    796                 }
    797             });
    798
    799         panePanel.add(getTabbedPane());
    800         addWelcomePanel();
    801
    802         getContentPane().add(toolbar, BorderLayout.NORTH);
    803         getContentPane().add(statusBar, BorderLayout.SOUTH);
    804
    805         mainReceiverSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, panePanel, receiversPanel);
    806         dividerSize = mainReceiverSplitPane.getDividerSize();
    807         mainReceiverSplitPane.setDividerLocation(-1);
    808
    809         getContentPane().add(mainReceiverSplitPane, BorderLayout.CENTER);
    810
    811         /**
    812          * We need to make sure that all the internal GUI components have been added to the
    813          * JFrame so that any plugns that get activated during initPlugins(...) method
    814          * have access to inject menus
    815          */
    816         initPlugins(pluginRegistry);
    817
    818         mainReceiverSplitPane.setResizeWeight(1.0);
    819         addWindowListener(
    820             new WindowAdapter() {
    821                 public void windowClosing(WindowEvent event) {
    822                     exit();
    823                 }
    824             });
    825         preferencesFrame.setTitle("'Application-wide Preferences");
    826         preferencesFrame.setIconImage(
    827             ((ImageIcon) ChainsawIcons.ICON_PREFERENCES).getImage());
    828         preferencesFrame.getContentPane().add(applicationPreferenceModelPanel);
    829
    830         preferencesFrame.setSize(750, 520);
    831
    832         Dimension screenDimension = Toolkit.getDefaultToolkit().getScreenSize();
    833         preferencesFrame.setLocation(
    834             new Point(
    835                 (screenDimension.width / 2) - (preferencesFrame.getSize().width / 2),
    836                 (screenDimension.height / 2) - (preferencesFrame.getSize().height / 2)));
    837
    838         pack();
    839
    840         final JPopupMenu tabPopup = new JPopupMenu();
    841         final Action hideCurrentTabAction =
    842             new AbstractAction("Hide") {
    843                 public void actionPerformed(ActionEvent e) {
    844                     Component selectedComp = getTabbedPane().getSelectedComponent();
    845                     if (selectedComp instanceof LogPanel) {
    846                         displayPanel(getCurrentLogPanel().getIdentifier(), false);
    847                         tbms.stateChange();
    848                     } else {
    849                         getTabbedPane().remove(selectedComp);
    850                     }
    851                 }
    852             };
    853
    854         final Action hideOtherTabsAction =
    855             new AbstractAction("Hide Others") {
    856                 public void actionPerformed(ActionEvent e) {
    857                     Component selectedComp = getTabbedPane().getSelectedComponent();
    858                     String currentName;
    859                     if (selectedComp instanceof LogPanel) {
    860                         currentName = getCurrentLogPanel().getIdentifier();
    861                     } else if (selectedComp instanceof WelcomePanel) {
    862                         currentName = ChainsawTabbedPane.WELCOME_TAB;
    863                     } else {
    864                         currentName = ChainsawTabbedPane.ZEROCONF;
    865                     }
    866
    867                     int count = getTabbedPane().getTabCount();
    868                     int index = 0;
    869
    870                     for (int i = 0; i < count; i++) {
    871                         String name = getTabbedPane().getTitleAt(index);
    872
    873                         if (
    874                             getPanelMap().keySet().contains(name)
    875                                 && !name.equals(currentName)) {
    876                             displayPanel(name, false);
    877                             tbms.stateChange();
    878                         } else {
    879                             index++;
    880                         }
    881                     }
    882                 }
    883             };
    884
    885         Action showHiddenTabsAction =
    886             new AbstractAction("Show All Hidden") {
    887                 public void actionPerformed(ActionEvent e) {
    888                     for (Object o : getPanels().entrySet()) {
    889                         Map.Entry entry = (Map.Entry) o;
    890                         Boolean docked = (Boolean) entry.getValue();
    891                         if (docked) {
    892                             String identifier = (String) entry.getKey();
    893                             int count = getTabbedPane().getTabCount();
    894                             boolean found = false;
    895
    896                             for (int i = 0; i < count; i++) {
    897                                 String name = getTabbedPane().getTitleAt(i);
    898
    899                                 if (name.equals(identifier)) {
    900                                     found = true;
    901
    902                                     break;
    903                                 }
    904                             }
    905
    906                             if (!found) {
    907                                 displayPanel(identifier, true);
    908                                 tbms.stateChange();
    909                             }
    910                         }
    911                     }
    912                 }
    913             };
    914
    915         tabPopup.add(hideCurrentTabAction);
    916         tabPopup.add(hideOtherTabsAction);
    917         tabPopup.addSeparator();
    918         tabPopup.add(showHiddenTabsAction);
    919
    920         final PopupListener tabPopupListener = new PopupListener(tabPopup);
    921         getTabbedPane().addMouseListener(tabPopupListener);
    922
    923         this.handler.addPropertyChangeListener(
    924             "dataRate",
    925             evt -> {
    926                 double dataRate = (Double) evt.getNewValue();
    927                 statusBar.setDataRate(dataRate);
    928             });
    929
    930         getSettingsManager().addSettingsListener(this);
    931         getSettingsManager().addSettingsListener(MRUFileListPreferenceSaver.getInstance());
    932         getSettingsManager().addSettingsListener(receiversPanel);
    933         try {
    934             //if an uncaught exception is thrown, allow the UI to continue to load
    935             getSettingsManager().loadSettings();
    936         } catch (Exception e) {
    937             e.printStackTrace();
    938         }
    939         //app preferences have already been loaded (and configuration url possibly set to blank if being overridden)
    940         //but we need a listener so the settings will be saved on exit (added after loadsettings was called)
    941         getSettingsManager().addSettingsListener(new ApplicationPreferenceModelSaver(applicationPreferenceModel));
    942
    943         setVisible(true);
    944
    945         if (applicationPreferenceModel.isReceivers()) {
    946             showReceiverPanel();
    947         } else {
    948             hideReceiverPanel();
    949         }
    950
    951         removeSplash();
    952
    953         synchronized (initializationLock) {
    954             isGUIFullyInitialized = true;
    955             initializationLock.notifyAll();
    956         }
    957
    958         if (
    959             noReceiversDefined
    960                 && applicationPreferenceModel.isShowNoReceiverWarning()) {
    961             SwingHelper.invokeOnEDT(this::showReceiverConfigurationPanel);
    962         }
    963
    964         Container container = tutorialFrame.getContentPane();
    965         final JEditorPane tutorialArea = new JEditorPane();
    966         tutorialArea.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
    967         tutorialArea.setEditable(false);
    968         container.setLayout(new BorderLayout());
    969
    970         try {
    971             tutorialArea.setPage(ChainsawConstants.TUTORIAL_URL);
    972             JTextComponentFormatter.applySystemFontAndSize(tutorialArea);
    973
    974             container.add(new JScrollPane(tutorialArea), BorderLayout.CENTER);
    975         } catch (Exception e) {
    976             MessageCenter.getInstance().getLogger().error(
    977                 "Error occurred loading the Tutorial", e);
    978         }
    979
    980         tutorialFrame.setIconImage(new ImageIcon(ChainsawIcons.HELP).getImage());
    981         tutorialFrame.setSize(new Dimension(640, 480));
    982
    983         final Action startTutorial =
    984             new AbstractAction(
    985                 "Start Tutorial", new ImageIcon(ChainsawIcons.ICON_RESUME_RECEIVER)) {
    986                 public void actionPerformed(ActionEvent e) {
    987                     if (
    988                         JOptionPane.showConfirmDialog(
    989                             null,
    990                             "This will start 3 \"Generator\" receivers for use in the Tutorial.  Is that ok?",
    991                             "Confirm", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
    992                         new Thread(new Tutorial()).start();
    993                         putValue("TutorialStarted", Boolean.TRUE);
    994                     } else {
    995                         putValue("TutorialStarted", Boolean.FALSE);
    996                     }
    997                 }
    998             };
    999
    1000         final Action stopTutorial =
    1001             new AbstractAction(
    1002                 "Stop Tutorial", new ImageIcon(ChainsawIcons.ICON_STOP_RECEIVER)) {
    1003                 public void actionPerformed(ActionEvent e) {
    1004                     if (
    1005                         JOptionPane.showConfirmDialog(
    1006                             null,
    1007                             "This will stop all of the \"Generator\" receivers used in the Tutorial, but leave any other Receiver untouched.  Is that ok?",
    1008                             "Confirm", JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) {
    1009                         new Thread(
    1010                             () -> {
    1011                                 LoggerRepository repo1 = LogManager.getLoggerRepository();
    1012                                 if (repo1 instanceof LoggerRepositoryEx) {
    1013                                     PluginRegistry pluginRegistry = ((LoggerRepositoryEx) repo1).getPluginRegistry();
    1014                                     List list = pluginRegistry.getPlugins(Generator.class);
    1015
    1016                                     for (Object aList : list) {
    1017                                         Plugin plugin = (Plugin) aList;
    1018                                         pluginRegistry.stopPlugin(plugin.getName());
    1019                                     }
    1020                                 }
    1021                             }).start();
    1022                         setEnabled(false);
    1023                         startTutorial.putValue("TutorialStarted", Boolean.FALSE);
    1024                     }
    1025                 }
    1026             };
    1027
    1028         stopTutorial.putValue(
    1029             Action.SHORT_DESCRIPTION,
    1030             "Removes all of the Tutorials Generator Receivers, leaving all other Receivers untouched");
    1031         startTutorial.putValue(
    1032             Action.SHORT_DESCRIPTION,
    1033             "Begins the Tutorial, starting up some Generator Receivers so you can see Chainsaw in action");
    1034         stopTutorial.setEnabled(false);
    1035
    1036         final SmallToggleButton startButton = new SmallToggleButton(startTutorial);
    1037         PropertyChangeListener pcl =
    1038             evt -> {
    1039                 stopTutorial.setEnabled(
    1040                     startTutorial.getValue("TutorialStarted").equals(Boolean.TRUE));
    1041                 startButton.setSelected(stopTutorial.isEnabled());
    1042             };
    1043
    1044         startTutorial.addPropertyChangeListener(pcl);
    1045         stopTutorial.addPropertyChangeListener(pcl);
    1046
    1047         pluginRegistry.addPluginListener(
    1048             new PluginListener() {
    1049                 public void pluginStarted(PluginEvent e) {
    1050                 }
    1051
    1052                 public void pluginStopped(PluginEvent e) {
    1053                     List list = pluginRegistry.getPlugins(Generator.class);
    1054
    1055                     if (list.size() == 0) {
    1056                         startTutorial.putValue("TutorialStarted", Boolean.FALSE);
    1057                     }
    1058                 }
    1059             });
    1060
    1061         final SmallButton stopButton = new SmallButton(stopTutorial);
    1062
    1063         final JToolBar tutorialToolbar = new JToolBar();
    1064         tutorialToolbar.setFloatable(false);
    1065         tutorialToolbar.add(startButton);
    1066         tutorialToolbar.add(stopButton);
    1067         container.add(tutorialToolbar, BorderLayout.NORTH);
    1068         tutorialArea.addHyperlinkListener(
    1069             e -> {
    1070                 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
    1071                     if (e.getDescription().equals("StartTutorial")) {
    1072                         startTutorial.actionPerformed(null);
    1073                     } else if (e.getDescription().equals("StopTutorial")) {
    1074                         stopTutorial.actionPerformed(null);
    1075                     } else {
    1076                         try {
    1077                             tutorialArea.setPage(e.getURL());
    1078                         } catch (IOException e1) {
    1079                             MessageCenter.getInstance().getLogger().error(
    1080                                 "Failed to change the URL for the Tutorial", e1);
    1081                         }
    1082                     }
    1083                 }
    1084             });
    1085
    1086         /**
    1087          * loads the saved tab settings and if there are hidden tabs,
    1088          * hide those tabs out of currently loaded tabs..
    1089          */
    1090
    1091         if (!getTabbedPane().tabSetting.isWelcome()) {
    1092             displayPanel(ChainsawTabbedPane.WELCOME_TAB, false);
    1093         }
    1094         if (!getTabbedPane().tabSetting.isZeroconf()) {
    1095             displayPanel(ChainsawTabbedPane.ZEROCONF, false);
    1096         }
    1097         tbms.stateChange();
    1098
    1099     }
    1100
    1101     /**
    1102      * Display the log tree pane, using the last known divider location
    1103      */
    1104     private void showReceiverPanel() {
    1105         mainReceiverSplitPane.setDividerSize(dividerSize);
    1106         mainReceiverSplitPane.setDividerLocation(lastMainReceiverSplitLocation);
    1107         receiversPanel.setVisible(true);
    1108         mainReceiverSplitPane.repaint();
    1109     }
    1110
    1111     /**
    1112      * Hide the log tree pane, holding the current divider location for later use
    1113      */
    1114     private void hideReceiverPanel() {
    1115         //subtract one to make sizes match
    1116         int currentSize = mainReceiverSplitPane.getWidth() - mainReceiverSplitPane.getDividerSize();
    1117         if (mainReceiverSplitPane.getDividerLocation() > -1) {
    1118             if (!(((mainReceiverSplitPane.getDividerLocation() + 1) == currentSize)
    1119                 || ((mainReceiverSplitPane.getDividerLocation() - 1) == 0))) {
    1120                 lastMainReceiverSplitLocation = ((double) mainReceiverSplitPane
    1121                     .getDividerLocation() / currentSize);
    1122             }
    1123         }
    1124         mainReceiverSplitPane.setDividerSize(0);
    1125         receiversPanel.setVisible(false);
    1126         mainReceiverSplitPane.repaint();
    1127     }
    1128
    1129     private void initSocketConnectionListener() {
    1130         final SocketNodeEventListener socketListener =
    1131             new SocketNodeEventListener() {
    1132                 public void socketOpened(String remoteInfo) {
    1133                     statusBar.remoteConnectionReceived(remoteInfo);
    1134                 }
    1135
    1136                 public void socketClosedEvent(Exception e) {
    1137                     MessageCenter.getInstance().getLogger().info(
    1138                         "Connection lost! :: " + e.getMessage());
    1139                 }
    1140             };
    1141
    1142         PluginListener pluginListener =
    1143             new PluginListener() {
    1144                 public void pluginStarted(PluginEvent e) {
    1145                     MessageCenter.getInstance().getLogger().info(
    1146                         e.getPlugin().getName() + " started!");
    1147
    1148                     Method method = getAddListenerMethod(e.getPlugin());
    1149
    1150                     if (method != null) {
    1151                         try {
    1152                             method.invoke(e.getPlugin(), socketListener);
    1153                         } catch (Exception ex) {
    1154                             MessageCenter.getInstance().getLogger().error(
    1155                                 "Failed to add a SocketNodeEventListener", ex);
    1156                         }
    1157                     }
    1158                 }
    1159
    1160                 Method getRemoveListenerMethod(Plugin p) {
    1161                     try {
    1162                         return p.getClass().getMethod(
    1163                             "removeSocketNodeEventListener",
    1164                             SocketNodeEventListener.class);
    1165                     } catch (Exception e) {
    1166                         return null;
    1167                     }
    1168                 }
    1169
    1170                 Method getAddListenerMethod(Plugin p) {
    1171                     try {
    1172                         return p.getClass().getMethod(
    1173                             "addSocketNodeEventListener",
    1174                             SocketNodeEventListener.class);
    1175                     } catch (Exception e) {
    1176                         return null;
    1177                     }
    1178                 }
    1179
    1180                 public void pluginStopped(PluginEvent e) {
    1181                     Method method = getRemoveListenerMethod(e.getPlugin());
    1182
    1183                     if (method != null) {
    1184                         try {
    1185                             method.invoke(e.getPlugin(), socketListener);
    1186                         } catch (Exception ex) {
    1187                             MessageCenter.getInstance().getLogger().error(
    1188                                 "Failed to remove SocketNodeEventListener", ex);
    1189                         }
    1190                     }
    1191
    1192                     MessageCenter.getInstance().getLogger().info(
    1193                         e.getPlugin().getName() + " stopped!");
    1194                 }
    1195             };
    1196
    1197         pluginRegistry.addPluginListener(pluginListener);
    1198     }
    1199
    1200     private void initPrefModelListeners() {
    1201         applicationPreferenceModel.addPropertyChangeListener(
    1202             "identifierExpression",
    1203             evt -> handler.setIdentifierExpression(evt.getNewValue().toString()));
    1204         handler.setIdentifierExpression(applicationPreferenceModel.getIdentifierExpression());
    1205
    1206
    1207         applicationPreferenceModel.addPropertyChangeListener(
    1208             "toolTipDisplayMillis",
    1209             evt -> ToolTipManager.sharedInstance().setDismissDelay(
    1210                 (Integer) evt.getNewValue()));
    1211         ToolTipManager.sharedInstance().setDismissDelay(
    1212             applicationPreferenceModel.getToolTipDisplayMillis());
    1213
    1214         applicationPreferenceModel.addPropertyChangeListener(
    1215             "responsiveness",
    1216             evt -> {
    1217                 int value = (Integer) evt.getNewValue();
    1218                 handler.setQueueInterval((value * 1000) - 750);
    1219             });
    1220         handler.setQueueInterval((applicationPreferenceModel.getResponsiveness() * 1000) - 750);
    1221
    1222         applicationPreferenceModel.addPropertyChangeListener(
    1223             "tabPlacement",
    1224             evt -> SwingUtilities.invokeLater(
    1225                 () -> {
    1226                     int placement = (Integer) evt.getNewValue();
    1227
    1228                     switch (placement) {
    1229                         case SwingConstants.TOP:
    1230                         case SwingConstants.BOTTOM:
    1231                             tabbedPane.setTabPlacement(placement);
    1232
    1233                             break;
    1234
    1235                         default:
    1236                             break;
    1237                     }
    1238                 }));
    1239
    1240         applicationPreferenceModel.addPropertyChangeListener(
    1241             "statusBar",
    1242             evt -> {
    1243                 boolean value = (Boolean) evt.getNewValue();
    1244                 setStatusBarVisible(value);
    1245             });
    1246         setStatusBarVisible(applicationPreferenceModel.isStatusBar());
    1247
    1248         applicationPreferenceModel.addPropertyChangeListener(
    1249             "receivers",
    1250             evt -> {
    1251                 boolean value = (Boolean) evt.getNewValue();
    1252
    1253                 if (value) {
    1254                     showReceiverPanel();
    1255                 } else {
    1256                     hideReceiverPanel();
    1257                 }
    1258             });
    1259 //    if (applicationPreferenceModel.isReceivers()) {
    1260 //      showReceiverPanel();
    1261 //    } else {
    1262 //      hideReceiverPanel();
    1263 //    }
    1264
    1265
    1266         applicationPreferenceModel.addPropertyChangeListener(
    1267             "toolbar",
    1268             evt -> {
    1269                 boolean value = (Boolean) evt.getNewValue();
    1270                 toolbar.setVisible(value);
    1271             });
    1272         toolbar.setVisible(applicationPreferenceModel.isToolbar());
    1273
    1274     }
    1275
    1276     /**
    1277      * Displays a dialog which will provide options for selecting a configuration
    1278      */
    1279     private void showReceiverConfigurationPanel() {
    1280         SwingUtilities.invokeLater(
    1281             () -> {
    1282                 final JDialog dialog = new JDialog(LogUI.this, true);
    1283                 dialog.setTitle("Load events into Chainsaw");
    1284                 dialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
    1285
    1286                 dialog.setResizable(false);
    1287
    1288                 receiverConfigurationPanel.setCompletionActionListener(
    1289                     e -> {
    1290                         dialog.setVisible(false);
    1291
    1292                         if (receiverConfigurationPanel.getModel().isCancelled()) {
    1293                             return;
    1294                         }
    1295                         applicationPreferenceModel.setShowNoReceiverWarning(!receiverConfigurationPanel.isDontWarnMeAgain());
    1296                         //remove existing plugins
    1297                         List<Plugin> plugins = pluginRegistry.getPlugins();
    1298                         for (Object plugin1 : plugins) {
    1299                             Plugin plugin = (Plugin) plugin1;
    1300                             //don't stop ZeroConfPlugin if it is registered
    1301                             if (!plugin.getName().toLowerCase(Locale.ENGLISH).contains("zeroconf")) {
    1302                                 pluginRegistry.stopPlugin(plugin.getName());
    1303                             }
    1304                         }
    1305                         URL configURL = null;
    1306
    1307                         if (receiverConfigurationPanel.getModel().isNetworkReceiverMode()) {
    1308                             int port = receiverConfigurationPanel.getModel().getNetworkReceiverPort();
    1309
    1310                             try {
    1311                                 Class<? extends Receiver> receiverClass = receiverConfigurationPanel.getModel().getNetworkReceiverClass();
    1312                                 Receiver networkReceiver = receiverClass.newInstance();
    1313                                 networkReceiver.setName(receiverClass.getSimpleName() + "-" + port);
    1314
    1315                                 Method portMethod =
    1316                                     networkReceiver.getClass().getMethod(
    1317                                         "setPort", int.class);
    1318                                 portMethod.invoke(
    1319                                     networkReceiver, port);
    1320
    1321                                 networkReceiver.setThreshold(Level.TRACE);
    1322
    1323                                 pluginRegistry.addPlugin(networkReceiver);
    1324                                 networkReceiver.activateOptions();
    1325                                 receiversPanel.updateReceiverTreeInDispatchThread();
    1326                             } catch (Exception e3) {
    1327                                 MessageCenter.getInstance().getLogger().error(
    1328                                     "Error creating Receiver", e3);
    1329                                 MessageCenter.getInstance().getLogger().info(
    1330                                     "An error occurred creating your Receiver");
    1331                             }
    1332                         } else if (receiverConfigurationPanel.getModel().isLog4jConfig()) {
    1333                             File log4jConfigFile = receiverConfigurationPanel.getModel().getLog4jConfigFile();
    1334                             if (log4jConfigFile != null) {
    1335                                 try {
    1336                                     Map<String, Map<String, String>> entries = LogFilePatternLayoutBuilder.getAppenderConfiguration(log4jConfigFile);
    1337                                     for (Object o : entries.entrySet()) {
    1338                                         try {
    1339                                             Map.Entry entry = (Map.Entry) o;
    1340                                             String name = (String) entry.getKey();
    1341                                             Map values = (Map) entry.getValue();
    1342                                             //values: conversion, file
    1343                                             String conversionPattern = values.get("conversion").toString();
    1344                                             File file = new File(values.get("file").toString());
    1345                                             URL fileURL = file.toURI().toURL();
    1346                                             String timestampFormat = LogFilePatternLayoutBuilder.getTimeStampFormat(conversionPattern);
    1347                                             String receiverPattern = LogFilePatternLayoutBuilder.getLogFormatFromPatternLayout(conversionPattern);
    1348                                             VFSLogFilePatternReceiver fileReceiver = new VFSLogFilePatternReceiver();
    1349                                             fileReceiver.setName(name);
    1350                                             fileReceiver.setAutoReconnect(true);
    1351                                             fileReceiver.setContainer(LogUI.this);
    1352                                             fileReceiver.setAppendNonMatches(true);
    1353                                             fileReceiver.setFileURL(fileURL.toURI().toString());
    1354                                             fileReceiver.setTailing(true);
    1355                                             fileReceiver.setLogFormat(receiverPattern);
    1356                                             fileReceiver.setTimestampFormat(timestampFormat);
    1357                                             fileReceiver.setThreshold(Level.TRACE);
    1358                                             pluginRegistry.addPlugin(fileReceiver);
    1359                                             fileReceiver.activateOptions();
    1360                                             receiversPanel.updateReceiverTreeInDispatchThread();
    1361                                         } catch (URISyntaxException e1) {
    1362                                             e1.printStackTrace();
    1363                                         }
    1364                                     }
    1365                                 } catch (IOException e1) {
    1366                                     e1.printStackTrace();
    1367                                 }
    1368                             }
    1369                         } else if (receiverConfigurationPanel.getModel().isLoadConfig()) {
    1370                             configURL = receiverConfigurationPanel.getModel().getConfigToLoad();
    1371                         } else if (receiverConfigurationPanel.getModel().isLogFileReceiverConfig()) {
    1372                             try {
    1373                                 URL fileURL = receiverConfigurationPanel.getModel().getLogFileURL();
    1374                                 if (fileURL != null) {
    1375                                     VFSLogFilePatternReceiver fileReceiver = new VFSLogFilePatternReceiver();
    1376                                     fileReceiver.setName(fileURL.getFile());
    1377                                     fileReceiver.setAutoReconnect(true);
    1378                                     fileReceiver.setContainer(LogUI.this);
    1379                                     fileReceiver.setAppendNonMatches(true);
    1380                                     fileReceiver.setFileURL(fileURL.toURI().toString());
    1381                                     fileReceiver.setTailing(true);
    1382                                     if (receiverConfigurationPanel.getModel().isPatternLayoutLogFormat()) {
    1383                                         fileReceiver.setLogFormat(LogFilePatternLayoutBuilder.getLogFormatFromPatternLayout(receiverConfigurationPanel.getModel().getLogFormat()));
    1384                                     } else {
    1385                                         fileReceiver.setLogFormat(receiverConfigurationPanel.getModel().getLogFormat());
    1386                                     }
    1387                                     fileReceiver.setTimestampFormat(receiverConfigurationPanel.getModel().getLogFormatTimestampFormat());
    1388                                     fileReceiver.setThreshold(Level.TRACE);
    1389
    1390                                     pluginRegistry.addPlugin(fileReceiver);
    1391                                     fileReceiver.activateOptions();
    1392                                     receiversPanel.updateReceiverTreeInDispatchThread();
    1393                                 }
    1394                             } catch (Exception e2) {
    1395                                 MessageCenter.getInstance().getLogger().error(
    1396                                     "Error creating Receiver", e2);
    1397                                 MessageCenter.getInstance().getLogger().info(
    1398                                     "An error occurred creating your Receiver");
    1399                             }
    1400                         }
    1401                         if (configURL == null && receiverConfigurationPanel.isDontWarnMeAgain()) {
    1402                             //use the saved config file as the config URL if defined
    1403                             if (receiverConfigurationPanel.getModel().getSaveConfigFile() != null) {
    1404                                 try {
    1405                                     configURL = receiverConfigurationPanel.getModel().getSaveConfigFile().toURI().toURL();
    1406                                 } catch (MalformedURLException e1) {
    1407                                     e1.printStackTrace();
    1408                                 }
    1409                             } else {
    1410                                 //no saved config defined but don't warn me is checked - use default config
    1411                                 configURL = receiverConfigurationPanel.getModel().getDefaultConfigFileURL();
    1412                             }
    1413                         }
    1414                         if (configURL != null) {
    1415                             MessageCenter.getInstance().getLogger().debug(
    1416                                 "Initialiazing Log4j with " + configURL.toExternalForm());
    1417                             final URL finalURL = configURL;
    1418                             new Thread(
    1419                                 () -> {
    1420                                     if (receiverConfigurationPanel.isDontWarnMeAgain()) {
    1421                                         applicationPreferenceModel.setConfigurationURL(finalURL.toExternalForm());
    1422                                     } else {
    1423                                         try {
    1424                                             if (new File(finalURL.toURI()).exists()) {
    1425                                                 loadConfigurationUsingPluginClassLoader(finalURL);
    1426                                             }
    1427                                         } catch (URISyntaxException e12) {
    1428                                             //ignore
    1429                                         }
    1430                                     }
    1431
    1432                                     receiversPanel.updateReceiverTreeInDispatchThread();
    1433                                 }).start();
    1434                         }
    1435                         File saveConfigFile = receiverConfigurationPanel.getModel().getSaveConfigFile();
    1436                         if (saveConfigFile != null) {
    1437                             ReceiversHelper.getInstance().saveReceiverConfiguration(saveConfigFile);
    1438                         }
    1439                     });
    1440
    1441                 receiverConfigurationPanel.setDialog(dialog);
    1442                 dialog.getContentPane().add(receiverConfigurationPanel);
    1443
    1444                 dialog.pack();
    1445
    1446                 Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    1447                 dialog.setLocation(
    1448                     (screenSize.width / 2) - (dialog.getWidth() / 2),
    1449                     (screenSize.height / 2) - (dialog.getHeight() / 2));
    1450
    1451                 dialog.setVisible(true);
    1452             });
    1453     }
    1454
    1455     /**
    1456      * Exits the application, ensuring Settings are saved.
    1457      */
    1458     public boolean exit() {
    1459         getSettingsManager().saveSettings();
    1460
    1461         return shutdown();
    1462     }
    1463
    1464     void addWelcomePanel() {
    1465         getTabbedPane().insertTab(
    1466             ChainsawTabbedPane.WELCOME_TAB, new ImageIcon(ChainsawIcons.ABOUT), welcomePanel,
    1467             "Welcome/Help", 0);
    1468         getTabbedPane().setSelectedComponent(welcomePanel);
    1469         getPanelMap().put(ChainsawTabbedPane.WELCOME_TAB, welcomePanel);
    1470     }
    1471
    1472     void removeWelcomePanel() {
    1473         EventQueue.invokeLater(() -> {
    1474             if (getTabbedPane().containsWelcomePanel()) {
    1475                 getTabbedPane().remove(
    1476                     getTabbedPane().getComponentAt(getTabbedPane().indexOfTab(ChainsawTabbedPane.WELCOME_TAB)));
    1477             }
    1478         });
    1479     }
    1480
    1481     ChainsawStatusBar getStatusBar() {
    1482         return statusBar;
    1483     }
    1484
    1485     public void showApplicationPreferences() {
    1486         applicationPreferenceModelPanel.updateModel();
    1487         preferencesFrame.setVisible(true);
    1488     }
    1489
    1490     public void showReceiverConfiguration() {
    1491         showReceiverConfigurationPanel();
    1492     }
    1493
    1494     public void showAboutBox() {
    1495         if (aboutBox == null) {
    1496             aboutBox = new ChainsawAbout(this);
    1497         }
    1498
    1499         aboutBox.setVisible(true);
    1500     }
    1501
    1502     Map getPanels() {
    1503         Map m = new HashMap();
    1504         Set<Map.Entry<String, Component>> panelSet = getPanelMap().entrySet();
    1505
    1506         for (Object aPanelSet : panelSet) {
    1507             Map.Entry entry = (Map.Entry) aPanelSet;
    1508             Object o = entry.getValue();
    1509             boolean valueToSend;
    1510             valueToSend = !(o instanceof LogPanel) || ((DockablePanel) entry.getValue()).isDocked();
    1511             m.put(entry.getKey(), valueToSend);
    1512         }
    1513
    1514         return m;
    1515     }
    1516
    1517     void displayPanel(String panelName, boolean display) {
    1518         Component p = getPanelMap().get(panelName);
    1519
    1520         int index = getTabbedPane().indexOfTab(panelName);
    1521
    1522         if ((index == -1) && display) {
    1523             getTabbedPane().addTab(panelName, p);
    1524         }
    1525
    1526         if ((index > -1) && !display) {
    1527             getTabbedPane().removeTabAt(index);
    1528         }
    1529     }
    1530
    1531
    1532     /**
    1533      * Shutsdown by ensuring the Appender gets a chance to close.
    1534      */
    1535     public boolean shutdown() {
    1536         if (getApplicationPreferenceModel().isConfirmExit()) {
    1537             if (
    1538                 JOptionPane.showConfirmDialog(
    1539                     LogUI.this, "Are you sure you want to exit Chainsaw?",
    1540                     "Confirm Exit", JOptionPane.YES_NO_OPTION,
    1541                     JOptionPane.INFORMATION_MESSAGE) != JOptionPane.YES_OPTION) {
    1542                 return false;
    1543             }
    1544
    1545         }
    1546
    1547         final JWindow progressWindow = new JWindow();
    1548         final ProgressPanel panel = new ProgressPanel(1, 3, "Shutting down");
    1549         progressWindow.getContentPane().add(panel);
    1550         progressWindow.pack();
    1551
    1552         Point p = new Point(getLocation());
    1553         p.move((int) getSize().getWidth() >> 1, (int) getSize().getHeight() >> 1);
    1554         progressWindow.setLocation(p);
    1555         progressWindow.setVisible(true);
    1556
    1557         Runnable runnable =
    1558             () -> {
    1559                 try {
    1560                     int progress = 1;
    1561                     final int delay = 25;
    1562
    1563                     handler.close();
    1564                     panel.setProgress(progress++);
    1565
    1566                     Thread.sleep(delay);
    1567
    1568                     pluginRegistry.stopAllPlugins();
    1569                     panel.setProgress(progress++);
    1570
    1571                     Thread.sleep(delay);
    1572
    1573                     panel.setProgress(progress++);
    1574                     Thread.sleep(delay);
    1575                 } catch (Exception e) {
    1576                     e.printStackTrace();
    1577                 }
    1578
    1579                 fireShutdownEvent();
    1580                 performShutdownAction();
    1581                 progressWindow.setVisible(false);
    1582             };
    1583
    1584         if (OSXIntegration.IS_OSX) {
    1585             /**
    1586              * or OSX we do it in the current thread because otherwise returning
    1587              * will exit the process before it's had a chance to save things
    1588              *
    1589              */
    1590             runnable.run();
    1591         } else {
    1592             new Thread(runnable).start();
    1593         }
    1594         return true;
    1595     }
    1596
    1597     /**
    1598      * Ensures all the registered ShutdownListeners are notified.
    1599      */
    1600     private void fireShutdownEvent() {
    1601         ShutdownListener[] listeners =
    1602             shutdownListenerList.getListeners(
    1603                 ShutdownListener.class);
    1604
    1605         for (ShutdownListener listener : listeners) {
    1606             listener.shuttingDown();
    1607         }
    1608     }
    1609
    1610     /**
    1611      * Configures LogUI's with an action to execute when the user requests to
    1612      * exit the application, the default action is to exit the VM. This Action is
    1613      * called AFTER all the ShutdownListeners have been notified
    1614      *
    1615      * @param shutdownAction
    1616      */
    1617     public final void setShutdownAction(Action shutdownAction) {
    1618         this.shutdownAction = shutdownAction;
    1619     }
    1620
    1621     /**
    1622      * Using the current thread, calls the registed Shutdown action's
    1623      * actionPerformed(...) method.
    1624      */
    1625     private void performShutdownAction() {
    1626         MessageCenter.getInstance().getLogger().debug(
    1627             "Calling the shutdown Action. Goodbye!");
    1628
    1629         shutdownAction.actionPerformed(
    1630             new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Shutting Down"));
    1631     }
    1632
    1633     /**
    1634      * Returns the currently selected LogPanel, if there is one, otherwise null
    1635      *
    1636      * @return current log panel
    1637      */
    1638     LogPanel getCurrentLogPanel() {
    1639         Component selectedTab = getTabbedPane().getSelectedComponent();
    1640
    1641         if (selectedTab instanceof LogPanel) {
    1642             return (LogPanel) selectedTab;
    1643         }
    1644
    1645         return null;
    1646     }
    1647
    1648     /**
    1649      * @param visible
    1650      */
    1651     private void setStatusBarVisible(final boolean visible) {
    1652         MessageCenter.getInstance().getLogger().debug(
    1653             "Setting StatusBar to " + visible);
    1654         SwingUtilities.invokeLater(
    1655             () -> statusBar.setVisible(visible));
    1656     }
    1657
    1658     boolean isStatusBarVisible() {
    1659         return statusBar.isVisible();
    1660     }
    1661
    1662     /**
    1663      * DOCUMENT ME!
    1664      *
    1665      * @return DOCUMENT ME!
    1666      */
    1667     public String getActiveTabName() {
    1668         int index = getTabbedPane().getSelectedIndex();
    1669
    1670         if (index == -1) {
    1671             return null;
    1672         } else {
    1673             return getTabbedPane().getTitleAt(index);
    1674         }
    1675     }
    1676
    1677     /**
    1678      * Causes the Welcome Panel to become visible, and shows the URL specified as
    1679      * it's contents
    1680      *
    1681      * @param url for content to show
    1682      */
    1683     public void showHelp(URL url) {
    1684         ensureWelcomePanelVisible();
    1685         //    TODO ensure the Welcome Panel is the selected tab
    1686         getWelcomePanel().setURL(url);
    1687     }
    1688
    1689     /**
    1690      * DOCUMENT ME!
    1691      *
    1692      * @return welcome panel
    1693      */
    1694     private WelcomePanel getWelcomePanel() {
    1695         return welcomePanel;
    1696     }
    1697
    1698     /**
    1699      * DOCUMENT ME!
    1700      *
    1701      * @return log tree panel visible flag
    1702      */
    1703     public boolean isLogTreePanelVisible() {
    1704         return getCurrentLogPanel() != null && getCurrentLogPanel().isLogTreeVisible();
    1705
    1706     }
    1707
    1708     /**
    1709      * DOCUMENT ME!
    1710      *
    1711      * @return DOCUMENT ME!
    1712      */
    1713     public Map<String, Component> getPanelMap() {
    1714         return panelMap;
    1715     }
    1716
    1717     //  public Map getLevelMap() {
    1718     //    return levelMap;
    1719     //  }
    1720
    1721     /**
    1722      * DOCUMENT ME!
    1723      *
    1724      * @return DOCUMENT ME!
    1725      */
    1726     public SettingsManager getSettingsManager() {
    1727         return sm;
    1728     }
    1729
    1730     /**
    1731      * DOCUMENT ME!
    1732      *
    1733      * @return DOCUMENT ME!
    1734      */
    1735     public List<String> getFilterableColumns() {
    1736         return filterableColumns;
    1737     }
    1738
    1739     /**
    1740      * DOCUMENT ME!
    1741      *
    1742      * @param tbms DOCUMENT ME!
    1743      */
    1744     public void setToolBarAndMenus(ChainsawToolBarAndMenus tbms) {
    1745         this.tbms = tbms;
    1746     }
    1747
    1748     /**
    1749      * DOCUMENT ME!
    1750      *
    1751      * @return DOCUMENT ME!
    1752      */
    1753     public ChainsawToolBarAndMenus getToolBarAndMenus() {
    1754         return tbms;
    1755     }
    1756
    1757     /**
    1758      * DOCUMENT ME!
    1759      *
    1760      * @return DOCUMENT ME!
    1761      */
    1762     public Map getTableMap() {
    1763         return tableMap;
    1764     }
    1765
    1766     /**
    1767      * DOCUMENT ME!
    1768      *
    1769      * @return DOCUMENT ME!
    1770      */
    1771     public Map getTableModelMap() {
    1772         return tableModelMap;
    1773     }
    1774
    1775     /**
    1776      * DOCUMENT ME!
    1777      *
    1778      * @param tabbedPane DOCUMENT ME!
    1779      */
    1780     public void setTabbedPane(ChainsawTabbedPane tabbedPane) {
    1781         this.tabbedPane = tabbedPane;
    1782     }
    1783
    1784     /**
    1785      * DOCUMENT ME!
    1786      *
    1787      * @return DOCUMENT ME!
    1788      */
    1789     public ChainsawTabbedPane getTabbedPane() {
    1790         return tabbedPane;
    1791     }
    1792
    1793     /**
    1794      * @return Returns the applicationPreferenceModel.
    1795      */
    1796     public final ApplicationPreferenceModel getApplicationPreferenceModel() {
    1797         return applicationPreferenceModel;
    1798     }
    1799
    1800     /**
    1801      * DOCUMENT ME!
    1802      */
    1803     public void setupTutorial() {
    1804         SwingUtilities.invokeLater(
    1805             () -> {
    1806                 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
    1807                 setLocation(0, getLocation().y);
    1808
    1809                 double chainsawwidth = 0.7;
    1810                 double tutorialwidth = 1 - chainsawwidth;
    1811                 setSize((int) (screen.width * chainsawwidth), getSize().height);
    1812                 invalidate();
    1813                 validate();
    1814
    1815                 Dimension size = getSize();
    1816                 Point loc = getLocation();
    1817                 tutorialFrame.setSize(
    1818                     (int) (screen.width * tutorialwidth), size.height);
    1819                 tutorialFrame.setLocation(loc.x + size.width, loc.y);
    1820                 tutorialFrame.setVisible(true);
    1821             });
    1822     }
    1823
    1824     private void buildLogPanel(
    1825         boolean customExpression, final String ident, final List<LoggingEvent> events)
    1826         throws IllegalArgumentException {
    1827         final LogPanel thisPanel = new LogPanel(getStatusBar(), ident, cyclicBufferSize, allColorizers, applicationPreferenceModel);
    1828
    1829         getSettingsManager().addSettingsListener(thisPanel);
    1830         getSettingsManager().configure(thisPanel);
    1831
    1832
    1833         /**
    1834          * Now add the panel as a batch listener so it can handle it's own
    1835          * batchs
    1836          */
    1837         if (customExpression) {
    1838             handler.addCustomEventBatchListener(ident, thisPanel);
    1839         } else {
    1840             identifierPanels.add(thisPanel);
    1841             handler.addEventBatchListener(thisPanel);
    1842         }
    1843
    1844         TabIconHandler iconHandler = new TabIconHandler(ident);
    1845         thisPanel.addEventCountListener(iconHandler);
    1846
    1847
    1848         tabbedPane.addChangeListener(iconHandler);
    1849
    1850         PropertyChangeListener toolbarMenuUpdateListener =
    1851             evt -> tbms.stateChange();
    1852
    1853         thisPanel.addPropertyChangeListener(toolbarMenuUpdateListener);
    1854         thisPanel.addPreferencePropertyChangeListener(toolbarMenuUpdateListener);
    1855
    1856         thisPanel.addPropertyChangeListener(
    1857             "docked",
    1858             evt -> {
    1859                 LogPanel logPanel = (LogPanel) evt.getSource();
    1860
    1861                 if (logPanel.isDocked()) {
    1862                     getPanelMap().put(logPanel.getIdentifier(), logPanel);
    1863                     getTabbedPane().addANewTab(
    1864                         logPanel.getIdentifier(), logPanel, null);
    1865                     getTabbedPane().setSelectedTab(getTabbedPane().indexOfTab(logPanel.getIdentifier()));
    1866                 } else {
    1867                     getTabbedPane().remove(logPanel);
    1868                 }
    1869             });
    1870
    1871         logger.debug("adding logpanel to tabbed pane: " + ident);
    1872
    1873         //NOTE: tab addition is a very fragile process - if you modify this code,
    1874         //verify the frames in the individual log panels initialize to their
    1875         //correct sizes
    1876         getTabbedPane().add(ident, thisPanel);
    1877         getPanelMap().put(ident, thisPanel);
    1878
    1879         /**
    1880          * Let the new LogPanel receive this batch
    1881          */
    1882
    1883         SwingUtilities.invokeLater(
    1884             () -> {
    1885                 getTabbedPane().addANewTab(
    1886                     ident, thisPanel, new ImageIcon(ChainsawIcons.ANIM_RADIO_TOWER));
    1887                 thisPanel.layoutComponents();
    1888                 thisPanel.receiveEventBatch(ident, events);
    1889                 if (!getTabbedPane().tabSetting.isChainsawLog()) {
    1890                     displayPanel("chainsaw-log", false);
    1891                 }
    1892             });
    1893
    1894         String msg = "added tab " + ident;
    1895         MessageCenter.getInstance().getLogger().debug(msg);
    1896     }
    1897
    1898
    1899     public void createCustomExpressionLogPanel(String ident) {
    1900         //collect events matching the rule from all of the tabs
    1901         try {
    1902             List<LoggingEvent> list = new ArrayList<>();
    1903             Rule rule = ExpressionRule.getRule(ident);
    1904
    1905             for (Object identifierPanel : identifierPanels) {
    1906                 LogPanel panel = (LogPanel) identifierPanel;
    1907
    1908                 for (Object o : panel.getMatchingEvents(rule)) {
    1909                     LoggingEventWrapper e = (LoggingEventWrapper) o;
    1910                     list.add(e.getLoggingEvent());
    1911                 }
    1912             }
    1913
    1914             buildLogPanel(true, ident, list);
    1915         } catch (IllegalArgumentException iae) {
    1916             MessageCenter.getInstance().getLogger().info(
    1917                 "Unable to add tab using expression: " + ident + ", reason: "
    1918                     + iae.getMessage());
    1919         }
    1920     }
    1921
    1922     /**
    1923      * Loads the log4j configuration file specified by the url, using
    1924      * the PluginClassLoader instance as a TCCL, but only replacing it temporarily, with the original
    1925      * TCCL being restored in a finally block to ensure consitency.
    1926      *
    1927      * @param url
    1928      */
    1929     private void loadConfigurationUsingPluginClassLoader(final URL url) {
    1930         ClassLoader classLoader = PluginClassLoaderFactory.getInstance().getClassLoader();
    1931         ClassLoader previousTCCL = Thread.currentThread().getContextClassLoader();
    1932
    1933         if (url != null) {
    1934             try {
    1935                 // we temporarily swap the TCCL so that plugins can find resources
    1936                 Thread.currentThread().setContextClassLoader(classLoader);
    1937                 try {
    1938                     DOMConfigurator.configure(url);
    1939                 } catch (Exception e) {
    1940                     logger.warn("Unable to load configuration URL: " + url, e);
    1941                 }
    1942             } finally {
    1943                 // now switch it back...
    1944                 Thread.currentThread().setContextClassLoader(previousTCCL);
    1945             }
    1946         }
    1947         ensureChainsawAppenderHandlerAdded();
    1948     }
    1949
    1950     private static void loadLookAndFeelUsingPluginClassLoader(String lookAndFeelClassName) {
    1951         ClassLoader classLoader = PluginClassLoaderFactory.getInstance().getClassLoader();
    1952         ClassLoader previousTCCL = Thread.currentThread().getContextClassLoader();
    1953         try {
    1954             // we temporarily swap the TCCL so that plugins can find resources
    1955             Thread.currentThread().setContextClassLoader(classLoader);
    1956             UIManager.setLookAndFeel(lookAndFeelClassName);
    1957             UIManager.getLookAndFeelDefaults().put("ClassLoader", classLoader);
    1958         } catch (Exception e) {
    1959             e.printStackTrace();
    1960         } finally {
    1961             // now switch it back...
    1962             Thread.currentThread().setContextClassLoader(previousTCCL);
    1963         }
    1964     }
    1965
    1966     /**
    1967      * Makes sure that the LoggerRepository has the ChainsawAppenderHandler
    1968      * added to the root logger so Chainsaw can receive all the events.
    1969      */
    1970     private void ensureChainsawAppenderHandlerAdded() {
    1971         if (!LogManager.getLoggerRepository().getRootLogger().isAttached(handler)) {
    1972             LogManager.getLoggerRepository().getRootLogger().addAppender(handler);
    1973         }
    1974     }
    1975
    1976     /**
    1977      * This class handles the recption of the Event batches and creates new
    1978      * LogPanels if the identifier is not in use otherwise it ignores the event
    1979      * batch.
    1980      *
    1981      * @author Paul Smith
    1982      * &lt;psmith@apache.org&gt;
    1983      */
    1984     private class NewTabEventBatchReceiver implements EventBatchListener {
    1985         /**
    1986          * DOCUMENT ME!
    1987          *
    1988          * @param ident
    1989          * @param events
    1990          */
    1991         public void receiveEventBatch(
    1992             final String ident, final List<LoggingEvent> events) {
    1993             if (events.size() == 0) {
    1994                 return;
    1995             }
    1996
    1997             if (!isGUIFullyInitialized) {
    1998                 synchronized (initializationLock) {
    1999                     while (!isGUIFullyInitialized) {
    2000                         System.out.println(
    2001                             "Wanting to add a row, but GUI not initialized, waiting...");
    2002
    2003                         /**
    2004                          * Lets wait 1 seconds and recheck.
    2005                          */
    2006                         try {
    2007                             initializationLock.wait(1000);
    2008                             logger.debug("waiting for initialization to complete");
    2009                         } catch (InterruptedException e) {
    2010                         }
    2011                     }
    2012                     logger.debug("out of system initialization wait loop");
    2013                 }
    2014             }
    2015
    2016             if (!getPanelMap().containsKey(ident)) {
    2017                 logger.debug("panel " + ident + " does not exist - creating");
    2018                 try {
    2019                     buildLogPanel(false, ident, events);
    2020                 } catch (IllegalArgumentException iae) {
    2021                     logger.error("error creating log panel", iae);
    2022                     //should not happen - not a custom expression panel
    2023                 }
    2024             }
    2025         }
    2026
    2027         /*
    2028          * (non-Javadoc)
    2029          *
    2030          * @see org.apache.log4j.chainsaw.EventBatchListener#getInterestedIdentifier()
    2031          */
    2032
    2033         /**
    2034          * DOCUMENT ME!
    2035          *
    2036          * @return DOCUMENT ME!
    2037          */
    2038         public String getInterestedIdentifier() {
    2039             // we are interested in all batches so we can detect new identifiers
    2040             return null;
    2041         }
    2042     }
    2043
    2044     private class TabIconHandler implements EventCountListener, ChangeListener {
    2045         //the tabIconHandler is associated with a new tab, and a new tab always
    2046         //shows the 'new events' icon
    2047         private boolean newEvents = true;
    2048         private boolean seenEvents = false;
    2049         private final String ident;
    2050         ImageIcon NEW_EVENTS = new ImageIcon(ChainsawIcons.ANIM_RADIO_TOWER);
    2051         ImageIcon HAS_EVENTS = new ImageIcon(ChainsawIcons.INFO);
    2052         Icon SELECTED = LineIconFactory.createBlankIcon();
    2053
    2054         public TabIconHandler(String identifier) {
    2055             ident = identifier;
    2056
    2057             new Thread(
    2058                 () -> {
    2059                     while (true) {
    2060                         //if this tab is active, remove the icon
    2061                         //don't process undocked tabs
    2062                         if (getTabbedPane().indexOfTab(ident) > -1 &&
    2063                             getTabbedPane().getSelectedIndex() == getTabbedPane()
    2064                                 .indexOfTab(ident)) {
    2065                             getTabbedPane().setIconAt(
    2066                                 getTabbedPane().indexOfTab(ident), SELECTED);
    2067                             newEvents = false;
    2068                             seenEvents = true;
    2069                         } else if (getTabbedPane().indexOfTab(ident) > -1) {
    2070                             if (newEvents) {
    2071                                 getTabbedPane().setIconAt(
    2072                                     getTabbedPane().indexOfTab(ident), NEW_EVENTS);
    2073                                 newEvents = false;
    2074                                 seenEvents = false;
    2075                             } else if (!seenEvents) {
    2076                                 getTabbedPane().setIconAt(
    2077                                     getTabbedPane().indexOfTab(ident), HAS_EVENTS);
    2078                             }
    2079                         }
    2080
    2081                         try {
    2082                             Thread.sleep(handler.getQueueInterval() + 1000);
    2083                         } catch (InterruptedException ie) {
    2084                         }
    2085                     }
    2086                 }).start();
    2087         }
    2088
    2089         /**
    2090          * DOCUMENT ME!
    2091          *
    2092          * @param currentCount DOCUMENT ME!
    2093          * @param totalCount   DOCUMENT ME!
    2094          */
    2095         public void eventCountChanged(int currentCount, int totalCount) {
    2096             newEvents = true;
    2097         }
    2098
    2099         public void stateChanged(ChangeEvent event) {
    2100             if (
    2101                 getTabbedPane().indexOfTab(ident) > -1 && getTabbedPane().indexOfTab(ident) == getTabbedPane().getSelectedIndex()) {
    2102                 getTabbedPane().setIconAt(getTabbedPane().indexOfTab(ident), SELECTED);
    2103             }
    2104         }
    2105     }
    2106 }