view analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/fx/MainWindowController.java @ 279:898008f06beb

Bug 3766: HeapStats Analyzer plugin does not work Reviewed-by: ykubota https://github.com/HeapStats/heapstats/pull/152
author Yasumasa Suenaga <yasuenag@gmail.com>
date Mon, 11 Nov 2019 18:08:06 +0900
parents dd85c1cbc8c8
children
line wrap: on
line source

/*
 * Copyright (C) 2014-2019 Yasumasa Suenaga
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package jp.co.ntt.oss.heapstats.fx;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javafx.application.HostServices;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextInputDialog;
import javafx.scene.image.Image;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.stage.Modality;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.Window;
import jp.co.ntt.oss.heapstats.api.WindowController;
import jp.co.ntt.oss.heapstats.lambda.FunctionWrapper;
import jp.co.ntt.oss.heapstats.api.plugin.PluginController;
import jp.co.ntt.oss.heapstats.fx.plugin.builtin.jvmlive.JVMLiveController;
import jp.co.ntt.oss.heapstats.fx.plugin.builtin.log.LogController;
import jp.co.ntt.oss.heapstats.fx.plugin.builtin.snapshot.SnapShotController;
import jp.co.ntt.oss.heapstats.fx.plugin.builtin.threadrecorder.ThreadRecorderController;
import jp.co.ntt.oss.heapstats.api.utils.HeapStatsUtils;

/**
 * Main window controller.
 *
 * @author Yasumasa Suenaga
 */
public class MainWindowController implements Initializable, WindowController {

    private Map<String, PluginController> pluginList;

    private Region veil;

    private ProgressIndicator progress;

    private ClassLoader pluginClassLoader;

    private Window owner;

    @FXML
    private StackPane stackPane;

    @FXML
    private TabPane tabPane;

    private AboutDialogController aboutDialogController;

    private Scene aboutDialogScene;

    private static MainWindowController thisController;
    
    private HostServices hostServices;

    @FXML
    private void onExitClick(ActionEvent event) {
        Platform.exit();
    }

    @FXML
    private void onRankLevelClick(ActionEvent event){
        TextInputDialog dialog = new TextInputDialog(Integer.toString(HeapStatsUtils.getRankLevel()));
        
        try(InputStream icon = getClass().getResourceAsStream("heapstats-icon.png")){
            Stage dialogStage = (Stage)dialog.getDialogPane().getScene().getWindow();
            dialogStage.getIcons().add(new Image(icon));
        }
        catch(IOException e){
            HeapStatsUtils.showExceptionDialog(e);
        }
        
        dialog.setTitle("Rank Level setting");
        dialog.setHeaderText("Rank Level setting");
        ResourceBundle resource = ResourceBundle.getBundle("HeapStatsResources", new Locale(HeapStatsUtils.getLanguage()));
        dialog.setContentText(resource.getString("rank.label"));
        dialog.showAndWait()
              .ifPresent(v -> HeapStatsUtils.setRankLevel(Integer.parseInt(v)));
    }
    
    @FXML
    private void onHowToClick(ActionEvent event) {
        hostServices.showDocument("http://icedtea.classpath.org/wiki/HeapStats/Analyzer-version2");
    }

    @FXML
    private void onAboutMenuClick(ActionEvent event){
        Stage dialog = new Stage(StageStyle.UTILITY);
        aboutDialogController.setStage(dialog);

        dialog.setScene(aboutDialogScene);
        dialog.initModality(Modality.APPLICATION_MODAL);
        dialog.setResizable(false);
        dialog.setTitle("About HeapStats Analyzer");
        dialog.showAndWait();
    }

    private void addPlugin(String packageName){
        String lastPackageName = packageName.substring(packageName.lastIndexOf('.') + 1);
        packageName = packageName.replace('.', '/');
        String fxmlName = packageName + "/" + lastPackageName + ".fxml";
        FXMLLoader loader;

        try{
            ResourceBundle pluginResource = ResourceBundle.getBundle(lastPackageName + "Resources", new Locale(HeapStatsUtils.getLanguage()), pluginClassLoader);
            loader = new FXMLLoader(pluginClassLoader.getResource(fxmlName), pluginResource);
        }
        catch(MissingResourceException e){
            loader = new FXMLLoader(pluginClassLoader.getResource(fxmlName));
        }

        Parent root;

        try {
            root = loader.load();
        }
        catch (IOException ex) {
            HeapStatsUtils.showExceptionDialog(ex);
            return;
        }

        PluginController controller = (PluginController)loader.getController();
        controller.setVeil(veil);
        controller.setProgress(progress);

        Tab tab = new Tab();
        tab.setText(controller.getPluginName());
        tab.setContent(root);
        tab.setOnSelectionChanged(controller.getOnPluginTabSelected());

        tabPane.getTabs().add(tab);

        pluginList.put(controller.getPluginName(), controller);
    }

    public static MainWindowController getInstance(){
        return thisController;
    }

    @FXML
    private void onGCAllClick(ActionEvent event) {
        SnapShotController snapShotController = (SnapShotController)getPluginController("SnapShot Data");
        snapShotController.dumpGCStatisticsToCSV(false);
    }

    @FXML
    private void onGCSelectedClick(ActionEvent event) {
        SnapShotController snapShotController = (SnapShotController)getPluginController("SnapShot Data");
        snapShotController.dumpGCStatisticsToCSV(true);
    }

    @FXML
    private void onHeapAllClick(ActionEvent event) {
        SnapShotController snapShotController = (SnapShotController)getPluginController("SnapShot Data");
        snapShotController.dumpClassHistogramToCSV(false);
    }

    @FXML
    private void onHeapSelectedClick(ActionEvent event) {
        SnapShotController snapShotController = (SnapShotController)getPluginController("SnapShot Data");
        snapShotController.dumpClassHistogramToCSV(true);
    }

    @FXML
    private void onSnapShotOpenClick(ActionEvent event) {
        Tab snapShotTab = tabPane.getTabs().stream()
                                 .filter(t -> t.getText().equals("SnapShot Data"))
                                 .findAny()
                                 .orElseThrow(() -> new IllegalStateException("SnapShot plugin must be loaded."));
        tabPane.getSelectionModel().select(snapShotTab);
        SnapShotController snapShotController = (SnapShotController)getPluginController("SnapShot Data");

        snapShotController.onSnapshotFileClick(event);
    }

    @FXML
    private void onLogOpenClick(ActionEvent event) {
        Tab snapShotTab = tabPane.getTabs().stream()
                                 .filter(t -> t.getText().equals("Log Data"))
                                 .findAny()
                                 .orElseThrow(() -> new IllegalStateException("Log plugin must be loaded."));
        tabPane.getSelectionModel().select(snapShotTab);
        LogController logController = (LogController)getPluginController("Log Data");

        logController.onLogFileClick(event);
    }

    @FXML
    private void onThreadRecorderOpenClick(ActionEvent event) {
        Tab threadRecorderTab = tabPane.getTabs().stream()
                                       .filter(t -> t.getText().equals("Thread Recorder"))
                                       .findAny()
                                       .orElseThrow(() -> new IllegalStateException("Thread Recorder plugin must be loaded."));
        tabPane.getSelectionModel().select(threadRecorderTab);
        ThreadRecorderController threadRecorderController = (ThreadRecorderController)getPluginController("Thread Recorder");

        threadRecorderController.onOpenBtnClick(event);
    }

    private void initializeAboutDialog(){
        FXMLLoader loader = new FXMLLoader(getClass().getResource("/jp/co/ntt/oss/heapstats/fx/aboutDialog.fxml"), HeapStatsUtils.getResourceBundle());

        try {
            loader.load();
            aboutDialogController = (AboutDialogController)loader.getController();
            aboutDialogScene = new Scene(loader.getRoot());
        }
        catch (IOException ex) {
            HeapStatsUtils.showExceptionDialog(ex);
        }

    }

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        thisController = this;
        pluginList = new ConcurrentHashMap<>();
        veil = new Region();
        veil.setStyle("-fx-background-color: rgba(0, 0, 0, 0.2)");
        veil.setVisible(false);

        progress = new ProgressIndicator();
        progress.setMaxSize(200.0d, 200.0d);
        progress.setVisible(false);

        stackPane.getChildren().add(veil);
        stackPane.getChildren().add(progress);

        initializeAboutDialog();
    }
    
    /**
     * Set HostServices to open HowTo page.
     * @param hostServices HostServices
     */
    protected void setHostServices(HostServices hostServices) {
        this.hostServices = hostServices;
    }

    private ClassLoader createPluginClassLoader(){
        URL[] jarURLList = null;

        try(DirectoryStream<Path> jarPaths = Files.newDirectoryStream(HeapStatsUtils.getHeapStatsHomeDirectory().resolve("plugins"), "*.jar")){
            jarURLList = StreamSupport.stream(jarPaths.spliterator(), false)
                                      .map(new FunctionWrapper<>(p -> p.toUri().toURL()))
                                      .collect(Collectors.toList())
                                      .toArray(new URL[0]);
        }
        catch(IOException ex) {
            Logger.getLogger(MainWindowController.class.getName()).log(Level.SEVERE, null, ex);
        }

        var parentLoader = this.getClass().getClassLoader();
        return (jarURLList == null) ? parentLoader : new URLClassLoader("HeapStats Analyzer Plugin ClassLoader", jarURLList, parentLoader);
    }

    /**
     * Load plugins which is defined in heapstats.properties.
     */
    public void loadPlugin(){
        pluginClassLoader = createPluginClassLoader();
        FXMLLoader.setDefaultClassLoader(pluginClassLoader);

        List<String> plugins =  new ArrayList<>();
        /* Add built-in plugins */
        plugins.add(LogController.class.getPackage().getName());
        plugins.add(SnapShotController.class.getPackage().getName());
        plugins.add(ThreadRecorderController.class.getPackage().getName());
        plugins.add(JVMLiveController.class.getPackage().getName());
        /* Add customized plugins by config */
        plugins.addAll(HeapStatsUtils.getPlugins());
        plugins.stream().distinct().forEach(s -> addPlugin(s));

        aboutDialogController.setPluginInfo();
    }

    /**
     * Get controller instance of plugin.
     *
     * @param pluginName Plugin name which you want.
     * @return Controller of Plugin. If it does not exist, return null.
     */
    public PluginController getPluginController(String pluginName){
        return pluginList.get(pluginName);
    }

    /**
     * Get loaded plugin list.
     *
     * @return Loaded plugin list.
     */
    public Map<String, PluginController> getPluginList() {
        return pluginList;
    }

    /**
     * Select plugin tab
     *
     * @param pluginName Name of plugin to active.
     */
    public void selectTab(String pluginName) throws IllegalArgumentException{
        Tab target = tabPane.getTabs().stream()
                            .filter(t -> t.getText().equals(pluginName))
                            .findAny()
                            .orElseThrow(() -> new IllegalArgumentException(pluginName + " is not loaded."));
        tabPane.getSelectionModel().select(target);
    }

    public Window getOwner() {
        return owner;
    }

    public void setOwner(Window owner) {
        this.owner = owner;
    }

}