From 71cc5b1612e79a6d24fd3c68564171bddee614df Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 20 Jul 2024 17:55:37 -0400 Subject: [PATCH] Added more documentation to PerfinApp.java, and added JSON export. --- .../com/andrewlalis/perfin/PerfinApp.java | 31 ++++++++++++++-- .../control/SqlConsoleViewController.java | 35 +++++++++++++++++-- src/main/java/module-info.java | 1 + 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/andrewlalis/perfin/PerfinApp.java b/src/main/java/com/andrewlalis/perfin/PerfinApp.java index dd3774a..62dda11 100644 --- a/src/main/java/com/andrewlalis/perfin/PerfinApp.java +++ b/src/main/java/com/andrewlalis/perfin/PerfinApp.java @@ -30,7 +30,9 @@ import java.util.function.Consumer; public class PerfinApp extends Application { private static final Logger log = LoggerFactory.getLogger(PerfinApp.class); public static final Path APP_DIR = Path.of(System.getProperty("user.home", "."), ".perfin"); + /** The singleton instance of the application. */ public static PerfinApp instance; + /** The singleton profile loader for the application. */ public static ProfileLoader profileLoader; /** @@ -66,20 +68,28 @@ public class PerfinApp extends Application { } } + /** + * Part of the app's startup, where the main scene is initialized. + * @param stage The JavaFX stage where the scene will be placed. + * @param msgConsumer A message consumer to relay status messages to the user. + */ private void initMainScreen(Stage stage, Consumer msgConsumer) { msgConsumer.accept("Initializing main screen."); Platform.runLater(() -> { stage.hide(); Scene mainViewScene = SceneUtil.load("/main-view.fxml"); - mainViewScene.getStylesheets().addAll( - PerfinApp.class.getResource("/style/base.css").toExternalForm() - ); + SceneUtil.addStylesheets(mainViewScene, "/style/base.css"); stage.setScene(mainViewScene); stage.setTitle("Perfin"); stage.getIcons().add(ImageCache.getLogo256()); }); } + /** + * Part of the app's startup, where all the app's routes to various views + * are defined. + * @param msgConsumer A message consumer to relay status messages to the user. + */ private static void defineRoutes(Consumer msgConsumer) { msgConsumer.accept("Initializing application views."); Platform.runLater(() -> { @@ -111,6 +121,11 @@ public class PerfinApp extends Application { }); } + /** + * A part of the app's startup which ensures that the main directory exists. + * @param msgConsumer A message consumer to relay status messages to the user. + * @throws Exception If file operations fail. + */ private static void initAppDir(Consumer msgConsumer) throws Exception { msgConsumer.accept("Validating application files."); if (Files.notExists(APP_DIR)) { @@ -124,6 +139,13 @@ public class PerfinApp extends Application { } } + /** + * The final part of the app's startup sequence, where the last profile is + * loaded and set as the current profile. Calling `Profile.setCurrent` + * triggers many components to refresh their data for the current profile. + * @param msgConsumer A message consumer to relay status messages to the user. + * @throws Exception If the profile could not be loaded for some reason. + */ private static void loadLastUsedProfile(Consumer msgConsumer) throws Exception { String lastProfile = ProfileLoader.getLastProfile(); msgConsumer.accept("Loading the most recent profile: \"" + lastProfile + "\"."); @@ -135,6 +157,9 @@ public class PerfinApp extends Application { } } + /** + * Loads all application fonts from the bundled resource files. + */ private static void loadFonts() { List fontResources = List.of( "/font/JetBrainsMono-2.304/fonts/ttf/JetBrainsMono-Regular.ttf", diff --git a/src/main/java/com/andrewlalis/perfin/control/SqlConsoleViewController.java b/src/main/java/com/andrewlalis/perfin/control/SqlConsoleViewController.java index 39cf77a..9a70fe1 100644 --- a/src/main/java/com/andrewlalis/perfin/control/SqlConsoleViewController.java +++ b/src/main/java/com/andrewlalis/perfin/control/SqlConsoleViewController.java @@ -5,6 +5,10 @@ import com.andrewlalis.perfin.data.SavedQueryRepository; import com.andrewlalis.perfin.data.impl.JdbcDataSource; import com.andrewlalis.perfin.data.util.FileUtil; import com.andrewlalis.perfin.model.Profile; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import javafx.fxml.FXML; import javafx.scene.Node; import javafx.scene.control.*; @@ -24,6 +28,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Types; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -145,8 +150,7 @@ public class SqlConsoleViewController implements RouteSelectionListener { if (name.endsWith(".csv")) { writeQueryResultsToCsv(rs, chosenFile.toPath()); } else if (name.endsWith(".json")) { -// writeQueryResultsToJson(rs, chosenFile.toPath()); - Popups.message(sqlEditorTextArea, "JSON not yet supported."); + writeQueryResultsToJson(rs, chosenFile.toPath()); } } catch (Exception e) { Popups.error(sqlEditorTextArea, e); @@ -173,6 +177,33 @@ public class SqlConsoleViewController implements RouteSelectionListener { } } + private void writeQueryResultsToJson(ResultSet rs, Path file) throws SQLException, IOException { + ObjectMapper mapper = new ObjectMapper(); + JsonNodeFactory nf = mapper.getNodeFactory(); + final int columnCount = rs.getMetaData().getColumnCount(); + try ( + var out = Files.newOutputStream(file); + var arrayWriter = mapper.writerWithDefaultPrettyPrinter().writeValuesAsArray(out) + ) { + while (rs.next()) { + ObjectNode obj = mapper.createObjectNode(); + for (int i = 1; i <= columnCount; i++) { + String label = rs.getMetaData().getColumnLabel(i); + int type = rs.getMetaData().getColumnType(i); + JsonNode valueNode = switch (type) { + case Types.INTEGER | Types.BIGINT -> nf.numberNode(rs.getLong(i)); + case Types.FLOAT | Types.DECIMAL -> nf.numberNode(rs.getDouble(i)); + case Types.NUMERIC -> nf.numberNode(rs.getBigDecimal(i)); + case Types.BOOLEAN -> nf.booleanNode(rs.getBoolean(i)); + default -> nf.textNode(rs.getString(i)); + }; + obj.set(label, valueNode); + } + arrayWriter.write(obj); + } + } + } + private void refreshSavedQueries() { savedQueriesVBox.getChildren().clear(); List savedQueries = Profile.getCurrent().dataSource() diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 5f7f3ba..19526b2 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -14,6 +14,7 @@ module com.andrewlalis.perfin { exports com.andrewlalis.perfin to javafx.graphics; exports com.andrewlalis.perfin.view to javafx.graphics; exports com.andrewlalis.perfin.model to javafx.graphics; + exports com.andrewlalis.perfin.data.util to javafx.graphics; opens com.andrewlalis.perfin.control to javafx.fxml; opens com.andrewlalis.perfin.view to javafx.fxml;