brainleg.app.intellij.ui.EForm.java Source code

Java tutorial

Introduction

Here is the source code for brainleg.app.intellij.ui.EForm.java

Source

/*
 * Copyright 2011 Roman Stepanenko
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package brainleg.app.intellij.ui;

import brainleg.app.api.SearchResults;
import brainleg.app.intellij.BLProjectComponent;
import brainleg.app.util.BLExceptionUtil;
import brainleg.app.util.CssUtil;
import com.intellij.icons.AllIcons;
import com.intellij.ide.BrowserUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import brainleg.app.engine.Calculator;
import brainleg.app.engine.EData;
import brainleg.app.intellij.BLSettingsService;
import brainleg.app.util.AppWeb;
import org.apache.commons.lang.time.FastDateFormat;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.Document;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;

/**
 * Represents a tab in the BrainLeg panel, capturing data about specific exception
 */
public class EForm {
    BLProjectComponent projectComponent;
    private ExceptionTabMode mode;
    private ExceptionTabMode pendingMode; //until we have fully processed a new mode (different threads), this is where we keep what's coming
    private EData exception;
    private String exceptionHash;
    private String requestToken; //unique token given by the server for the search request (short-lived object on the server)
    private Long firstTime;
    private Long lastTime;
    public int numOccurrences;

    private ContentManager contentManager;
    private Content content;

    private JPanel rootPanel;
    private JButton manualSearchButton;
    private JButton submitSolutionButton;
    private JButton ignoreFromNowOnButton;
    private JButton settingsButton;
    private JPanel exceptionPanel;
    private JLabel detailsLabel;
    private JLabel exceptionStacktraceLabel;
    private JPanel exceptionViewContainer;
    private JPanel solutionViewContainer;
    private JPanel solutionPanel;
    private JCheckBox showExceptionCheckBox;
    private JLabel solutionsLabel;
    private JButton openSearchResultsInBrowserButton;
    private HyperlinkLabel feedbackLinkLabel;
    private JPanel leftOfLogoContainer;
    private HyperlinkLabel logoLabel;

    JEditorPane htmlPane;
    Document htmlDoc;

    public EForm(final EData exception, final ContentManager contentManager, long creationTime,
            BLProjectComponent projectComponent) {
        this.projectComponent = projectComponent;
        this.exception = exception;
        exceptionHash = Calculator.calcNoLineMD5ForTracesRecursively(exception);
        numOccurrences = 1;
        firstTime = creationTime;
        this.contentManager = contentManager;

        ignoreFromNowOnButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                System.out.println("ignoreFromNowOnButton: " + exception);
                String hash = Calculator.calcNoLineMD5ForTracesRecursively(exception);
                BLSettingsService.getSettings().ignoreFullTracesNoLinesMD5s.add(hash);
                contentManager.removeContent(content, true);
            }
        });
        ignoreFromNowOnButton.setIcon(AllIcons.Actions.Delete);

        feedbackLinkLabel.setHyperlinkTarget("mailto:support@brainleg.com?subject=BrainLeg%20Feedback");
        feedbackLinkLabel.setHyperlinkText("Ideas or suggestions? ", "Let us know", "");

        try {
            BufferedImage logoImage = ImageIO.read(
                    AppWeb.class.getResourceAsStream(BLUIUtil.getLightOrDarkResourceName("/brainleg-logo.png")));
            logoLabel.setIcon(new ImageIcon(logoImage));
            logoLabel.setHyperlinkTarget("http://brainleg.com");
            logoLabel.setUseIconAsLink(true);
        } catch (IOException e) {
            e.printStackTrace(System.out);
        }

        manualSearchButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (BLSettingsService.getSettings().forManualSearchShowResultsInIdea) {
                    //make search in another thread so as not to stall processing of ui events
                    BLProjectComponent.executor.execute(new Runnable() {
                        public void run() {
                            sendRequestToServerAndRenderInIDEA(exception, false, false); //don't show notification if invoked from here - the user is already in BL tab
                        }
                    });
                    markExceptionAsDontShowNotificationFor();
                } else {
                    AppWeb.sendQueryAndOpenBrowser(exception);
                    markExceptionAsDontShowNotificationFor();
                }
            }
        });
        manualSearchButton.setIcon(AllIcons.Actions.Find);

        openSearchResultsInBrowserButton.setIcon(AllIcons.Xml.Browsers.Chrome16);
        openSearchResultsInBrowserButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (requestToken != null) {
                    AppWeb.openSearchResultsInBrowser(requestToken);
                    markExceptionAsDontShowNotificationFor();
                } else {
                    //it is possible we got exception from cache, and so the request token is not known at this point (or the server might not have it).
                    //in this case we just have to make a new web browser search
                    AppWeb.sendQueryAndOpenBrowser(exception);
                    markExceptionAsDontShowNotificationFor();
                }

            }
        });
        //        submitSolutionButton.addActionListener(new ActionListener() {
        //            public void actionPerformed(ActionEvent e) {
        //                String userAuthToken = BLSettingsService.getSettings().userAuthToken;
        //                if (userAuthToken != null && !"".equals(userAuthToken)) {
        //                    SubmitSolutionDialog.askUser(exception, submitSolutionButton, submissionCountLabel);
        //                } else {
        //                    Messages.showMessageDialog(
        //                            "<html>In order to post solutions via plugin you have to register " +
        //                                    "at <a href=\"http://www.brainleg.com/register\">http://www.brainleg.com</a> (free and takes 30 sec) <br/>" +
        //                                    "and then copy your 'Plugin user token' from 'My BrainLeg' page into plugin settings</html>",
        //                            "Please configure your user token",
        //                            Messages.getInformationIcon()
        //                    );
        //                }
        //            }
        //        });
        settingsButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                BLSettingsDialog.configure();
            }
        });

        showExceptionCheckBox.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                if (exceptionViewContainer.isVisible()) {
                    exceptionViewContainer.setVisible(false);
                } else {
                    exceptionViewContainer.setVisible(true);
                }
            }
        });

        refreshDetailsText();
    }

    public void sendRequestToServerAndRenderInIDEA(final EData exception, final boolean showNotificationWhenDone,
            final boolean isAutomaticSearch) {
        setMode(ExceptionTabMode.WAITING_FOR_RESULTS_FROM_SERVER);

        BLProjectComponent.executor.execute(new Runnable() {
            public void run() {
                final SearchResults searchResults = AppWeb.sendQuery(exception, isAutomaticSearch);

                if (searchResults != null) {
                    requestToken = searchResults.requestToken; //remember the request token given by the server to this exception search
                }

                //        final SearchResults searchResults = new SearchResults();
                //        searchResults.html = "";
                //        searchResults.totalMatches=55;

                //all things affecting ui must be done in awt dispatch thread...
                ApplicationManager.getApplication().invokeLater(new Runnable() {
                    public void run() {
                        if (searchResults != null) {
                            addSolutionResults(searchResults);
                            if (BLProjectComponent.useCache) {
                                BLSettingsService.getSettings().addToCache(searchResults, exceptionHash);
                            }
                        } else {
                            setMode(ExceptionTabMode.SERVER_CONNECTIVITY_ERROR);
                        }

                        if (showNotificationWhenDone) {
                            String exceptionChainForNotification = BLExceptionUtil
                                    .getExceptionChainFromTheBottom(exception);

                            projectComponent.showBrainLegNotification(exceptionChainForNotification, true,
                                    searchResults, getPendingMode(), exceptionHash);
                        }
                    }
                });
            }
        });
    }

    public ExceptionTabMode getPendingMode() {
        if (pendingMode != null) {
            return pendingMode;
        }

        return mode;
    }

    private String getSolutionsLabel(SearchResults searchResults) {
        if (searchResults != null) {
            return "BrainLeg engine found: " + searchResults.totalMatches + " search results";
        }

        return "Analysis and search results from BrainLeg engine:";
    }

    public void displayConsoleUI(JComponent consoleUIComponent) {
        exceptionPanel.add(consoleUIComponent, BorderLayout.CENTER);
    }

    public void setContent(Content content) {
        this.content = content;
    }

    public void addSolutionResults(SearchResults searchResults) {
        solutionsLabel.setText(getSolutionsLabel(searchResults));

        configureHtmlPane();

        //        System.out.println("inserting html: "+searchResults.html);

        htmlPane.setText(searchResults.html);
        htmlPane.setCaretPosition(0); //scroll to the top

        //if there are more than a few results, minimize the exception trace pane
        if (searchResults.totalMatches > 5) {
            showExceptionCheckBox.setSelected(false);
        }

        setMode(ExceptionTabMode.GOT_SEARCH_RESULTS);
    }

    private void configureHtmlPane() {
        if (htmlPane == null) {
            htmlPane = new JEditorPane();

            htmlPane.setEditable(false);

            HTMLEditorKit kit = new HTMLEditorKit();
            htmlPane.setEditorKit(kit);

            JScrollPane scrollPane = new JBScrollPane(htmlPane);

            scrollPane.getVerticalScrollBar().addAdjustmentListener(new AdjustmentListener() {
                public void adjustmentValueChanged(AdjustmentEvent e) {
                    Adjustable source = e.getAdjustable();

                    // check if user is currently dragging the scrollbar's knob
                    if (e.getValueIsAdjusting()) {
                        return;
                    }

                    int orient = source.getOrientation();
                    if (orient == Adjustable.HORIZONTAL) {
                        return; //we are not interested in horizontal scroll
                    }

                    // get the type of adjustment which caused the value changed event
                    int type = e.getAdjustmentType();
                    switch (type) {
                    case AdjustmentEvent.UNIT_INCREMENT:
                        //                            System.out.println("increased by one unit");
                        break;

                    case AdjustmentEvent.UNIT_DECREMENT:
                        //                            System.out.println("decreased by one unit");
                        break;

                    case AdjustmentEvent.BLOCK_INCREMENT:
                        //                            System.out.println("increased by one block");
                        break;

                    case AdjustmentEvent.BLOCK_DECREMENT:
                        //                            System.out.println("decreased by one block");
                        break;

                    case AdjustmentEvent.TRACK:
                        //                            System.out.println("knob on the scrollbar was dragged");
                        break;

                    }

                    // get the current value in the adjustment event
                    int value = e.getValue();
                    //                    System.out.println("Current Value: " + value);
                    if (value > 200) {
                        //this is about click middle mouse flip - i.e. about 15% of vertical space (approx)
                        markExceptionAsDontShowNotificationFor();
                    }
                }
            });

            StyleSheet styleSheet = kit.getStyleSheet();
            CssUtil.addStyles(styleSheet);

            configureNewHtmlDocument();

            htmlPane.addHyperlinkListener(new HyperlinkListener() {
                public void hyperlinkUpdate(HyperlinkEvent e) {
                    if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                        try {
                            URL url = e.getURL();
                            if (url != null) {
                                BrowserUtil.launchBrowser(url.toString());
                            }
                        } catch (Throwable t) {
                            t.printStackTrace();
                        }
                    }
                }
            });
            solutionPanel.add(scrollPane);
        }
    }

    /**
     * We mark this exception after user saw search results in the UI or kicked off manual search.
     * Basically after he did either step, it means he looked at what BL has to say, so
     * don't attract any more attention again to the same thing. Let developer focus on his work.
     */
    private void markExceptionAsDontShowNotificationFor() {
        System.out.println("marking this exception as 'reviewed'");
        BLSettingsService.getSettings().dontShowNotificationsFullTracesNoLinesMD5s.add(exceptionHash);
    }

    private void configureNewHtmlDocument() {
        htmlDoc = htmlPane.getEditorKit().createDefaultDocument();
        htmlPane.setDocument(htmlDoc);
    }

    /**
     * Returns true if the pretty much same exception exists in this form and updates the counts.
     *
     * @param candidate
     * @param candidateHash
     * @param time
     * @return
     */
    public boolean checkIfSameExceptionExistsAndUpdateCounts(EData candidate, String candidateHash, long time) {
        if (exceptionHash.equals(candidateHash)) {
            numOccurrences++;
            lastTime = time;
            refreshDetailsText();
            return true;
        }
        return false;
    }

    private void refreshDetailsText() {
        FastDateFormat formatter = FastDateFormat.getInstance("HH:mm:ss");
        String desc = "num of occurrences: " + numOccurrences + "   first: " + formatter.format(firstTime);
        if (lastTime != null) {
            desc = desc + "   last: " + formatter.format(lastTime);
        }
        detailsLabel.setText(desc);
    }

    public JComponent getRootComponent() {
        return rootPanel;
    }

    public void setMode(final ExceptionTabMode newMode) {
        pendingMode = newMode;
        System.out.println("new pending mode: " + pendingMode);
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            public void run() {
                System.out.println("reacting to new mode: " + newMode);
                if (newMode == ExceptionTabMode.NO_AUTOMATIC_SEARCH_SETTING) {
                    manualSearchButton.setVisible(true); //if we don't make automatic search then this button is needed so that user can make manual search
                    openSearchResultsInBrowserButton.setVisible(false); //when there is no automatic this button does not make sense
                    setPluginHtmlMessage("Automatic exception search is disabled by you in BrainLeg settings.",
                            "Enable this setting or click on 'Check For Solution' button above to perform a manual search");
                } else if (newMode == ExceptionTabMode.NO_AUTOMATIC_SEARCH_BECAUSE_PROJECT_SPECIFIC_EXCEPTION) {
                    manualSearchButton.setVisible(true); //if we don't make automatic search then this button is needed so that user can make manual search
                    openSearchResultsInBrowserButton.setVisible(false); //when there is no automatic this button does not make sense

                    //use appropriate message that is consistent with automatic search setting
                    if (BLSettingsService.getSettings().automaticSearch) {
                        setPluginHtmlMessage(
                                "Looks like this exception is thrown by or inside the code your wrote.",
                                "By default we don't make automatic searches in such cases.",
                                "You are welcome to make a manual search by pressing 'Check For Solution' button above, and if you believe this kind of exception occurrence is not specific to your project - please let us know!");
                    } else {
                        setPluginHtmlMessage("Automatic exception search is disabled by you in BrainLeg settings.",
                                "Enable this setting or click on 'Check For Solution' button above to perform a manual search");
                    }
                } else if (newMode == ExceptionTabMode.WAITING_FOR_RESULTS_FROM_SERVER) {
                    setPluginHtmlMessage("Request sent, waiting for the response...");
                    manualSearchButton.setEnabled(false); //disable the button so that the user can't double-submit the search
                    openSearchResultsInBrowserButton.setVisible(false); //this button has meaning only when we got some results back from the server
                } else if (newMode == ExceptionTabMode.GOT_SEARCH_RESULTS) {
                    manualSearchButton.setVisible(false); //since all is well we don't need this button any more
                    openSearchResultsInBrowserButton.setVisible(true); //since all is well we can show this button to user now
                } else if (newMode == ExceptionTabMode.SERVER_CONNECTIVITY_ERROR) {
                    manualSearchButton.setEnabled(true); //let user make another manual search
                    manualSearchButton.setVisible(true); //let user make another manual search
                    setPluginHtmlMessage(
                            "Could not connect to BrainLeg server - either you are offline or BrainLeg server is experiencing problems.");
                }
                mode = newMode;
                pendingMode = null;
            }
        });
    }

    private void setPluginHtmlMessage(String... messages) {
        configureHtmlPane();
        configureNewHtmlDocument(); //we have to create new document whenever we change html, otherwise weird rendering problems

        String html = "<html><div class=\"plugin-message\">";
        for (String message : messages) {
            html = html + "<p>" + message + "</p>";
        }
        html = html + "</div></html>";

        htmlPane.setText(html);
    }
}