changeset 243:be9892e921b3

Bug 3420: Migrate Plugin API module from fx module Reviewed-by: yasuenag GitHub: https://github.com/HeapStats/heapstats/pull/109
author KUBOTA Yuji <kubota.yuji@lab.ntt.co.jp>
date Mon, 17 Jul 2017 23:44:04 +0900
parents f1096afde036
children 1e7c0fb4048a
files .idea/compiler.xml .idea/encodings.xml .idea/modules.xml .ignore ChangeLog Makefile.am Makefile.in analyzer/cli/heapstats-cli.iml analyzer/core/heapstats-core.iml analyzer/fx/heapstats-analyzer.iml analyzer/fx/pom.xml analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/AboutDialogController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/HeapStatsFXAnalyzer.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/MainWindowController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/WindowController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/PluginController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/jvmlive/JVMLiveController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/jvmlive/mbean/HeapStatsMBeanController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/log/LogController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/snapshot/SnapShotController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/threadrecorder/ThreadRecorderController.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsConfigException.java analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsUtils.java analyzer/fx/src/main/resources/jp/co/ntt/oss/heapstats/window.fxml analyzer/jmx-helper/heapstats-jmx-helper.iml analyzer/jmx-helper/heapstats-jmx.iml analyzer/plugin-api/heapstats-plugin-api.iml analyzer/plugin-api/pom.xml analyzer/plugin-api/src/main/java/jp/co/ntt/oss/heapstats/WindowController.java analyzer/plugin-api/src/main/java/jp/co/ntt/oss/heapstats/plugin/PluginController.java analyzer/plugin-api/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsConfigException.java analyzer/plugin-api/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsUtils.java pom.xml specs/heapstats.spec
diffstat 34 files changed, 1332 insertions(+), 1046 deletions(-) [+]
line wrap: on
line diff
--- a/.idea/compiler.xml	Fri Jul 14 12:18:45 2017 +0900
+++ b/.idea/compiler.xml	Mon Jul 17 23:44:04 2017 +0900
@@ -9,7 +9,9 @@
         <module name="heapstats-analyzer" />
         <module name="heapstats-cli" />
         <module name="heapstats-core" />
+        <module name="heapstats-jmx-helper" />
         <module name="heapstats-mbean" />
+        <module name="heapstats-plugin-api" />
       </profile>
     </annotationProcessing>
     <bytecodeTargetLevel>
@@ -17,7 +19,9 @@
       <module name="heapstats-analyzer" target="1.8" />
       <module name="heapstats-cli" target="1.8" />
       <module name="heapstats-core" target="1.8" />
+      <module name="heapstats-jmx-helper" target="1.8" />
       <module name="heapstats-mbean" target="1.6" />
+      <module name="heapstats-plugin-api" target="1.8" />
     </bytecodeTargetLevel>
   </component>
 </project>
\ No newline at end of file
--- a/.idea/encodings.xml	Fri Jul 14 12:18:45 2017 +0900
+++ b/.idea/encodings.xml	Mon Jul 17 23:44:04 2017 +0900
@@ -5,6 +5,8 @@
     <file url="file://$PROJECT_DIR$/analyzer/cli" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/analyzer/core" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/analyzer/fx" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/analyzer/jmx-helper" charset="UTF-8" />
+    <file url="file://$PROJECT_DIR$/analyzer/plugin-api" charset="UTF-8" />
     <file url="file://$PROJECT_DIR$/mbean/java" charset="UTF-8" />
   </component>
 </project>
\ No newline at end of file
--- a/.idea/modules.xml	Fri Jul 14 12:18:45 2017 +0900
+++ b/.idea/modules.xml	Mon Jul 17 23:44:04 2017 +0900
@@ -6,7 +6,9 @@
       <module fileurl="file://$PROJECT_DIR$/analyzer/fx/heapstats-analyzer.iml" filepath="$PROJECT_DIR$/analyzer/fx/heapstats-analyzer.iml" />
       <module fileurl="file://$PROJECT_DIR$/analyzer/cli/heapstats-cli.iml" filepath="$PROJECT_DIR$/analyzer/cli/heapstats-cli.iml" />
       <module fileurl="file://$PROJECT_DIR$/analyzer/core/heapstats-core.iml" filepath="$PROJECT_DIR$/analyzer/core/heapstats-core.iml" />
+      <module fileurl="file://$PROJECT_DIR$/analyzer/jmx-helper/heapstats-jmx-helper.iml" filepath="$PROJECT_DIR$/analyzer/jmx-helper/heapstats-jmx-helper.iml" />
       <module fileurl="file://$PROJECT_DIR$/mbean/java/heapstats-mbean.iml" filepath="$PROJECT_DIR$/mbean/java/heapstats-mbean.iml" />
+      <module fileurl="file://$PROJECT_DIR$/analyzer/plugin-api/heapstats-plugin-api.iml" filepath="$PROJECT_DIR$/analyzer/plugin-api/heapstats-plugin-api.iml" />
     </modules>
   </component>
 </project>
\ No newline at end of file
--- a/.ignore	Fri Jul 14 12:18:45 2017 +0900
+++ b/.ignore	Mon Jul 17 23:44:04 2017 +0900
@@ -25,6 +25,7 @@
 agent/attacher/dist/*.jar
 agent/attacher/heapstats-attacher
 analyzer/cli/heapstats-cli
+analyzer/fx/heapstats-analyzer
 *.o
 *.lo
 *.Po
--- a/ChangeLog	Fri Jul 14 12:18:45 2017 +0900
+++ b/ChangeLog	Mon Jul 17 23:44:04 2017 +0900
@@ -1,3 +1,7 @@
+2017-07-17 KUBOTA Yuji <kubota.yuji@lab.ntt.co.jp>
+
+	* Bug 3420: Migrate Plugin API module from fx module
+
 2017-07-14 Yasumasa Suenaga <yasuenag@gmail.com>
 
 	* Bug 3421: Override functions might crash on Fedora 26
--- a/Makefile.am	Fri Jul 14 12:18:45 2017 +0900
+++ b/Makefile.am	Mon Jul 17 23:44:04 2017 +0900
@@ -31,6 +31,7 @@
 	$(INSTALL_DATA) $(ANALYZER_DIR)/filterDefine.xsd $(DESTDIR)/$(libexecdir)
 	$(INSTALL_DATA) $(ANALYZER_DIR)/heapstats.properties $(DESTDIR)/$(libexecdir)
 	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/heapstats-core.jar $(DESTDIR)/$(libexecdir)/lib
+	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/heapstats-plugin-api.jar $(DESTDIR)/$(libexecdir)/lib
 	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/heapstats-mbean.jar $(DESTDIR)/$(libexecdir)/lib
 	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/heapstats-jmx-helper.jar $(DESTDIR)/$(libexecdir)/lib
 	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/jgraphx.jar $(DESTDIR)/$(libexecdir)/lib
--- a/Makefile.in	Fri Jul 14 12:18:45 2017 +0900
+++ b/Makefile.in	Mon Jul 17 23:44:04 2017 +0900
@@ -810,6 +810,7 @@
 	$(INSTALL_DATA) $(ANALYZER_DIR)/filterDefine.xsd $(DESTDIR)/$(libexecdir)
 	$(INSTALL_DATA) $(ANALYZER_DIR)/heapstats.properties $(DESTDIR)/$(libexecdir)
 	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/heapstats-core.jar $(DESTDIR)/$(libexecdir)/lib
+	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/heapstats-plugin-api.jar $(DESTDIR)/$(libexecdir)/lib
 	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/heapstats-mbean.jar $(DESTDIR)/$(libexecdir)/lib
 	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/heapstats-jmx-helper.jar $(DESTDIR)/$(libexecdir)/lib
 	$(INSTALL_DATA) $(ANALYZER_DIR)/lib/jgraphx.jar $(DESTDIR)/$(libexecdir)/lib
--- a/analyzer/cli/heapstats-cli.iml	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/cli/heapstats-cli.iml	Mon Jul 17 23:44:04 2017 +0900
@@ -10,8 +10,8 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
-    <orderEntry type="module" module-name="heapstats-jmx" />
+    <orderEntry type="module" module-name="heapstats-jmx-helper" />
     <orderEntry type="module" module-name="heapstats-mbean" />
     <orderEntry type="module" module-name="heapstats-core" />
   </component>
-</module>
+</module>
\ No newline at end of file
--- a/analyzer/core/heapstats-core.iml	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/core/heapstats-core.iml	Mon Jul 17 23:44:04 2017 +0900
@@ -10,4 +10,4 @@
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
-</module>
+</module>
\ No newline at end of file
--- a/analyzer/fx/heapstats-analyzer.iml	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/heapstats-analyzer.iml	Mon Jul 17 23:44:04 2017 +0900
@@ -11,8 +11,9 @@
     </content>
     <orderEntry type="inheritedJdk" />
     <orderEntry type="sourceFolder" forTests="false" />
-    <orderEntry type="module" module-name="heapstats-jmx" />
+    <orderEntry type="module" module-name="heapstats-jmx-helper" />
     <orderEntry type="module" module-name="heapstats-mbean" />
+    <orderEntry type="module" module-name="heapstats-plugin-api" />
     <orderEntry type="module" module-name="heapstats-core" />
     <orderEntry type="module-library">
       <library name="Maven: com.mxgraph:jgraphx:3.3.1.1">
@@ -24,4 +25,4 @@
       </library>
     </orderEntry>
   </component>
-</module>
+</module>
\ No newline at end of file
--- a/analyzer/fx/pom.xml	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/pom.xml	Mon Jul 17 23:44:04 2017 +0900
@@ -82,6 +82,11 @@
         </dependency>
         <dependency>
             <groupId>${project.groupId}</groupId>
+            <artifactId>heapstats-plugin-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
             <artifactId>heapstats-core</artifactId>
             <version>${project.version}</version>
         </dependency>
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/AboutDialogController.java	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/AboutDialogController.java	Mon Jul 17 23:44:04 2017 +0900
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2015 Yasumasa Suenaga
+ * Copyright (C) 2014-2017 Yasumasa Suenaga
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -97,12 +97,12 @@
          * Thus I create array of AbstractMap.SimpleEntry .
          */
         List<AbstractMap.SimpleEntry<String, String>> plugins = new ArrayList<>();
-        WindowController.getInstance().getPluginList().forEach((k, v) -> plugins.add(new AbstractMap.SimpleEntry<>(k, v.getLicense())));
+        MainWindowController.getInstance().getPluginList().forEach((k, v) -> plugins.add(new AbstractMap.SimpleEntry<>(k, v.getLicense())));
         pluginTable.getItems().addAll(plugins);
         
         /* Set library license to libraryTable */
         List<PluginController.LibraryLicense> libraryList = new ArrayList<>();
-        WindowController.getInstance().getPluginList().forEach((n, c) -> Optional.ofNullable(c.getLibraryLicense())
+        MainWindowController.getInstance().getPluginList().forEach((n, c) -> Optional.ofNullable(c.getLibraryLicense())
                                                                                  .ifPresent(l -> l.forEach((k, v) -> libraryList.add(new PluginController.LibraryLicense(n, k, v)))));
         libraryTable.getItems().addAll(libraryList);
     }
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/HeapStatsFXAnalyzer.java	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/HeapStatsFXAnalyzer.java	Mon Jul 17 23:44:04 2017 +0900
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2015 Yasumasa Suenaga
+ * Copyright (C) 2014-2017 Yasumasa Suenaga
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -37,7 +37,7 @@
  */
 public class HeapStatsFXAnalyzer extends Application {
     
-    private WindowController windowController;
+    private MainWindowController mainWindowController;
 
     @Override
     public void start(Stage stage) throws Exception {
@@ -58,10 +58,11 @@
 
         Parent root = mainWindowLoader.load();
         Scene scene = new Scene(root);
-        windowController = (WindowController) mainWindowLoader.getController();
-        windowController.setOwner(stage);
-        windowController.setHostServices(getHostServices());
-        windowController.loadPlugin();
+        HeapStatsUtils.setWindowController(mainWindowLoader.getController());
+        mainWindowController = (MainWindowController) HeapStatsUtils.getWindowController();
+        mainWindowController.setOwner(stage);
+        mainWindowController.setHostServices(getHostServices());
+        mainWindowController.loadPlugin();
 
         stage.setScene(scene);
         stage.show();
@@ -69,7 +70,7 @@
 
     @Override
     public void stop() throws Exception {
-        windowController.getPluginList().values().forEach(c -> Optional.ofNullable(c.getOnCloseRequest()).ifPresent(r -> r.run()));
+        mainWindowController.getPluginList().values().forEach(c -> Optional.ofNullable(c.getOnCloseRequest()).ifPresent(r -> r.run()));
     }
 
     /**
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/MainWindowController.java	Mon Jul 17 23:44:04 2017 +0900
@@ -0,0 +1,382 @@
+/*
+ * Copyright (C) 2014-2017 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;
+
+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.InvalidPathException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+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.lambda.FunctionWrapper;
+import jp.co.ntt.oss.heapstats.plugin.PluginController;
+import jp.co.ntt.oss.heapstats.plugin.builtin.jvmlive.JVMLiveController;
+import jp.co.ntt.oss.heapstats.plugin.builtin.log.LogController;
+import jp.co.ntt.oss.heapstats.plugin.builtin.snapshot.SnapShotController;
+import jp.co.ntt.oss.heapstats.plugin.builtin.threadrecorder.ThreadRecorderController;
+import jp.co.ntt.oss.heapstats.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/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(String appJarString){
+        Path appJarPath;
+
+        try{
+            appJarPath = Paths.get(appJarString);
+        }
+        catch(InvalidPathException e){
+            if((appJarString.charAt(0) == '/') && (appJarString.length() > 2)){ // for Windows
+                appJarPath = Paths.get(appJarString.substring(1));
+            }
+            else{
+                throw e;
+            }
+        }
+
+        Path libPath = appJarPath.getParent().resolve("lib");
+        URL[] jarURLList = null;
+
+        try(DirectoryStream<Path> jarPaths = Files.newDirectoryStream(libPath, "*.jar")){
+            jarURLList = StreamSupport.stream(jarPaths.spliterator(), false)
+                                      .map(new FunctionWrapper<>(p -> p.toUri().toURL()))
+                                      .filter(u -> !u.getFile().endsWith("heapstats-core.jar"))
+                                      .filter(u -> !u.getFile().endsWith("heapstats-mbean.jar"))
+                                      .filter(u -> !u.getFile().endsWith("jgraphx.jar"))
+                                      .collect(Collectors.toList())
+                                      .toArray(new URL[0]);
+        }
+        catch(IOException ex) {
+            Logger.getLogger(MainWindowController.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+        return (jarURLList == null) ? MainWindowController.class.getClassLoader() : new URLClassLoader(jarURLList);
+    }
+
+    /**
+     * Load plugins which is defined in heapstats.properties.
+     */
+    public void loadPlugin(){
+        String resourceName = "/" + this.getClass().getName().replace('.', '/') + ".class";
+        String appJarString = this.getClass().getResource(resourceName).getPath();
+
+        pluginClassLoader = appJarString.contains("!") ? createPluginClassLoader(appJarString.substring(0, appJarString.indexOf('!')).replaceFirst("file:", ""))
+                                                       : MainWindowController.class.getClassLoader();
+
+        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;
+    }
+
+}
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/WindowController.java	Fri Jul 14 12:18:45 2017 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,374 +0,0 @@
-/*
- * Copyright (C) 2014-2015 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;
-
-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.InvalidPathException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.MissingResourceException;
-import java.util.Optional;
-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.lambda.FunctionWrapper;
-import jp.co.ntt.oss.heapstats.plugin.PluginController;
-import jp.co.ntt.oss.heapstats.plugin.builtin.log.LogController;
-import jp.co.ntt.oss.heapstats.plugin.builtin.snapshot.SnapShotController;
-import jp.co.ntt.oss.heapstats.plugin.builtin.threadrecorder.ThreadRecorderController;
-import jp.co.ntt.oss.heapstats.utils.HeapStatsUtils;
-
-/**
- * Main window controller.
- *
- * @author Yasumasa Suenaga
- */
-public class WindowController implements Initializable {
-
-    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 WindowController 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 WindowController 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/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(String appJarString){
-        Path appJarPath;
-
-        try{
-            appJarPath = Paths.get(appJarString);
-        }
-        catch(InvalidPathException e){
-            if((appJarString.charAt(0) == '/') && (appJarString.length() > 2)){ // for Windows
-                appJarPath = Paths.get(appJarString.substring(1));
-            }
-            else{
-                throw e;
-            }
-        }
-
-        Path libPath = appJarPath.getParent().resolve("lib");
-        URL[] jarURLList = null;
-
-        try(DirectoryStream<Path> jarPaths = Files.newDirectoryStream(libPath, "*.jar")){
-            jarURLList = StreamSupport.stream(jarPaths.spliterator(), false)
-                                      .map(new FunctionWrapper<>(p -> p.toUri().toURL()))
-                                      .filter(u -> !u.getFile().endsWith("heapstats-core.jar"))
-                                      .filter(u -> !u.getFile().endsWith("heapstats-mbean.jar"))
-                                      .filter(u -> !u.getFile().endsWith("jgraphx.jar"))
-                                      .collect(Collectors.toList())
-                                      .toArray(new URL[0]);
-        }
-        catch(IOException ex) {
-            Logger.getLogger(WindowController.class.getName()).log(Level.SEVERE, null, ex);
-        }
-
-        return (jarURLList == null) ? WindowController.class.getClassLoader() : new URLClassLoader(jarURLList);
-    }
-
-    /**
-     * Load plugins which is defined in heapstats.properties.
-     */
-    public void loadPlugin(){
-        String resourceName = "/" + this.getClass().getName().replace('.', '/') + ".class";
-        String appJarString = this.getClass().getResource(resourceName).getPath();
-
-        pluginClassLoader = appJarString.contains("!") ? createPluginClassLoader(appJarString.substring(0, appJarString.indexOf('!')).replaceFirst("file:", ""))
-                                                       : WindowController.class.getClassLoader();
-
-        FXMLLoader.setDefaultClassLoader(pluginClassLoader);
-
-        List<String> plugins = HeapStatsUtils.getPlugins();
-        plugins.stream().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;
-    }
-
-}
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/PluginController.java	Fri Jul 14 12:18:45 2017 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2014-2015 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.plugin;
-
-import java.net.URL;
-import java.util.Map;
-import java.util.ResourceBundle;
-import javafx.beans.value.ChangeListener;
-import javafx.concurrent.Task;
-import javafx.event.Event;
-import javafx.event.EventHandler;
-import javafx.fxml.Initializable;
-import javafx.scene.control.ProgressIndicator;
-import javafx.scene.layout.Region;
-import jp.co.ntt.oss.heapstats.WindowController;
-
-/**
- * Base class for HeapStats FX Analyzer plugin.
- * 
- * @author Yasumasa Suenaga
- */
-public abstract class PluginController implements Initializable{
-    
-    /** License indication for GPLv2 */
-    public static final String LICENSE_GPL_V2 = "GNU General Public License version 2";
-    
-    /** License indication for BSD License */
-    public static final String LICENSE_BSD = "Berkeley Software Distribution License";
-
-    private Region veil;
-    
-    private ProgressIndicator progress;
-    
-    public abstract String getPluginName();
-    
-    /**
-     * Getter of license of this plugin.
-     * 
-     * @return License of this plugin.
-     */
-    public abstract String getLicense();
-    
-    /**
-     * Getter of license map which is used by this plugin.
-     * Key is library name, value is license of library.
-     * 
-     * @return License of libraryes.
-     */
-    public abstract Map<String, String> getLibraryLicense();
-    
-    /**
-     * Event handler when tab of this plugin is selected.
-     * 
-     * @return Event handler of this plugin.
-     */
-    public abstract EventHandler<Event> getOnPluginTabSelected();
-   
-    /**
-     * Event andler when main window is closed.
-     * 
-     * @return Event handler of this plugin.
-     */
-    public abstract Runnable getOnCloseRequest();
-
-    /**
-     * Setter of veil region.
-     * This region is used for veiling (e.g. showing progress)
-     * 
-     * @param veil veiling region.
-     */
-    public void setVeil(Region veil){
-        this.veil = veil;
-    }
-    
-    /**
-     * Setter of progress indicator.
-     * This region is used for veiling (e.g. showing progress)
-     * 
-     * @param progress progress indicator.
-     */
-    public void setProgress(ProgressIndicator progress){
-        this.progress = progress;
-    }
-    
-    @Override
-    public void initialize(URL location, ResourceBundle resources) {
-    }
-    
-    /**
-     * Task binder.
-     * This method binds veil and progress indicator to task.
-     * 
-     * @param task Task to be binded.
-     */
-    public void bindTask(Task task){
-        veil.visibleProperty().bind(task.runningProperty());
-        progress.visibleProperty().bind(task.runningProperty());
-        progress.progressProperty().bind(task.progressProperty());
-    }
- 
-    public void setOnWindowResize(ChangeListener<? super Number> event){
-        WindowController.getInstance().getOwner().widthProperty().addListener(event);
-        WindowController.getInstance().getOwner().heightProperty().addListener(event);
-    }
-    
-    /** 
-     * Set data to another plugin. 
-     * This method will be overrided by each plugins. 
-     *  
-     * @param data Data to set. 
-     * @param select Plugin tab will be actived if this value is true. 
-     */ 
-    public void setData(Object data, boolean select){ 
-        if(select){ 
-            WindowController.getInstance().selectTab(getPluginName()); 
-        } 
-    } 
-
-    /**
-     * This class represents license of libraries which are used by each plugin.
-     */
-    public static class LibraryLicense{
-        
-        private final String pluginName;
-        
-        private final String libraryName;
-        
-        private final String license;
-
-        public LibraryLicense(String pluginName, String libraryName, String license) {
-            this.pluginName = pluginName;
-            this.libraryName = libraryName;
-            this.license = license;
-        }
-
-        public String getPluginName() {
-            return pluginName;
-        }
-
-        public String getLibraryName() {
-            return libraryName;
-        }
-
-        public String getLicense() {
-            return license;
-        }
-        
-    }
-
-}
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/jvmlive/JVMLiveController.java	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/jvmlive/JVMLiveController.java	Mon Jul 17 23:44:04 2017 +0900
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2015 Yasumasa Suenaga
+ * Copyright (C) 2014-2017 Yasumasa Suenaga
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -68,7 +68,7 @@
 import javafx.stage.Stage;
 import javafx.stage.StageStyle;
 import javafx.util.Duration;
-import jp.co.ntt.oss.heapstats.WindowController;
+import jp.co.ntt.oss.heapstats.MainWindowController;
 import jp.co.ntt.oss.heapstats.jmx.JMXHelper;
 import jp.co.ntt.oss.heapstats.lambda.ConsumerWrapper;
 import jp.co.ntt.oss.heapstats.plugin.PluginController;
@@ -239,7 +239,7 @@
         mbeanController.loadAllConfigs();
         Stage dialog = new Stage(StageStyle.UTILITY);
 
-        try(InputStream icon = WindowController.class.getResourceAsStream("heapstats-icon.png")){
+        try(InputStream icon = MainWindowController.class.getResourceAsStream("heapstats-icon.png")){
             dialog.getIcons().add(new Image(icon));
         }
         catch(IOException e){
@@ -305,7 +305,7 @@
                                             new FileChooser.ExtensionFilter("All files", "*.*"));
         
         Path hs_err_path = crashList.getSelectionModel().getSelectedItem().getHsErrFile().toPath();
-        Optional.ofNullable(dialog.showSaveDialog(WindowController.getInstance().getOwner()))
+        Optional.ofNullable(dialog.showSaveDialog(MainWindowController.getInstance().getOwner()))
                 .ifPresent(new ConsumerWrapper<>(t -> Files.copy(hs_err_path, t.toPath(), StandardCopyOption.REPLACE_EXISTING)));
     }
     
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/jvmlive/mbean/HeapStatsMBeanController.java	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/jvmlive/mbean/HeapStatsMBeanController.java	Mon Jul 17 23:44:04 2017 +0900
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2014-2015 Yasumasa Suenaga
+ * Copyright (C) 2014-2017 Yasumasa Suenaga
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public License
@@ -45,7 +45,7 @@
 import javafx.stage.FileChooser;
 import javafx.util.converter.IntegerStringConverter;
 import javafx.util.converter.LongStringConverter;
-import jp.co.ntt.oss.heapstats.WindowController;
+import jp.co.ntt.oss.heapstats.MainWindowController;
 import jp.co.ntt.oss.heapstats.jmx.JMXHelper;
 import jp.co.ntt.oss.heapstats.mbean.HeapStatsMBean;
 import jp.co.ntt.oss.heapstats.utils.HeapStatsUtils;
@@ -254,7 +254,7 @@
         dialog.setInitialDirectory(new File(HeapStatsUtils.getDefaultDirectory()));
         dialog.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("CSV file (*.csv)", "*.csv"),
                                             new FileChooser.ExtensionFilter("All files", "*.*"));
-        File logFile = dialog.showSaveDialog(WindowController.getInstance().getOwner());
+        File logFile = dialog.showSaveDialog(MainWindowController.getInstance().getOwner());
         
         if(logFile != null){
             try {
@@ -274,7 +274,7 @@
         dialog.setInitialDirectory(new File(HeapStatsUtils.getDefaultDirectory()));
         dialog.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("SnapShot file (*.dat)", "*.dat"),
                                             new FileChooser.ExtensionFilter("All files", "*.*"));
-        File snapshotFile = dialog.showSaveDialog(WindowController.getInstance().getOwner());
+        File snapshotFile = dialog.showSaveDialog(MainWindowController.getInstance().getOwner());
         
         if(snapshotFile != null){
             try {
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/log/LogController.java	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/log/LogController.java	Mon Jul 17 23:44:04 2017 +0900
@@ -34,7 +34,7 @@
 import javafx.scene.control.TextField;
 import javafx.stage.FileChooser;
 import javafx.stage.FileChooser.ExtensionFilter;
-import jp.co.ntt.oss.heapstats.WindowController;
+import jp.co.ntt.oss.heapstats.MainWindowController;
 import jp.co.ntt.oss.heapstats.container.log.ArchiveData;
 import jp.co.ntt.oss.heapstats.container.log.DiffData;
 import jp.co.ntt.oss.heapstats.container.log.LogData;
@@ -167,7 +167,7 @@
         dialog.getExtensionFilters().addAll(new ExtensionFilter("Log file (*.csv)", "*.csv"),
                 new ExtensionFilter("All files", "*.*"));
 
-        List<File> logList = dialog.showOpenMultipleDialog(WindowController.getInstance().getOwner());
+        List<File> logList = dialog.showOpenMultipleDialog(MainWindowController.getInstance().getOwner());
 
         if (logList != null) {
             logResourcesController.clearAllItems();
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/snapshot/SnapShotController.java	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/snapshot/SnapShotController.java	Mon Jul 17 23:44:04 2017 +0900
@@ -51,7 +51,7 @@
 import javafx.scene.shape.Rectangle;
 import javafx.stage.FileChooser;
 import javafx.stage.FileChooser.ExtensionFilter;
-import jp.co.ntt.oss.heapstats.WindowController;
+import jp.co.ntt.oss.heapstats.MainWindowController;
 import jp.co.ntt.oss.heapstats.container.snapshot.ObjectData;
 import jp.co.ntt.oss.heapstats.container.snapshot.SnapShotHeader;
 import jp.co.ntt.oss.heapstats.container.snapshot.SummaryData;
@@ -261,7 +261,7 @@
         dialog.getExtensionFilters().addAll(new ExtensionFilter("SnapShot file (*.dat)", "*.dat"),
                 new ExtensionFilter("All files", "*.*"));
 
-        List<File> snapshotFileList = dialog.showOpenMultipleDialog(WindowController.getInstance().getOwner());
+        List<File> snapshotFileList = dialog.showOpenMultipleDialog(MainWindowController.getInstance().getOwner());
 
         if (snapshotFileList != null) {
             clearAllItems();
@@ -349,7 +349,7 @@
         dialog.setInitialDirectory(new File(HeapStatsUtils.getDefaultDirectory()));
         dialog.getExtensionFilters().addAll(new ExtensionFilter("CSV file (*.csv)", "*.csv"),
                 new ExtensionFilter("All files", "*.*"));
-        File csvFile = dialog.showSaveDialog(WindowController.getInstance().getOwner());
+        File csvFile = dialog.showSaveDialog(MainWindowController.getInstance().getOwner());
 
         if (csvFile != null) {
             TaskAdapter<CSVDumpGC> task = new TaskAdapter<>(new CSVDumpGC(csvFile, isSelected ? currentTarget.get() : snapShotHeaders));
@@ -375,7 +375,7 @@
         dialog.setInitialDirectory(new File(HeapStatsUtils.getDefaultDirectory()));
         dialog.getExtensionFilters().addAll(new ExtensionFilter("CSV file (*.csv)", "*.csv"),
                 new ExtensionFilter("All files", "*.*"));
-        File csvFile = dialog.showSaveDialog(WindowController.getInstance().getOwner());
+        File csvFile = dialog.showSaveDialog(MainWindowController.getInstance().getOwner());
 
         if (csvFile != null) {
             Predicate<? super ObjectData> filter = histogramController.getFilter();
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/threadrecorder/ThreadRecorderController.java	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/plugin/builtin/threadrecorder/ThreadRecorderController.java	Mon Jul 17 23:44:04 2017 +0900
@@ -28,7 +28,7 @@
 import javafx.scene.control.cell.CheckBoxTableCell;
 import javafx.scene.control.cell.PropertyValueFactory;
 import javafx.stage.FileChooser;
-import jp.co.ntt.oss.heapstats.WindowController;
+import jp.co.ntt.oss.heapstats.MainWindowController;
 import jp.co.ntt.oss.heapstats.container.threadrecord.ThreadStat;
 import jp.co.ntt.oss.heapstats.plugin.PluginController;
 import jp.co.ntt.oss.heapstats.task.ThreadRecordParseTask;
@@ -154,7 +154,7 @@
         dialog.setInitialDirectory(new File(HeapStatsUtils.getDefaultDirectory()));
         dialog.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("Thread Recorder file (*.htr)", "*.htr"),
                                             new FileChooser.ExtensionFilter("All files", "*.*"));
-        File recorderFile = dialog.showOpenDialog(WindowController.getInstance().getOwner());
+        File recorderFile = dialog.showOpenDialog(MainWindowController.getInstance().getOwner());
         
         if(recorderFile != null){
             timelineView.getItems().clear();
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsConfigException.java	Fri Jul 14 12:18:45 2017 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2014-2015 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.utils;
-
-/**
- * Exception class when config loader encounters error.
- * This exception shows that user sets invalid value into configuration file (heapstats.properties) .
- * @author Yasumasa Suenaga
- */
-public class HeapStatsConfigException extends Exception{
-    
-    public HeapStatsConfigException(String message){
-        super(message);
-    }
-    
-    public HeapStatsConfigException(String message, Throwable cause){
-        super(message, cause);
-    }
-    
-}
--- a/analyzer/fx/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsUtils.java	Fri Jul 14 12:18:45 2017 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,423 +0,0 @@
-/*
- * Copyright (C) 2014-2016 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.utils;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.nio.file.Files;
-import java.nio.file.NoSuchFileException;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.time.format.DateTimeFormatter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Locale;
-import java.util.Properties;
-import java.util.ResourceBundle;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import javafx.scene.control.Alert;
-import javafx.scene.control.TextArea;
-import javafx.scene.paint.Color;
-import jp.co.ntt.oss.heapstats.plugin.builtin.jvmlive.JVMLiveController;
-import jp.co.ntt.oss.heapstats.plugin.builtin.log.LogController;
-import jp.co.ntt.oss.heapstats.plugin.builtin.snapshot.SnapShotController;
-import jp.co.ntt.oss.heapstats.plugin.builtin.threadrecorder.ThreadRecorderController;
-
-/**
- * Utility class for HeapStats FX Analyzer.
- *
- * @author Yasumasa Suenaga
- */
-public class HeapStatsUtils {
-
-    /* Rect size in Tooltip for chart legend. */
-    public static final double TOOLTIP_LEGEND_RECT_SIZE = 10.0d;
-
-    /* Gap between each controls in Tooltip. */
-    public static final double TOOLTIP_GRIDPANE_GAP = 5.0d;
-
-    /* Path of HeapStats home directory. */
-    private static Path currentPath = null;
-
-    /* Properties for HeapStats analyzer. */
-    private static final Properties prop = new Properties();
-
-    /* Resource bundle for HeapStats Analyzer. */
-    private static ResourceBundle resource;
-    
-    /* Cached DatetTimeFormatter */
-    private static DateTimeFormatter formatter;
-
-    public static Path getHeapStatsHomeDirectory() {
-
-        if (currentPath == null) {
-            String appJar = Stream.of(System.getProperty("java.class.path").split(System.getProperty("path.separator")))
-                    .filter(s -> s.endsWith("heapstats-analyzer.jar"))
-                    .findFirst()
-                    .orElse(".");
-            currentPath = Paths.get(appJar).toAbsolutePath().getParent();
-        }
-
-        return currentPath;
-    }
-
-    /**
-     * Load HeapStats property file.
-     *
-     * @author Yasumasa Suenaga
-     * @throws IOException - I/O error to parse property file.
-     * @throws HeapStatsConfigException - Invalid value.
-     */
-    public static void load() throws IOException, HeapStatsConfigException {
-        Path properties = Paths.get(getHeapStatsHomeDirectory().toString(), "heapstats.properties");
-
-        try (InputStream in = Files.newInputStream(properties, StandardOpenOption.READ)) {
-            prop.load(in);
-        } catch (NoSuchFileException e) {
-            // use default values.
-        }
-
-        /* Load resource bundle */
-        resource = ResourceBundle.getBundle("HeapStatsResources");
-
-        /* Validate values in properties. */
-
-        /* Language */
-        String language = prop.getProperty("language");
-        if (language == null) {
-            prop.setProperty("language", "en");
-        } else if (!language.equals("en") && !language.equals("ja")) {
-            throw new HeapStatsConfigException(resource.getString("invalid.option") + " language=" + language);
-        }
-
-        /* Load resource bundle again */
-        resource = ResourceBundle.getBundle("HeapStatsResources", new Locale(prop.getProperty("language")));
-
-        /* RankLevel */
-        String rankLevelStr = prop.getProperty("ranklevel");
-        if (rankLevelStr == null) {
-            prop.setProperty("ranklevel", "5");
-        } else {
-            try {
-                Integer.decode(rankLevelStr);
-            } catch (NumberFormatException e) {
-                throw new HeapStatsConfigException(resource.getString("invalid.option") + " ranklevel=" + rankLevelStr, e);
-            }
-        }
-
-        /* ClassRename */
-        /* java.lang.Boolean#parseStinrg() does not throws Throwable.
-         * If user sets invalid value, Boolean will treat "true" .
-         * See javadoc of java.lang.Boolean .
-         */
-        if (prop.getProperty("replace") == null) {
-            prop.setProperty("replace", "false");
-        }
-
-        /* Default directory for dialogs. */
-        if (prop.getProperty("defaultdir") == null) {
-            prop.setProperty("defaultdir", getHeapStatsHomeDirectory().toString());
-        } else {
-            /* check if defaultdir exists */
-            File defaultDir = new File(prop.getProperty("defaultdir"));
-            if (!defaultDir.exists() || !defaultDir.isDirectory()) {
-                prop.setProperty("defaultdir", getHeapStatsHomeDirectory().toString());
-            }
-        }
-
-        /* Log file list to parse. */
-        if (prop.getProperty("logfile") == null) {
-            prop.setProperty("logfile", "redhat-release,cmdline,status,smaps,limits");
-        }
-
-        /* Socket endpoint file to parse. */
-        if (prop.getProperty("socketend") == null) {
-            prop.setProperty("socketend", "tcp,udp,tcp6,udp6");
-        }
-
-        /* Socket owner file to parse. */
-        if (prop.getProperty("owner") == null) {
-            prop.setProperty("owner", "sockowner");
-        }
-
-        /* Background color to draw graphs. */
-        String bgColorStr = prop.getProperty("bgcolor");
-        if (bgColorStr == null) {
-            prop.setProperty("bgcolor", "white");
-        } else {
-            try {
-                Color.web(bgColorStr);
-            } catch (IllegalArgumentException e) {
-                throw new HeapStatsConfigException(resource.getString("invalid.option") + "  bgcolor=" + bgColorStr, e);
-            }
-        }
-
-        /* Java Heap order */
-        String heapOrder = prop.getProperty("heaporder_bottom_young");
-        if (heapOrder == null) {
-            prop.setProperty("heaporder", "true");
-        }
-        
-        /* DateTime format */
-        prop.putIfAbsent("datetime_format", "yyyy/MM/dd HH:mm:ss");
-        formatter = DateTimeFormatter.ofPattern(prop.getProperty("datetime_format"));
-
-        /* Font size of RefTree */
-        String fontsize = prop.getProperty("reftree_fontsize");
-        if (fontsize == null) {
-            prop.setProperty("reftree_fontsize", "11");
-        } else {
-            try {
-                Integer.decode(fontsize);
-            } catch (NumberFormatException e) {
-                throw new HeapStatsConfigException(resource.getString("invalid.option") + " reftree_fontsize=" + fontsize, e);
-            }
-        }
-
-        /* Tick marker on X axis */
-        if (prop.getProperty("tickmarker") == null) {
-            prop.setProperty("tickmarker", "false");
-        }
-
-        /* TickUnit of X axis */
-        String xTickUnitStr = prop.getProperty("x_tickunit");
-        if (xTickUnitStr == null) {
-            prop.setProperty("x_tickunit", "20");
-        } else {
-            try {
-                Double.parseDouble(xTickUnitStr);
-            } catch (NumberFormatException e) {
-                throw new HeapStatsConfigException(resource.getString("invalid.option") + " x_tickunit=" + xTickUnitStr, e);
-            }
-        }
-
-        /* Add shutdown hook for saving current settings. */
-        Runnable savePropImpl = () -> {
-            try (OutputStream out = Files.newOutputStream(properties, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) {
-                prop.store(out, null);
-            } catch (IOException ex) {
-                Logger.getLogger(HeapStatsUtils.class.getName()).log(Level.SEVERE, null, ex);
-            }
-        };
-        Runtime.getRuntime().addShutdownHook(new Thread(savePropImpl));
-    }
-
-    /**
-     * Get plugin list.
-     *
-     * @return Plugin list.
-     */
-    public static List<String> getPlugins() {
-        List<String> pluginList = new ArrayList<>();
-        pluginList.add(LogController.class.getPackage().getName());
-        pluginList.add(SnapShotController.class.getPackage().getName());
-        pluginList.add(ThreadRecorderController.class.getPackage().getName());
-        pluginList.add(JVMLiveController.class.getPackage().getName());
-        pluginList.addAll(Arrays.asList(prop.getProperty("plugins", "").split(";")));
-
-        return pluginList.stream()
-                .map(s -> s.trim())
-                .filter(s -> s.length() > 0)
-                .distinct()
-                .collect(Collectors.toList());
-    }
-
-    /**
-     * Get class name replacement.
-     *
-     * @return true if replace (Java-style).
-     */
-    public static boolean getReplaceClassName() {
-        return Boolean.parseBoolean(prop.getProperty("replace"));
-    }
-
-    /**
-     * Get default directory. This value will be overriden when any file is
-     * opened.
-     *
-     * @return Current default directory.
-     */
-    public static String getDefaultDirectory() {
-        return prop.getProperty("defaultdir");
-    }
-
-    /**
-     * Set default directory.
-     *
-     * @param currentDir New current directory.
-     */
-    public static void setDefaultDirectory(String currentDir) {
-
-        if (currentDir != null) {
-            prop.setProperty("defaultdir", currentDir);
-        }
-
-    }
-
-    /**
-     * Get rank level.
-     *
-     * @return Rank level.
-     */
-    public static int getRankLevel() {
-        return Integer.parseInt(prop.getProperty("ranklevel"));
-    }
-
-    /**
-     * Set rank level.
-     *
-     * @param rankLevel New rank level.
-     */
-    public static void setRankLevel(int rankLevel) {
-        prop.setProperty("ranklevel", Integer.toString(rankLevel));
-    }
-
-    /**
-     * Get background color of chart.
-     *
-     * @return Background color of chart.
-     */
-    public static String getChartBgColor() {
-        return prop.getProperty("bgcolor");
-    }
-
-    /**
-     * Get whether the bottom of java heap order is young or not.
-     *
-     * @return whether the bottom is young or not.
-     */
-    public static boolean getHeapOrder() {
-        return Boolean.parseBoolean(prop.getProperty("heaporder_bottom_young"));
-    }
-
-    /**
-     * Set whethr the bottom of java heap order is young or not.
-     *
-     * @param heapOrder whether the bottom is young or not.
-     */
-    public static void setHeapOrder(boolean heapOrder) {
-        prop.setProperty("heaporder_bottom_young", Boolean.toString(heapOrder));
-    }
-
-    /**
-     * Get language.
-     *
-     * @return Language
-     */
-    public static String getLanguage() {
-        return prop.getProperty("language");
-    }
-
-    /**
-     * Get ResourceBundle. This value depends on getLanguage().
-     *
-     * @return ResourceBundle.
-     */
-    public static ResourceBundle getResourceBundle() {
-        return ResourceBundle.getBundle("HeapStatsResources", new Locale(getLanguage()));
-    }
-    
-    /**
-     * Get DataTimeFormatter which is initialized by datetime_format value in heapstats.properties.
-     * @return Instance of DateTimeFormatter.
-     */
-    public static DateTimeFormatter getDateTimeFormatter(){
-        return formatter;
-    }
-
-    /**
-     * Get font size of ReferenceTree tab.
-     * @return fontSize
-     */
-    public static int getFontSizeOfRefTree() {
-        return Integer.parseInt(prop.getProperty("reftree_fontsize"));
-    }
-
-    /**
-     * Get font size of ReferenceTree tab.
-     * @param fontSize font size
-     */
-    public static void setFontSizeOfRefTree( int fontSize ) {
-        prop.setProperty("reftree_fontsize", Integer.toString(fontSize));
-    }
-
-    /**
-     * Get the switch to show tick marker on X axis.
-     *
-     * @return true if Analyzer should show tick marker.
-     */
-    public static boolean getTickMarkerSwitch() {
-        return Boolean.parseBoolean(prop.getProperty("tickmarker"));
-    }
-    
-    /**
-     * Get TickUnit on X axis.
-     * 
-     * @return TickUnit of X axis.
-     */
-    public static double getXTickUnit(){
-        return Double.parseDouble(prop.getProperty("x_tickunit"));
-    }
-    
-    /**
-     * Convert stack trace to String.
-     *
-     * @param e Throwable object to convert.
-     *
-     * @return String result of e.printStackTrace()
-     */
-    public static String stackTarceToString(Throwable e) {
-        String result = null;
-
-        try (StringWriter strWriter = new StringWriter();
-                PrintWriter printWriter = new PrintWriter(strWriter)) {
-            e.printStackTrace(printWriter);
-            result = strWriter.toString();
-        } catch (IOException ex) {
-            Logger.getLogger(HeapStatsUtils.class.getName()).log(Level.SEVERE, null, ex);
-        }
-
-        return result;
-    }
-
-    /**
-     * Show alert dialog.
-     *
-     * @param e Throwable instance to show.
-     */
-    public static void showExceptionDialog(Throwable e) {
-        TextArea details = new TextArea(HeapStatsUtils.stackTarceToString(e));
-        details.setEditable(false);
-
-        Alert dialog = new Alert(Alert.AlertType.ERROR);
-        dialog.setTitle("Error");
-        dialog.setHeaderText(e.getLocalizedMessage());
-        dialog.getDialogPane().setExpandableContent(details);
-        dialog.showAndWait();
-    }
-
-}
--- a/analyzer/fx/src/main/resources/jp/co/ntt/oss/heapstats/window.fxml	Fri Jul 14 12:18:45 2017 +0900
+++ b/analyzer/fx/src/main/resources/jp/co/ntt/oss/heapstats/window.fxml	Mon Jul 17 23:44:04 2017 +0900
@@ -22,7 +22,7 @@
 <?import javafx.scene.control.*?>
 <?import javafx.scene.layout.*?>
 
-<StackPane id="stackPane" fx:id="stackPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.co.ntt.oss.heapstats.WindowController">
+<StackPane id="stackPane" fx:id="stackPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="jp.co.ntt.oss.heapstats.MainWindowController">
     <children>
         <AnchorPane prefHeight="200.0" prefWidth="200.0">
             <children>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/analyzer/jmx-helper/heapstats-jmx-helper.iml	Mon Jul 17 23:44:04 2017 +0900
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="false">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="heapstats-mbean" />
+    <orderEntry type="module" module-name="heapstats-core" />
+  </component>
+</module>
\ No newline at end of file
--- a/analyzer/jmx-helper/heapstats-jmx.iml	Fri Jul 14 12:18:45 2017 +0900
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
-  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
-    <output url="file://$MODULE_DIR$/target/classes" />
-    <output-test url="file://$MODULE_DIR$/target/test-classes" />
-    <content url="file://$MODULE_DIR$">
-      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
-      <excludeFolder url="file://$MODULE_DIR$/target" />
-    </content>
-    <orderEntry type="inheritedJdk" />
-    <orderEntry type="sourceFolder" forTests="false" />
-    <orderEntry type="module" module-name="heapstats-mbean" />
-    <orderEntry type="module" module-name="heapstats-core" />
-  </component>
-</module>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/analyzer/plugin-api/heapstats-plugin-api.iml	Mon Jul 17 23:44:04 2017 +0900
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8" inherit-compiler-output="false">
+    <output url="file://$MODULE_DIR$/target/classes" />
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />
+    <content url="file://$MODULE_DIR$">
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
+      <excludeFolder url="file://$MODULE_DIR$/target" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+    <orderEntry type="module" module-name="heapstats-mbean" />
+    <orderEntry type="module" module-name="heapstats-core" />
+  </component>
+</module>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/analyzer/plugin-api/pom.xml	Mon Jul 17 23:44:04 2017 +0900
@@ -0,0 +1,197 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+ Copyright (C) 2017 Nippon Telegraph and Telephone Corporation
+
+ 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>jp.co.ntt.oss.heapstats</groupId>
+    <artifactId>heapstats-plugin-api</artifactId>
+    <version>2.1-SNAPSHOT</version>
+    <packaging>jar</packaging>
+
+    <name>HeapStats Analyzer Plugin API</name>
+    <description>The API for HeapStats Analyzer plugins</description>
+    <url>http://icedtea.classpath.org/wiki/HeapStats</url>
+
+    <licenses>
+        <license>
+            <name>GNU General Public License, version 2</name>
+            <url>https://www.gnu.org/licenses/gpl-2.0.html</url>
+            <distribution>repo</distribution>
+        </license>
+    </licenses>
+
+    <developers>
+        <developer>
+            <name>Yasumasa Suenaga</name>
+            <email>yasuenag@gmail.com</email>
+            <organization>icedtea</organization>
+            <organizationUrl>http://www.icedtea.classpath.org</organizationUrl>
+        </developer>
+        <developer>
+            <name>KUBOTA Yuji</name>
+            <email>kubota.yuji@gmail.com</email>
+            <organization>icedtea</organization>
+            <organizationUrl>http://www.icedtea.classpath.org</organizationUrl>
+        </developer>
+    </developers>
+
+    <scm>
+        <url>http://icedtea.classpath.org/hg/heapstats</url>
+        <connection>scm:hg:http://icedtea.classpath.org/hg/heapstats</connection>
+        <developerConnection>scm:hg:ssh://icedtea.classpath.org/hg/heapstats</developerConnection>
+    </scm>
+
+    <distributionManagement>
+        <snapshotRepository>
+            <id>ossrh</id>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+        </snapshotRepository>
+        <repository>
+            <id>ossrh</id>
+            <url>https://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+        </repository>
+    </distributionManagement>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <maven.compiler.source>1.8</maven.compiler.source>
+        <maven.compiler.target>1.8</maven.compiler.target>
+    </properties>
+
+    <organization>
+        <name>NTT OSS Center</name>
+    </organization>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>heapstats-mbean</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>heapstats-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <version>3.3</version>
+                <configuration>
+                    <compilerArgs>
+                        <arg>-Xlint:all</arg>
+                    </compilerArgs>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-jar-plugin</artifactId>
+                <version>2.6</version>
+            </plugin>
+            <plugin>
+                <groupId>org.sonatype.plugins</groupId>
+                <artifactId>nexus-staging-maven-plugin</artifactId>
+                <version>1.6.7</version>
+                <extensions>true</extensions>
+                <configuration>
+                    <serverId>ossrh</serverId>
+                    <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+                    <autoReleaseAfterClose>true</autoReleaseAfterClose>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-source-plugin</artifactId>
+                <version>2.3</version>
+                <executions>
+                    <execution>
+                        <id>attach-sources</id>
+                        <phase>deploy</phase>
+                        <goals>
+                            <goal>jar-no-fork</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <version>2.9.1</version>
+                <executions>
+                    <execution>
+                        <id>attach-javadocs</id>
+                        <phase>deploy</phase>
+                        <goals>
+                            <goal>jar</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-deploy-plugin</artifactId>
+                <version>2.8.2</version>
+                <executions>
+                    <execution>
+                        <id>deploy</id>
+                        <phase>deploy</phase>
+                        <goals>
+                            <goal>deploy</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+    <profiles>
+        <profile>
+            <id>release-sign-artifacts</id>
+            <!-- releasing for maven repository, mvn -DperformRelease=true deploy -->
+            <activation>
+                <property>
+                    <name>performRelease</name>
+                    <value>true</value>
+                </property>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-gpg-plugin</artifactId>
+                        <version>1.5</version>
+                        <executions>
+                            <execution>
+                                <id>sign-artifacts</id>
+                                <phase>verify</phase>
+                                <goals>
+                                    <goal>sign</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/analyzer/plugin-api/src/main/java/jp/co/ntt/oss/heapstats/WindowController.java	Mon Jul 17 23:44:04 2017 +0900
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 Nippon Telegraph and Telephone Corporation
+ *
+ * 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;
+
+import javafx.stage.Window;
+import jp.co.ntt.oss.heapstats.plugin.PluginController;
+
+import java.util.Map;
+
+public interface WindowController {
+    void selectTab(String pluginName) throws IllegalArgumentException;
+    Window getOwner();
+    Map<String, PluginController> getPluginList();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/analyzer/plugin-api/src/main/java/jp/co/ntt/oss/heapstats/plugin/PluginController.java	Mon Jul 17 23:44:04 2017 +0900
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2014-2017 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.plugin;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.ResourceBundle;
+import javafx.beans.value.ChangeListener;
+import javafx.concurrent.Task;
+import javafx.event.Event;
+import javafx.event.EventHandler;
+import javafx.fxml.Initializable;
+import javafx.scene.control.ProgressIndicator;
+import javafx.scene.layout.Region;
+import jp.co.ntt.oss.heapstats.utils.HeapStatsUtils;
+
+/**
+ * Base class for HeapStats FX Analyzer plugin.
+ * 
+ * @author Yasumasa Suenaga
+ */
+public abstract class PluginController implements Initializable{
+    
+    /** License indication for GPLv2 */
+    public static final String LICENSE_GPL_V2 = "GNU General Public License version 2";
+    
+    /** License indication for BSD License */
+    public static final String LICENSE_BSD = "Berkeley Software Distribution License";
+
+    private Region veil;
+    
+    private ProgressIndicator progress;
+    
+    public abstract String getPluginName();
+    
+    /**
+     * Getter of license of this plugin.
+     * 
+     * @return License of this plugin.
+     */
+    public abstract String getLicense();
+    
+    /**
+     * Getter of license map which is used by this plugin.
+     * Key is library name, value is license of library.
+     * 
+     * @return License of libraryes.
+     */
+    public abstract Map<String, String> getLibraryLicense();
+    
+    /**
+     * Event handler when tab of this plugin is selected.
+     * 
+     * @return Event handler of this plugin.
+     */
+    public abstract EventHandler<Event> getOnPluginTabSelected();
+   
+    /**
+     * Event andler when main window is closed.
+     * 
+     * @return Event handler of this plugin.
+     */
+    public abstract Runnable getOnCloseRequest();
+
+    /**
+     * Setter of veil region.
+     * This region is used for veiling (e.g. showing progress)
+     * 
+     * @param veil veiling region.
+     */
+    public void setVeil(Region veil){
+        this.veil = veil;
+    }
+    
+    /**
+     * Setter of progress indicator.
+     * This region is used for veiling (e.g. showing progress)
+     * 
+     * @param progress progress indicator.
+     */
+    public void setProgress(ProgressIndicator progress){
+        this.progress = progress;
+    }
+    
+    @Override
+    public void initialize(URL location, ResourceBundle resources) {
+    }
+    
+    /**
+     * Task binder.
+     * This method binds veil and progress indicator to task.
+     * 
+     * @param task Task to be binded.
+     */
+    public void bindTask(Task task){
+        veil.visibleProperty().bind(task.runningProperty());
+        progress.visibleProperty().bind(task.runningProperty());
+        progress.progressProperty().bind(task.progressProperty());
+    }
+ 
+    public void setOnWindowResize(ChangeListener<? super Number> event){
+        HeapStatsUtils.getWindowController().getOwner().widthProperty().addListener(event);
+        HeapStatsUtils.getWindowController().getOwner().heightProperty().addListener(event);
+    }
+    
+    /** 
+     * Set data to another plugin. 
+     * This method will be overrided by each plugins. 
+     *  
+     * @param data Data to set. 
+     * @param select Plugin tab will be actived if this value is true. 
+     */ 
+    public void setData(Object data, boolean select){ 
+        if(select){
+            HeapStatsUtils.getWindowController().selectTab(getPluginName());
+        } 
+    } 
+
+    /**
+     * This class represents license of libraries which are used by each plugin.
+     */
+    public static class LibraryLicense{
+        
+        private final String pluginName;
+        
+        private final String libraryName;
+        
+        private final String license;
+
+        public LibraryLicense(String pluginName, String libraryName, String license) {
+            this.pluginName = pluginName;
+            this.libraryName = libraryName;
+            this.license = license;
+        }
+
+        public String getPluginName() {
+            return pluginName;
+        }
+
+        public String getLibraryName() {
+            return libraryName;
+        }
+
+        public String getLicense() {
+            return license;
+        }
+        
+    }
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/analyzer/plugin-api/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsConfigException.java	Mon Jul 17 23:44:04 2017 +0900
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2014-2015 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.utils;
+
+/**
+ * Exception class when config loader encounters error.
+ * This exception shows that user sets invalid value into configuration file (heapstats.properties) .
+ * @author Yasumasa Suenaga
+ */
+public class HeapStatsConfigException extends Exception{
+    
+    public HeapStatsConfigException(String message){
+        super(message);
+    }
+    
+    public HeapStatsConfigException(String message, Throwable cause){
+        super(message, cause);
+    }
+    
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/analyzer/plugin-api/src/main/java/jp/co/ntt/oss/heapstats/utils/HeapStatsUtils.java	Mon Jul 17 23:44:04 2017 +0900
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2014-2017 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.utils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.time.format.DateTimeFormatter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javafx.scene.control.Alert;
+import javafx.scene.control.TextArea;
+import javafx.scene.paint.Color;
+import jp.co.ntt.oss.heapstats.WindowController;
+
+/**
+ * Utility class for HeapStats FX Analyzer.
+ *
+ * @author Yasumasa Suenaga
+ */
+public class HeapStatsUtils {
+
+    /* Rect size in Tooltip for chart legend. */
+    public static final double TOOLTIP_LEGEND_RECT_SIZE = 10.0d;
+
+    /* Gap between each controls in Tooltip. */
+    public static final double TOOLTIP_GRIDPANE_GAP = 5.0d;
+
+    /* Path of HeapStats home directory. */
+    private static Path currentPath = null;
+
+    /* Properties for HeapStats analyzer. */
+    private static final Properties prop = new Properties();
+
+    /* Resource bundle for HeapStats Analyzer. */
+    private static ResourceBundle resource;
+    
+    /* Cached DateTimeFormatter */
+    private static DateTimeFormatter formatter;
+
+    /* Main window controller */
+    private static WindowController controller;
+
+    public static Path getHeapStatsHomeDirectory() {
+
+        if (currentPath == null) {
+            String appJar = Stream.of(System.getProperty("java.class.path").split(System.getProperty("path.separator")))
+                    .filter(s -> s.endsWith("heapstats-analyzer.jar"))
+                    .findFirst()
+                    .orElse(".");
+            currentPath = Paths.get(appJar).toAbsolutePath().getParent();
+        }
+
+        return currentPath;
+    }
+
+    /**
+     * Load HeapStats property file.
+     *
+     * @author Yasumasa Suenaga
+     * @throws IOException - I/O error to parse property file.
+     * @throws HeapStatsConfigException - Invalid value.
+     */
+    public static void load() throws IOException, HeapStatsConfigException {
+        Path properties = Paths.get(getHeapStatsHomeDirectory().toString(), "heapstats.properties");
+
+        try (InputStream in = Files.newInputStream(properties, StandardOpenOption.READ)) {
+            prop.load(in);
+        } catch (NoSuchFileException e) {
+            // use default values.
+        }
+
+        /* Load resource bundle */
+        resource = ResourceBundle.getBundle("HeapStatsResources");
+
+        /* Validate values in properties. */
+
+        /* Language */
+        String language = prop.getProperty("language");
+        if (language == null) {
+            prop.setProperty("language", "en");
+        } else if (!language.equals("en") && !language.equals("ja")) {
+            throw new HeapStatsConfigException(resource.getString("invalid.option") + " language=" + language);
+        }
+
+        /* Load resource bundle again */
+        resource = ResourceBundle.getBundle("HeapStatsResources", new Locale(prop.getProperty("language")));
+
+        /* RankLevel */
+        String rankLevelStr = prop.getProperty("ranklevel");
+        if (rankLevelStr == null) {
+            prop.setProperty("ranklevel", "5");
+        } else {
+            try {
+                Integer.decode(rankLevelStr);
+            } catch (NumberFormatException e) {
+                throw new HeapStatsConfigException(resource.getString("invalid.option") + " ranklevel=" + rankLevelStr, e);
+            }
+        }
+
+        /* ClassRename */
+        /* java.lang.Boolean#parseStinrg() does not throws Throwable.
+         * If user sets invalid value, Boolean will treat "true" .
+         * See javadoc of java.lang.Boolean .
+         */
+        if (prop.getProperty("replace") == null) {
+            prop.setProperty("replace", "false");
+        }
+
+        /* Default directory for dialogs. */
+        if (prop.getProperty("defaultdir") == null) {
+            prop.setProperty("defaultdir", getHeapStatsHomeDirectory().toString());
+        } else {
+            /* check if defaultdir exists */
+            File defaultDir = new File(prop.getProperty("defaultdir"));
+            if (!defaultDir.exists() || !defaultDir.isDirectory()) {
+                prop.setProperty("defaultdir", getHeapStatsHomeDirectory().toString());
+            }
+        }
+
+        /* Log file list to parse. */
+        if (prop.getProperty("logfile") == null) {
+            prop.setProperty("logfile", "redhat-release,cmdline,status,smaps,limits");
+        }
+
+        /* Socket endpoint file to parse. */
+        if (prop.getProperty("socketend") == null) {
+            prop.setProperty("socketend", "tcp,udp,tcp6,udp6");
+        }
+
+        /* Socket owner file to parse. */
+        if (prop.getProperty("owner") == null) {
+            prop.setProperty("owner", "sockowner");
+        }
+
+        /* Background color to draw graphs. */
+        String bgColorStr = prop.getProperty("bgcolor");
+        if (bgColorStr == null) {
+            prop.setProperty("bgcolor", "white");
+        } else {
+            try {
+                Color.web(bgColorStr);
+            } catch (IllegalArgumentException e) {
+                throw new HeapStatsConfigException(resource.getString("invalid.option") + "  bgcolor=" + bgColorStr, e);
+            }
+        }
+
+        /* Java Heap order */
+        String heapOrder = prop.getProperty("heaporder_bottom_young");
+        if (heapOrder == null) {
+            prop.setProperty("heaporder", "true");
+        }
+        
+        /* DateTime format */
+        prop.putIfAbsent("datetime_format", "yyyy/MM/dd HH:mm:ss");
+        formatter = DateTimeFormatter.ofPattern(prop.getProperty("datetime_format"));
+
+        /* Font size of RefTree */
+        String fontsize = prop.getProperty("reftree_fontsize");
+        if (fontsize == null) {
+            prop.setProperty("reftree_fontsize", "11");
+        } else {
+            try {
+                Integer.decode(fontsize);
+            } catch (NumberFormatException e) {
+                throw new HeapStatsConfigException(resource.getString("invalid.option") + " reftree_fontsize=" + fontsize, e);
+            }
+        }
+
+        /* Tick marker on X axis */
+        if (prop.getProperty("tickmarker") == null) {
+            prop.setProperty("tickmarker", "false");
+        }
+
+        /* TickUnit of X axis */
+        String xTickUnitStr = prop.getProperty("x_tickunit");
+        if (xTickUnitStr == null) {
+            prop.setProperty("x_tickunit", "20");
+        } else {
+            try {
+                Double.parseDouble(xTickUnitStr);
+            } catch (NumberFormatException e) {
+                throw new HeapStatsConfigException(resource.getString("invalid.option") + " x_tickunit=" + xTickUnitStr, e);
+            }
+        }
+
+        /* Add shutdown hook for saving current settings. */
+        Runnable savePropImpl = () -> {
+            try (OutputStream out = Files.newOutputStream(properties, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE)) {
+                prop.store(out, null);
+            } catch (IOException ex) {
+                Logger.getLogger(HeapStatsUtils.class.getName()).log(Level.SEVERE, null, ex);
+            }
+        };
+        Runtime.getRuntime().addShutdownHook(new Thread(savePropImpl));
+    }
+
+    /**
+     * Get plugin list by config.
+     *
+     * @return Plugin list.
+     */
+    public static List<String> getPlugins() {
+        return Arrays.asList(prop.getProperty("plugins", "").split(";"))
+                .stream()
+                .map(s -> s.trim())
+                .filter(s -> s.length() > 0)
+                .collect(Collectors.toList());
+    }
+
+    /**
+     * Get class name replacement.
+     *
+     * @return true if replace (Java-style).
+     */
+    public static boolean getReplaceClassName() {
+        return Boolean.parseBoolean(prop.getProperty("replace"));
+    }
+
+    /**
+     * Get default directory. This value will be overriden when any file is
+     * opened.
+     *
+     * @return Current default directory.
+     */
+    public static String getDefaultDirectory() {
+        return prop.getProperty("defaultdir");
+    }
+
+    /**
+     * Set default directory.
+     *
+     * @param currentDir New current directory.
+     */
+    public static void setDefaultDirectory(String currentDir) {
+
+        if (currentDir != null) {
+            prop.setProperty("defaultdir", currentDir);
+        }
+
+    }
+
+    /**
+     * Get rank level.
+     *
+     * @return Rank level.
+     */
+    public static int getRankLevel() {
+        return Integer.parseInt(prop.getProperty("ranklevel"));
+    }
+
+    /**
+     * Set rank level.
+     *
+     * @param rankLevel New rank level.
+     */
+    public static void setRankLevel(int rankLevel) {
+        prop.setProperty("ranklevel", Integer.toString(rankLevel));
+    }
+
+    /**
+     * Get background color of chart.
+     *
+     * @return Background color of chart.
+     */
+    public static String getChartBgColor() {
+        return prop.getProperty("bgcolor");
+    }
+
+    /**
+     * Get whether the bottom of java heap order is young or not.
+     *
+     * @return whether the bottom is young or not.
+     */
+    public static boolean getHeapOrder() {
+        return Boolean.parseBoolean(prop.getProperty("heaporder_bottom_young"));
+    }
+
+    /**
+     * Set whethr the bottom of java heap order is young or not.
+     *
+     * @param heapOrder whether the bottom is young or not.
+     */
+    public static void setHeapOrder(boolean heapOrder) {
+        prop.setProperty("heaporder_bottom_young", Boolean.toString(heapOrder));
+    }
+
+    /**
+     * Get language.
+     *
+     * @return Language
+     */
+    public static String getLanguage() {
+        return prop.getProperty("language");
+    }
+
+    /**
+     * Get ResourceBundle. This value depends on getLanguage().
+     *
+     * @return ResourceBundle.
+     */
+    public static ResourceBundle getResourceBundle() {
+        return ResourceBundle.getBundle("HeapStatsResources", new Locale(getLanguage()));
+    }
+    
+    /**
+     * Get DataTimeFormatter which is initialized by datetime_format value in heapstats.properties.
+     * @return Instance of DateTimeFormatter.
+     */
+    public static DateTimeFormatter getDateTimeFormatter(){
+        return formatter;
+    }
+
+    /**
+     * Get font size of ReferenceTree tab.
+     * @return fontSize
+     */
+    public static int getFontSizeOfRefTree() {
+        return Integer.parseInt(prop.getProperty("reftree_fontsize"));
+    }
+
+    /**
+     * Get font size of ReferenceTree tab.
+     * @param fontSize font size
+     */
+    public static void setFontSizeOfRefTree( int fontSize ) {
+        prop.setProperty("reftree_fontsize", Integer.toString(fontSize));
+    }
+
+    /**
+     * Get the switch to show tick marker on X axis.
+     *
+     * @return true if Analyzer should show tick marker.
+     */
+    public static boolean getTickMarkerSwitch() {
+        return Boolean.parseBoolean(prop.getProperty("tickmarker"));
+    }
+    
+    /**
+     * Get TickUnit on X axis.
+     * 
+     * @return TickUnit of X axis.
+     */
+    public static double getXTickUnit(){
+        return Double.parseDouble(prop.getProperty("x_tickunit"));
+    }
+    
+    /**
+     * Convert stack trace to String.
+     *
+     * @param e Throwable object to convert.
+     *
+     * @return String result of e.printStackTrace()
+     */
+    public static String stackTraceToString(Throwable e) {
+        String result = null;
+
+        try (StringWriter strWriter = new StringWriter();
+                PrintWriter printWriter = new PrintWriter(strWriter)) {
+            e.printStackTrace(printWriter);
+            result = strWriter.toString();
+        } catch (IOException ex) {
+            Logger.getLogger(HeapStatsUtils.class.getName()).log(Level.SEVERE, null, ex);
+        }
+
+        return result;
+    }
+
+    /**
+     * Show alert dialog.
+     *
+     * @param e Throwable instance to show.
+     */
+    public static void showExceptionDialog(Throwable e) {
+        TextArea details = new TextArea(HeapStatsUtils.stackTraceToString(e));
+        details.setEditable(false);
+
+        Alert dialog = new Alert(Alert.AlertType.ERROR);
+        dialog.setTitle("Error");
+        dialog.setHeaderText(e.getLocalizedMessage());
+        dialog.getDialogPane().setExpandableContent(details);
+        dialog.showAndWait();
+    }
+
+    /**
+     * Set an instance of main window controller.
+     *
+     * @param cont an instance of WindowController.
+     */
+    public static void setWindowController(WindowController cont) {
+        controller = cont;
+    }
+
+    /**
+     * Get an instance of main window controller.
+     */
+    public static WindowController getWindowController() {
+        return controller;
+    }
+
+}
--- a/pom.xml	Fri Jul 14 12:18:45 2017 +0900
+++ b/pom.xml	Mon Jul 17 23:44:04 2017 +0900
@@ -67,6 +67,7 @@
         <module>mbean/java</module>
         <module>analyzer/core</module>
         <module>analyzer/cli</module>
+        <module>analyzer/plugin-api</module>
         <module>analyzer/fx</module>
         <module>analyzer/jmx-helper</module>
     </modules>
--- a/specs/heapstats.spec	Fri Jul 14 12:18:45 2017 +0900
+++ b/specs/heapstats.spec	Mon Jul 17 23:44:04 2017 +0900
@@ -162,6 +162,7 @@
 %{_libexecdir}/heapstats/filterDefine.xsd
 %{_libexecdir}/heapstats/heapstats.properties
 %{_libexecdir}/heapstats/lib/jgraphx.jar
+%{_libexecdir}/heapstats/lib/heapstats-plugin-api.jar
 %endif
 
 %changelog