Added more documentation to PerfinApp.java, and added JSON export.

This commit is contained in:
Andrew Lalis 2024-07-20 17:55:37 -04:00
parent 6d720b9645
commit 71cc5b1612
3 changed files with 62 additions and 5 deletions

View File

@ -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<String> 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<String> 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<String> 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<String> 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<String> fontResources = List.of(
"/font/JetBrainsMono-2.304/fonts/ttf/JetBrainsMono-Regular.ttf",

View File

@ -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<String> savedQueries = Profile.getCurrent().dataSource()

View File

@ -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;