diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..cbab909 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,82 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +### Added + +### Changed + +### Fixed + +## [6.0.0] - 2024-01-14 + +### Added + +- Added PostgreSQL support. +- Added tools-scripts (bat/sh). +- Added license. + +### Changed + +- Removed datastore selection on startup. +- Removed editing date addition and added sorting of parameters by key in properties file. +- Made refactoring. + +### Fixed + +- Fixed several bugs. + +## [5.0.0] - around 2022-08-22 + +### Added + +- Added import data from file and export data to file for different formats : xml, yaml, json, csv, bser, ser. + +### Changed + +- Made refactoring using features from java 17. + +### Fixed + +- Fixed several bugs. + +## [4.0.0] - around 2022-04-10 + +### Changed + +- Made clean up, improved and refactored code. + +### Fixed + +- Fixed several bugs. + +## [3.0.0] - around 2021-07-24 + +### Changed + +- The operation logic of saving data has been changed: now saving data to database occurs after pressing the save button. + +### Fixed + +- Fixed several bugs. + +## [2.0.0] - around 2021-07-21 + +### Added + +- Added SQLite database file chooser. + +### Fixed + +- Fixed several bugs. + +## [1.0.0] - 2021-07-11 + +### Added + +- Added base version that works with one internal SQLite database file. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index e0d0d38..be55458 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,48 @@ -# Vehicle_accounting +[![Apache 2.0](https://img.shields.io/github/license/m1ra9e/gui-swing.svg)](http://www.apache.org/licenses/LICENSE-2.0) -## Version description +# Gui-swing + +GUI application for working with the vehicle database and the ability to import and export data to files. + +* Supported databases: SQLite, PostgreSQL. + +* Supported import and export formats: xml, yaml, jsom, csv, bser, ser. + +## Build + +Build requires Java (JDK) 17+ and Apache Maven 3.8+. + +```sh +git clone https://github.com/m1ra9e/gui-swing.git gui-swing +cd gui-swing +mvn clean package +``` + +## Run + +For run the application, execute [run.bat](tools/run.bat) on Windows or [run.sh](tools/run.sh) on Linux. + +## Test + +For test with local PostgreSQL fill [pg_test_settings.properties](src/test/resources/home/db/pg_test_settings.properties) by own data. + +For run tests, execute [test.bat](tools/test.bat) on Windows or [test.sh](tools/test.sh) on Linux. + +## Changelog + +[Changelog information](CHANGELOG.md) + + +### Short version description | version | description | | ------- | ----------- | -| 1.0.0 | work with one internal db | -| 2.0.0 | db chooser | -| 3.0.0 | save data after push button | -| 4.0.0 | improve and refactoring | +| 6.0.0 | added PostgreSQL, removed choose data store on start, date and sorting in properties-file, added tools-scripts, refactoring, added license | | 5.0.0 | java_17, import and export | +| 4.0.0 | improve and refactoring | +| 3.0.0 | save data after push button | +| 2.0.0 | db chooser | +| 1.0.0 | work with one internal db | ## Plans diff --git a/pom.xml b/pom.xml index b10e990..67d7452 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ home vehicle - 5.0.0 + 6.0.0 jar @@ -30,6 +30,11 @@ sqlite-jdbc 3.34.0 + + org.postgresql + postgresql + 42.3.4 + org.yaml snakeyaml @@ -58,7 +63,7 @@ org.junit.jupiter junit-jupiter - 5.8.1 + 5.9.3 test diff --git a/src/main/java/home/Const.java b/src/main/java/home/Const.java new file mode 100644 index 0000000..fec94f3 --- /dev/null +++ b/src/main/java/home/Const.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home; + +public final class Const { + + public static final String EMPTY_STRING = ""; + + private Const() { + } +} diff --git a/src/main/java/home/Data.java b/src/main/java/home/Data.java index e89b56e..564b37d 100644 --- a/src/main/java/home/Data.java +++ b/src/main/java/home/Data.java @@ -1,36 +1,63 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home; import java.io.IOException; import java.sql.SQLException; +import javax.swing.JOptionPane; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import home.db.DbInitializer; -import home.db.dao.DaoSQLite; +import home.db.conn.Connector; +import home.db.dao.Dao; +import home.db.init.DbInitializer; import home.gui.DataActionInGui; import home.gui.Gui; -import home.gui.component.CustomJFileChooserDb; -import home.gui.component.CustomJFileChooserDb.ChooserDbOperation; -import home.utils.ThreadUtil; +import home.gui.GuiConst; import home.utils.LogUtils; +import home.utils.ThreadUtil; +import home.utils.Utils; final class Data { private static final Logger LOG = LoggerFactory.getLogger(Data.class); static void initDb() { - if (Settings.hasPathToDbFile()) { + if (!Settings.hasDatabase()) { + return; + } + + int dialogContinuePreviousDbResult = JOptionPane.showConfirmDialog(null, + GuiConst.PREVIOUS_DATABASE_TEXT.formatted(Utils.generateDbDescription()), + GuiConst.PREVIOUS_DATABASE_TITLE, JOptionPane.YES_NO_OPTION); + if (dialogContinuePreviousDbResult == JOptionPane.YES_OPTION) { readDataFromDb(); - } else { - try { - CustomJFileChooserDb.createAndShowChooser(null, - ChooserDbOperation.CREATE_OR_OPEN); - readDataFromDb(); - Gui.INSTANCE.setDbLabel(Settings.getDbFilePath()); - } catch (IOException e) { - throw new IllegalStateException("Error while create/open DB file.", e); - } + return; + } + + try { + Connector.resetConnectionDataAndSettings(); + Gui.INSTANCE.setDbLabel(GuiConst.DATABASE_NOT_SELECTED); + JOptionPane.showMessageDialog(null, GuiConst.REMOVED_PREVIOUS_CONNECTION_TEXT, + GuiConst.REMOVED_PREVIOUS_CONNECTION_TITLE, JOptionPane.INFORMATION_MESSAGE); + } catch (IOException e) { + throw LogUtils.logAndCreateIllegalStateException( + "Error while removing connection to previous database.", LOG, e); } } @@ -39,7 +66,7 @@ private static void readDataFromDb() { Thread.currentThread().setName("-> read data from database"); try { DbInitializer.createTableIfNotExists(); - DataActionInGui.init(DaoSQLite.getInstance().readAll()); + DataActionInGui.init(Dao.readAll()); } catch (SQLException e) { String errorMsg = "Error while read data from database: " + e.getMessage(); LogUtils.logAndShowError(LOG, null, errorMsg, "Data reading error", e); diff --git a/src/main/java/home/IConsts.java b/src/main/java/home/IConsts.java deleted file mode 100644 index 8136e3e..0000000 --- a/src/main/java/home/IConsts.java +++ /dev/null @@ -1,6 +0,0 @@ -package home; - -public interface IConsts { - - String EMPTY_STRING = ""; -} diff --git a/src/main/java/home/Main.java b/src/main/java/home/Main.java index 6bb4327..5390c34 100644 --- a/src/main/java/home/Main.java +++ b/src/main/java/home/Main.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home; import java.lang.Thread.UncaughtExceptionHandler; @@ -45,7 +60,7 @@ private static void startApplication() { } private static void setUncaughtExceptionProcessing() { - UncaughtExceptionHandler handler = new UncaughtExceptionHandler() { + var handler = new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { LogUtils.logAndShowError(LOG, null, e.getMessage(), "Error", e); diff --git a/src/main/java/home/Settings.java b/src/main/java/home/Settings.java index 529bea7..c744fa0 100644 --- a/src/main/java/home/Settings.java +++ b/src/main/java/home/Settings.java @@ -1,21 +1,46 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; -import java.io.OutputStream; +import java.sql.SQLException; import java.util.Locale; -import java.util.Properties; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Function; +import home.db.DbType; import home.gui.ColorSchema; +import home.utils.CustomProperties; public final class Settings { public enum Setting { - STYLE("style", ColorSchema.CROSSPLATFORM.name().toLowerCase(Locale.ROOT)), - DB_FILE_PATH("db_file_path", IConsts.EMPTY_STRING); + AUTO_RESIZE_TABLE_WIDTH("gui.auto_resize_table_width", "false"), + DATABASE("db.database", Const.EMPTY_STRING), + DATABASE_TYPE("db.type", Const.EMPTY_STRING), + HOST("db.host", Const.EMPTY_STRING), + PASSWORD("db.password", Const.EMPTY_STRING), + PORT("db.port", Const.EMPTY_STRING), + USER("db.user", Const.EMPTY_STRING), + STYLE("gui.style", ColorSchema.CROSSPLATFORM.name().toLowerCase(Locale.ROOT)); private final String name; private final String defaultValue; @@ -34,49 +59,127 @@ public String getDefaultValue() { } } + public enum ResetType { + SET_DEFAULT, CLEAR; + } + private static final String SETTINGS_FILE_NAME = "settings.properties"; - private static final Properties SETTINGS = new Properties(); + private static final CustomProperties SETTINGS = new CustomProperties(); public static String getStyle() { return get(Setting.STYLE); } - public static String getDbFilePath() { - return get(Setting.DB_FILE_PATH); + public static boolean isAutoResizeTableWidth() { + return Boolean.parseBoolean(get(Setting.AUTO_RESIZE_TABLE_WIDTH)); + } + + public static DbType getDatabaseType() throws SQLException { + String databaseTypeStr = get(Setting.DATABASE_TYPE); + return DbType.getDbType(databaseTypeStr); } - public static boolean hasPathToDbFile() { - String dbFilePath = get(Setting.DB_FILE_PATH); - return dbFilePath != null && !dbFilePath.isBlank(); + public static String getHost() { + return get(Setting.HOST); + } + + public static int getPort() throws SQLException { + String port = get(Setting.PORT); + try { + return Integer.parseInt(port); + } catch (NumberFormatException e) { + throw new SQLException("Incorrect value of parameter %s: %s" + .formatted(Setting.PORT, port)); + } + } + + public static String getDatabase() { + return get(Setting.DATABASE); + } + + public static String getUser() { + return get(Setting.USER); + } + + public static String getPassword() { + return get(Setting.PASSWORD); + } + + public static boolean hasDatabase() { + String database = get(Setting.DATABASE); + return database != null && !database.isBlank(); } private static String get(Setting setting) { return SETTINGS.getProperty(setting.getName()); } - public static void writeSetting(Setting setting, String value) throws IOException { + public static void saveStyle(String style) throws IOException { + saveSetting(Setting.STYLE, style); + } + + public static void saveAutoResizeTableWidth(boolean isAutoResizeTableWidth) throws IOException { + saveSetting(Setting.AUTO_RESIZE_TABLE_WIDTH, String.valueOf(isAutoResizeTableWidth)); + } + + public static void saveDatabaseType(String databaseType) throws IOException { + saveSetting(Setting.DATABASE_TYPE, databaseType); + } + + public static void saveDatabase(String database) throws IOException { + saveSetting(Setting.DATABASE, database); + } + + private static void saveSetting(Setting setting, String value) throws IOException { SETTINGS.setProperty(setting.getName(), value); - try (OutputStream outputStream = new FileOutputStream(SETTINGS_FILE_NAME)) { - SETTINGS.store(outputStream, null); + writeSettings(); + readSettings(); + } + + public static void saveDbConnSettings(String host, String port, + String dbName, String user, String pass, String dbType) throws IOException { + saveSettings(Map.ofEntries( + Map.entry(Setting.HOST, host), + Map.entry(Setting.PORT, port), + Map.entry(Setting.DATABASE, dbName), + Map.entry(Setting.USER, user), + Map.entry(Setting.PASSWORD, pass), + Map.entry(Setting.DATABASE_TYPE, dbType))); + } + + public static void saveSettings(Map settingsMap) throws IOException { + for (Entry settingEntry : settingsMap.entrySet()) { + SETTINGS.setProperty(settingEntry.getKey().getName(), settingEntry.getValue()); + } + writeSettings(); + readSettings(); + } + + private static void writeSettings() { + try (var outputStream = new FileOutputStream(SETTINGS_FILE_NAME)) { + SETTINGS.store(outputStream); } catch (IOException e) { throw new IllegalStateException("Error while filling the settings file: " + SETTINGS_FILE_NAME, e); } - readSettings(); } public static void readSettings() { - try (var inputStream = new FileInputStream(getSettingsPath())) { + readSettings(SETTINGS_FILE_NAME); + } + + public static void readSettings(String settingsFileName) { + try (var inputStream = new FileInputStream(getSettingsPath(settingsFileName))) { SETTINGS.load(inputStream); } catch (IOException e) { throw new IllegalStateException("Error while reading settings from file: " - + SETTINGS_FILE_NAME, e); + + settingsFileName, e); } } - private static String getSettingsPath() { + private static String getSettingsPath(String settingsFileName) { try { - File file = new File(SETTINGS_FILE_NAME); + var file = new File(settingsFileName); if (!file.exists()) { file.createNewFile(); fillWithDefaultSettings(); @@ -84,19 +187,40 @@ private static String getSettingsPath() { return file.getAbsolutePath(); } catch (IOException e) { throw new IllegalStateException("Error while creating the settings file: " - + SETTINGS_FILE_NAME, e); + + settingsFileName, e); } } private static void fillWithDefaultSettings() { - try (OutputStream outputStream = new FileOutputStream(SETTINGS_FILE_NAME)) { - SETTINGS.setProperty(Setting.STYLE.getName(), Setting.STYLE.getDefaultValue()); - SETTINGS.setProperty(Setting.DB_FILE_PATH.getName(), Setting.DB_FILE_PATH.getDefaultValue()); - SETTINGS.store(outputStream, null); - } catch (IOException e) { - throw new IllegalStateException("Error while filling the settings file: " - + SETTINGS_FILE_NAME, e); + for (Setting setting : Setting.values()) { + SETTINGS.setProperty(setting.getName(), setting.getDefaultValue()); + } + writeSettings(); + } + + // clearing DB settings is used only if the user wants to work + // with a new DB when the application starts + public static void resetDbSettings(ResetType resetType) throws IOException { + String styleValue = getStyle(); + String isAutoResizeTableWidthValue = get(Setting.AUTO_RESIZE_TABLE_WIDTH); + + Function getValueFnc = getSettingValueFunction(resetType); + for (Setting setting : Setting.values()) { + SETTINGS.setProperty(setting.getName(), getValueFnc.apply(setting)); } + + SETTINGS.setProperty(Setting.STYLE.getName(), styleValue); + SETTINGS.setProperty(Setting.AUTO_RESIZE_TABLE_WIDTH.getName(), isAutoResizeTableWidthValue); + + writeSettings(); + readSettings(); + } + + private static Function getSettingValueFunction(ResetType resetType) { + return switch (resetType) { + case SET_DEFAULT -> setting -> setting.getDefaultValue(); + case CLEAR -> setting -> Const.EMPTY_STRING; + }; } private Settings() { diff --git a/src/main/java/home/Storage.java b/src/main/java/home/Storage.java index aab320b..b649fbc 100644 --- a/src/main/java/home/Storage.java +++ b/src/main/java/home/Storage.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home; import java.util.HashSet; diff --git a/src/main/java/home/db/Connector.java b/src/main/java/home/db/Connector.java deleted file mode 100644 index 9ed2779..0000000 --- a/src/main/java/home/db/Connector.java +++ /dev/null @@ -1,81 +0,0 @@ - -package home.db; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; -import java.util.Properties; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import home.Settings; -import home.utils.LogUtils; - -public final class Connector { - - private static final Logger LOG = LoggerFactory.getLogger(Connector.class); - - private static final String QUERY_TIMEOUT = "30"; - - private static final String JDBC_DRIVER_POSTGRESQL = "org.postgresql.Driver"; - private static final String URL_POSTGRESQL = "jdbc:postgresql://%s:%s/%s"; - - private static final String JDBC_DRIVER_SQLITE = "org.sqlite.JDBC"; - private static final String URL_SQLITE = "jdbc:sqlite:%s"; - - // TODO add PostgreSQL - public static Connection getConnectionToPostgreSQL(String host, String port, - String dbName, String user, String password) throws SQLException { - String url = generatePostgreSqlURL(host, port, dbName); - - Properties props = new Properties(); - props.setProperty("user", user); - props.setProperty("password", password); - props.setProperty("reWriteBatchedInserts", "true"); - props.setProperty("loginTimeout", QUERY_TIMEOUT); - props.setProperty("connectTimeout", QUERY_TIMEOUT); - props.setProperty("cancelSignalTimeout", QUERY_TIMEOUT); - props.setProperty("socketTimeout", QUERY_TIMEOUT); - - return getConnection(url, props, JDBC_DRIVER_POSTGRESQL); - } - - private static String generatePostgreSqlURL(String host, String port, String dbName) { - String db; - try { - db = URLEncoder.encode(dbName, "UTF-8"); - } catch (UnsupportedEncodingException ex) { - LOG.error("Encoding error of database name.", ex); - db = dbName; - } - return String.format(URL_POSTGRESQL, host, port, db); - } - - public static Connection getConnectionToSQLite() throws SQLException { - return getConnection(String.format(URL_SQLITE, Settings.getDbFilePath()), - new Properties(), JDBC_DRIVER_SQLITE); - } - - private static Connection getConnection(String url, Properties props, String jdbcDriver) - throws SQLException { - try { - Class.forName(jdbcDriver); - - // Driver driver = (Driver) - // Class.forName(jdbcDriver).newInstance(); - // DriverManager.registerDriver(driver); - - return DriverManager.getConnection(url, props); - } catch (ClassNotFoundException e) { - throw LogUtils.logAndCreateSqlException("Database driver class not found.", LOG, e); - } catch (SQLException e) { - throw LogUtils.logAndCreateSqlException("Error while connecting to the database.", LOG, e); - } - } - - private Connector() { - } -} diff --git a/src/main/java/home/db/DbInitializer.java b/src/main/java/home/db/DbInitializer.java deleted file mode 100644 index 6da5858..0000000 --- a/src/main/java/home/db/DbInitializer.java +++ /dev/null @@ -1,50 +0,0 @@ -package home.db; - -import java.io.File; -import java.io.IOException; -import java.sql.SQLException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import home.Settings; -import home.Settings.Setting; - -public final class DbInitializer { - - private static final Logger LOG = LoggerFactory.getLogger(DbInitializer.class); - - private static final String CREATE_TBL_QUERY = """ - CREATE TABLE IF NOT EXISTS vehicle ( - 'id' INTEGER PRIMARY KEY AUTOINCREMENT, - 'type' TEXT, - 'color' TEXT, - 'number' TEXT, - 'is_transports_cargo' INTEGER, - 'is_transports_passengers' INTEGER, - 'has_trailer' INTEGER, - 'has_cradle' INTEGER, - 'date_time' INTEGER);"""; - - public static void createDbFileIfNotExists(File file) throws IOException { - try { - if (!file.exists()) { - file.createNewFile(); - } - Settings.writeSetting(Setting.DB_FILE_PATH, file.getAbsolutePath()); - } catch (IOException e) { - LOG.error("Error while creating the database file.", e); - throw new IOException("Error while creating the database file.", e); - } - } - - public static void createTableIfNotExists() throws SQLException { - try (var conn = Connector.getConnectionToSQLite(); - var stmt = conn.createStatement()) { - stmt.execute(CREATE_TBL_QUERY); - } - } - - private DbInitializer() { - } -} diff --git a/src/main/java/home/db/DbType.java b/src/main/java/home/db/DbType.java new file mode 100644 index 0000000..580f283 --- /dev/null +++ b/src/main/java/home/db/DbType.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +import java.sql.SQLException; + +public enum DbType { + + // TODO add support for MS SQL Server + // MS_SQL_Server("com.microsoft.sqlserver.jdbc.SQLServerDriver", + // "jdbc:sqlserver://%s:%d;DatabaseName=%s"), + PostgreSQL("org.postgresql.Driver", "jdbc:postgresql://%s:%d/%s"), + SQLite("org.sqlite.JDBC", "jdbc:sqlite:%s"); + + private final String jdbcDriver; + private final String url; + + private DbType(String jdbcDriver, String url) { + this.jdbcDriver = jdbcDriver; + this.url = url; + } + + public String getJdbcDriver() { + return jdbcDriver; + } + + /** + * Gets the URL of the database + * + *
+     * {@code
+     * DbType.PostgreSQL.getUrl("host", "port", "db");
+     * DbType.SQLite.getUrl("db");
+     * }
+     * 
+ * + * @param args database url parameters + * @return database url + */ + public String getUrl(Object... args) { + return url.formatted(args); + } + + public boolean in(DbType... dbTypes) { + for (var dbType : dbTypes) { + if (this == dbType) { + return true; + } + } + return false; + } + + public static DbType getDbType(String type) throws SQLException { + String typeFormatted = type.strip(); + for (DbType dbType : DbType.values()) { + if (typeFormatted.equalsIgnoreCase(dbType.name())) { + return dbType; + } + } + + throw new SQLException("Unsupported database type : " + type); + } +} diff --git a/src/main/java/home/db/conn/AbstractConnectionDataHelper.java b/src/main/java/home/db/conn/AbstractConnectionDataHelper.java new file mode 100644 index 0000000..3b3eb23 --- /dev/null +++ b/src/main/java/home/db/conn/AbstractConnectionDataHelper.java @@ -0,0 +1,36 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.conn; + +import java.util.Properties; + +import home.db.DbType; + +abstract sealed class AbstractConnectionDataHelper permits PgConnectionDataHelper, SQLiteConnectionDataHelper { + + ConnectionData getConnectionData() { + String url = getUrl(); + Properties connProps = getConnectionProps(); + DbType dbType = getDbType(); + return new ConnectionData(url, connProps, dbType.getJdbcDriver()); + } + + protected abstract DbType getDbType(); + + protected abstract String getUrl(); + + protected abstract Properties getConnectionProps(); +} diff --git a/src/main/java/home/db/conn/ConnectionData.java b/src/main/java/home/db/conn/ConnectionData.java new file mode 100644 index 0000000..a8469cd --- /dev/null +++ b/src/main/java/home/db/conn/ConnectionData.java @@ -0,0 +1,21 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.conn; + +import java.util.Properties; + +record ConnectionData(String url, Properties connProps, String jdbcDriver) { +} diff --git a/src/main/java/home/db/conn/Connector.java b/src/main/java/home/db/conn/Connector.java new file mode 100644 index 0000000..694f92b --- /dev/null +++ b/src/main/java/home/db/conn/Connector.java @@ -0,0 +1,116 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.conn; + +import java.io.IOException; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import home.Settings; +import home.Settings.ResetType; +import home.db.DbType; +import home.gui.GuiConst; +import home.utils.LogUtils; + +public final class Connector { + + private static final Logger LOG = LoggerFactory.getLogger(Connector.class); + + private static ConnectionData connectionData; + + // resetting the connection settings cache is used only when changing the DB + public static void resetConnectionDataAndSettings() throws IOException { + Settings.resetDbSettings(ResetType.CLEAR); + connectionData = null; + } + + public static boolean testConnection(String host, int port, String dbName, + String user, String pass, DbType dbType) throws SQLException { + if (dbType == DbType.PostgreSQL) { + ConnectionData connectionData = new PgConnectionDataHelper(host, port, dbName, user, pass) + .getConnectionData(); + return testConnection(connectionData); + } + + throw new SQLException(GuiConst.CONNECTION_TEST_SUPPORT_ERROR_TEXT.formatted(dbType)); + } + + public static boolean testCurrentConnection() throws SQLException { + ConnectionData connData = getConnectionData(); + return testConnection(connData); + } + + private static boolean testConnection(ConnectionData connData) throws SQLException { + try (Connection conn = getConnection(connData)) { + return true; + } catch (SQLException e) { + throw new SQLException(GuiConst.CONNECTION_TEST_ERROR_TEXT.formatted(e.getMessage()), e); + } + } + + public static Connection getConnection() throws SQLException { + ConnectionData connData = getConnectionData(); + return getConnection(connData); + } + + private static ConnectionData getConnectionData() throws SQLException { + if (connectionData != null) { + return connectionData; + } + + DbType dbType = Settings.getDatabaseType(); + + AbstractConnectionDataHelper connectionDataHelper = switch (dbType) { + case PostgreSQL -> new PgConnectionDataHelper(Settings.getHost(), + Settings.getPort(), Settings.getDatabase(), + Settings.getUser(), Settings.getPassword()); + case SQLite -> new SQLiteConnectionDataHelper(Settings.getDatabase()); + }; + + connectionData = connectionDataHelper.getConnectionData(); + + return connectionData; + } + + private static Connection getConnection(ConnectionData connData) throws SQLException { + return getConnection(connData.url(), connData.connProps(), connData.jdbcDriver()); + } + + private static Connection getConnection(String url, Properties props, String jdbcDriver) + throws SQLException { + try { + Class.forName(jdbcDriver); + + // Driver driver = (Driver) Class.forName(jdbcDriver).newInstance(); + // DriverManager.registerDriver(driver); + + return DriverManager.getConnection(url, props); + } catch (ClassNotFoundException e) { + throw LogUtils.logAndCreateSqlException("Database driver class not found.", LOG, e); + } catch (SQLException e) { + throw LogUtils.logAndCreateSqlException("Error while connecting to the database. %s" + .formatted(e.getMessage()), LOG, e); + } + } + + private Connector() { + } +} diff --git a/src/main/java/home/db/conn/MsConnectionDataHelper.java b/src/main/java/home/db/conn/MsConnectionDataHelper.java new file mode 100644 index 0000000..5a0c1b9 --- /dev/null +++ b/src/main/java/home/db/conn/MsConnectionDataHelper.java @@ -0,0 +1,64 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.conn; + +import java.util.Properties; + +import home.db.DbType; + +// TODO add support for MS SQL Server +final class MsConnectionDataHelper { // extends AbstractConnectionDataHelper + + private static String QUERY_TIMEOUT = "15"; // sec + private static String LOCK_TIMEOUT = "10000"; // ms + + private final String host; + private final int port; // default port usually is 1433 + private final String dbName; + private final String user; + private final String pass; + + MsConnectionDataHelper(String host, int port, String dbName, + String user, String pass) { + this.host = host; + this.port = port; + this.dbName = dbName; + this.user = user; + this.pass = pass; + } + + // @Override + protected DbType getDbType() { + // DbType.MS_SQL_Server + return null; + } + + // @Override + protected String getUrl() { + return getDbType().getUrl(host, port, dbName); + } + + // @Override + protected Properties getConnectionProps() { + var props = new Properties(); + props.setProperty("user", user); + props.setProperty("password", pass); + props.setProperty("trustServerCertificate", "true"); + props.setProperty("queryTimeout", QUERY_TIMEOUT); + props.setProperty("lockTimeout", LOCK_TIMEOUT); + return props; + } +} diff --git a/src/main/java/home/db/conn/PgConnectionDataHelper.java b/src/main/java/home/db/conn/PgConnectionDataHelper.java new file mode 100644 index 0000000..5efc7af --- /dev/null +++ b/src/main/java/home/db/conn/PgConnectionDataHelper.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.conn; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import home.db.DbType; + +final class PgConnectionDataHelper extends AbstractConnectionDataHelper { + + private static final Logger LOG = LoggerFactory.getLogger(PgConnectionDataHelper.class); + + private static final String TIMEOUT = "30"; // sec + private static String QUERY_TIMEOUT = "15000"; // ms // without final-modifier because of test + private static String LOCK_TIMEOUT = "10000"; // ms // without final-modifier because of test + + private final String host; + private final int port; // default port usually is 5432 + private final String dbName; + private final String user; + private final String pass; + + PgConnectionDataHelper(String host, int port, String dbName, + String user, String pass) { + this.host = host; + this.port = port; + this.dbName = dbName; + this.user = user; + this.pass = pass; + } + + @Override + protected DbType getDbType() { + return DbType.PostgreSQL; + } + + @Override + protected String getUrl() { + String db; + try { + db = URLEncoder.encode(dbName, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException ex) { + LOG.error("Encoding error of database name.", ex); + db = dbName; + } + return getDbType().getUrl(host, port, db); + } + + @Override + protected Properties getConnectionProps() { + var props = new Properties(); + props.setProperty("user", user); + props.setProperty("password", pass); + props.setProperty("reWriteBatchedInserts", "true"); + props.setProperty("loginTimeout", TIMEOUT); + props.setProperty("connectTimeout", TIMEOUT); + props.setProperty("cancelSignalTimeout", TIMEOUT); + props.setProperty("socketTimeout", TIMEOUT); + props.setProperty("options", "-c statement_timeout=%s -c lock_timeout=%s" + .formatted(QUERY_TIMEOUT, LOCK_TIMEOUT)); + return props; + } +} diff --git a/src/main/java/home/db/conn/SQLiteConnectionDataHelper.java b/src/main/java/home/db/conn/SQLiteConnectionDataHelper.java new file mode 100644 index 0000000..98b97e2 --- /dev/null +++ b/src/main/java/home/db/conn/SQLiteConnectionDataHelper.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.conn; + +import java.util.Properties; + +import home.db.DbType; + +final class SQLiteConnectionDataHelper extends AbstractConnectionDataHelper { + + private final String dbName; + + SQLiteConnectionDataHelper(String dbName) { + this.dbName = dbName; + } + + @Override + protected DbType getDbType() { + return DbType.SQLite; + } + + @Override + protected String getUrl() { + return getDbType().getUrl(dbName); + } + + @Override + protected Properties getConnectionProps() { + return new Properties(); + } +} diff --git a/src/main/java/home/db/dao/AbstractDao.java b/src/main/java/home/db/dao/AbstractDao.java index 3f0f51e..9f3d140 100644 --- a/src/main/java/home/db/dao/AbstractDao.java +++ b/src/main/java/home/db/dao/AbstractDao.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.db.dao; import java.sql.Connection; @@ -14,50 +29,69 @@ import java.util.stream.Stream; import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import home.IConsts; +import home.Const; import home.Storage; +import home.db.conn.Connector; import home.model.AbstractVehicle; import home.model.Car; import home.model.Motorcycle; import home.model.Truck; import home.model.VehicleType; import home.utils.LogUtils; - -abstract sealed class AbstractDao implements IDao permits DaoSQLite { - - private static final String SELECT_ALL = "SELECT * FROM vehicle;"; - - private static final String SELECT_ONE = "SELECT * FROM vehicle WHERE id = ?;"; - - private static final String INSERT = """ - INSERT INTO vehicle - ('type', 'color', 'number', 'date_time', 'is_transports_cargo', - 'is_transports_passengers', 'has_trailer', 'has_cradle') - VALUES (?, ?, ?, ?, ?, ?, ?, ?);"""; - - private static final String UPDATE = """ - UPDATE vehicle SET - type = ?, color = ?, number = ?, date_time = ?, is_transports_cargo = ?, - is_transports_passengers = ?, has_trailer = ?, has_cradle = ? - WHERE id = ?;"""; - - private static final String DELETE = "DELETE FROM vehicle WHERE id in (%s);"; - +import home.utils.NamedFormatter; + +abstract sealed class AbstractDao implements IDao permits PgDao, SQLiteDao { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractDao.class); + + private static final String SELECT_ALL = NamedFormatter + .format("SELECT * FROM ${table_name}", + DaoConst.PLACEHOLDER_VALUES); + + private static final String SELECT_ONE = NamedFormatter + .format("SELECT * FROM ${table_name} WHERE ${col_id} = ?", + DaoConst.PLACEHOLDER_VALUES); + + // !!! the order of the columns in the INSERT query must be the same as in the + // UPDATE query because they both use the fillStmtByDataFromObj method. + private static final String INSERT = NamedFormatter.format(""" + INSERT INTO ${table_name} + (${col_type}, ${col_color}, ${col_number}, ${col_date_time}, + ${col_is_transports_cargo}, ${col_is_transports_passengers}, + ${col_has_trailer}, ${col_has_cradle}) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """, DaoConst.PLACEHOLDER_VALUES); + + // !!! the order of the columns in the UPDATE query must be the same as in the + // INSERT query because they both use the fillStmtByDataFromObj method. + private static final String UPDATE = NamedFormatter.format(""" + UPDATE ${table_name} SET + ${col_type} = ?, ${col_color} = ?, ${col_number} = ?, ${col_date_time} = ?, + ${col_is_transports_cargo} = ?, ${col_is_transports_passengers} = ?, + ${col_has_trailer} = ?, ${col_has_cradle} = ? + WHERE ${col_id} = ? + """, DaoConst.PLACEHOLDER_VALUES); + + private static final String DELETE = NamedFormatter + .format("DELETE FROM ${table_name} WHERE ${col_id} ", + DaoConst.PLACEHOLDER_VALUES) + + "IN (%s)"; + + private static final int BATCH_SIZE = 1_000; + + // https://www.ibm.com/docs/en/db2woc?topic=messages-sqlstate + // 08 - Connection Exception private static final String CONNECTION_ERROR_CODE = "08"; - protected AbstractDao() { - } - - protected abstract Connection getConnection() throws SQLException; + private static final int FALSE_VALUE_FOR_DB = 0; protected abstract int getTransactionIsolation(); - protected abstract Logger getLogger(); - @Override public AbstractVehicle readOne(long id) throws SQLException { - try (var conn = getConnection()) { + try (var conn = Connector.getConnection()) { conn.setTransactionIsolation(getTransactionIsolation()); try (var pstmt = conn.prepareStatement(SELECT_ONE)) { @@ -82,7 +116,7 @@ public AbstractVehicle readOne(long id) throws SQLException { @Override public List readAll() throws SQLException { - try (var conn = getConnection()) { + try (var conn = Connector.getConnection()) { conn.setTransactionIsolation(getTransactionIsolation()); try (var stmt = conn.createStatement(); var res = stmt.executeQuery(SELECT_ALL)) { @@ -96,40 +130,46 @@ public List readAll() throws SQLException { } private AbstractVehicle convertResultToDataObj(ResultSet res) throws SQLException { - String type = res.getString(IDaoConsts.TYPE); - VehicleType vehicleType = VehicleType.getVehicleType(type); - if (vehicleType == null) { - throw new SQLException("Wrong vehicle type received : " + type); - } + String type = res.getString(DaoConst.TYPE); + + VehicleType vehicleType = getVehicleTypeOrThrowSqlException(type); AbstractVehicle vehicle = switch (vehicleType) { case CAR -> { var car = new Car(); - car.setTransportsPassengers(convertToBoolean(res.getInt(IDaoConsts.IS_TRANSPORTS_PASSENGERS))); - car.setHasTrailer(convertToBoolean(res.getInt(IDaoConsts.HAS_TRAILER))); + car.setTransportsPassengers(convertToBoolean(res.getInt(DaoConst.IS_TRANSPORTS_PASSENGERS))); + car.setHasTrailer(convertToBoolean(res.getInt(DaoConst.HAS_TRAILER))); yield car; } case TRUCK -> { var truck = new Truck(); - truck.setTransportsCargo(convertToBoolean(res.getInt(IDaoConsts.IS_TRANSPORTS_CARGO))); - truck.setHasTrailer(convertToBoolean(res.getInt(IDaoConsts.HAS_TRAILER))); + truck.setTransportsCargo(convertToBoolean(res.getInt(DaoConst.IS_TRANSPORTS_CARGO))); + truck.setHasTrailer(convertToBoolean(res.getInt(DaoConst.HAS_TRAILER))); yield truck; } case MOTORCYCLE -> { var motorcycle = new Motorcycle(); - motorcycle.setHasCradle(convertToBoolean(res.getInt(IDaoConsts.HAS_CRADLE))); + motorcycle.setHasCradle(convertToBoolean(res.getInt(DaoConst.HAS_CRADLE))); yield motorcycle; } }; - vehicle.setId(res.getLong(IDaoConsts.ID)); - vehicle.setColor(res.getString(IDaoConsts.COLOR)); - vehicle.setNumber(res.getString(IDaoConsts.NUMBER)); - vehicle.setDateTime(res.getLong(IDaoConsts.DATE_TIME)); + vehicle.setId(res.getLong(DaoConst.ID)); + vehicle.setColor(res.getString(DaoConst.COLOR)); + vehicle.setNumber(res.getString(DaoConst.NUMBER)); + vehicle.setDateTime(res.getLong(DaoConst.DATE_TIME)); return vehicle; } + private VehicleType getVehicleTypeOrThrowSqlException(String type) throws SQLException { + try { + return VehicleType.getVehicleType(type); + } catch (IllegalArgumentException e) { + throw new SQLException(e.getMessage(), e); + } + } + private boolean convertToBoolean(int intBoolean) throws SQLException { return switch (intBoolean) { case 0 -> false; @@ -143,6 +183,7 @@ private boolean convertToBoolean(int intBoolean) throws SQLException { public void saveAllChanges() throws SQLException { var exceptions = new ArrayList(); + // delete operations try { Long[] idsForDel = Storage.INSTANCE.getIdsForDelete(); if (idsForDel.length > 0) { @@ -152,6 +193,7 @@ public void saveAllChanges() throws SQLException { exceptions.add(new SQLException("DELETE operation error.", e)); } + // update operations try { Set idsForUpdate = Storage.INSTANCE.getIdsForUpdate(); operation(this::update, dataObj -> dataObj.getId() > 0 @@ -160,6 +202,7 @@ public void saveAllChanges() throws SQLException { exceptions.add(new SQLException("UPDATE operation error.", e)); } + // insert operations try { operation(this::insert, dataObj -> dataObj.getId() == 0); } catch (IllegalStateException e) { @@ -203,7 +246,7 @@ private void sqlOperationBatch(boolean isUpdateOperation, List dataObjs, String errorMsg) { String sql = isUpdateOperation ? UPDATE : INSERT; - try (var conn = getConnection()) { + try (var conn = Connector.getConnection()) { conn.setAutoCommit(false); conn.setTransactionIsolation(getTransactionIsolation()); try (var pstmt = conn.prepareStatement(sql)) { @@ -214,21 +257,22 @@ private void sqlOperationBatch(boolean isUpdateOperation, pstmt.addBatch(); operationsCount++; - // Execute every 1_000 items. - if (operationsCount % 1_000 == 0 || operationsCount == dataObjs.size()) { + // Execute every BATCH_SIZE items. + if (operationsCount % BATCH_SIZE == 0 || operationsCount == dataObjs.size()) { checkBatchExecution(pstmt.executeBatch(), - String.format(errorMsg, dataObj), getLogger()); + errorMsg.formatted(dataObj), LOG); conn.commit(); } } - conn.setAutoCommit(true); } catch (SQLException e) { - String error = String.format(errorMsg, IConsts.EMPTY_STRING); + String error = errorMsg.formatted(Const.EMPTY_STRING); checkConnectionState(e, error); rollbackAndLog(conn, e, error); sqlOperationOneByOne(conn, sql, dataObjs, isUpdateOperation, error); + } finally { + conn.setAutoCommit(true); } } catch (SQLException e) { String operationType = isUpdateOperation ? "UPDATE" : "INSERT"; @@ -265,12 +309,12 @@ void checkBatchExecution(int[] batchResults, String errorMsg, Logger log) } private void rollbackAndLog(Connection conn, Exception e, String errorMsg) { - getLogger().error(errorMsg, e); + LOG.error(errorMsg, e); try { conn.rollback(); } catch (SQLException ex) { throw LogUtils.logAndCreateIllegalStateException( - errorMsg + " Sql rollback error.", getLogger(), e); + errorMsg + " Sql rollback error.", LOG, e); } } @@ -278,7 +322,7 @@ private void checkConnectionState(SQLException e, String errorMsg) throws SQLExc String sqlState = e.getSQLState(); if (sqlState.startsWith(CONNECTION_ERROR_CODE)) { throw LogUtils.logAndCreateSqlException( - "%s:\nConnection error (code %s)".formatted(errorMsg, sqlState), getLogger(), e); + "%s:\nConnection error (code %s)".formatted(errorMsg, sqlState), LOG, e); } } @@ -307,7 +351,7 @@ private void sqlOperationOneByOne(Connection conn, String sql, sb.append(errorMsg).append(" Can't ").append(operationType) .append(":\n").append(String.join("\n", errorsWithDataObjs)); - throw LogUtils.logAndCreateSqlException(sb.toString(), getLogger(), mainExeption); + throw LogUtils.logAndCreateSqlException(sb.toString(), LOG, mainExeption); } } @@ -323,17 +367,24 @@ private void fillStmtByDataFromObj(PreparedStatement pstmt, AbstractVehicle data switch (dataObjType) { case CAR: Car car = (Car) dataObj; + pstmt.setInt(5, FALSE_VALUE_FOR_DB); pstmt.setInt(6, convertToInt(car.isTransportsPassengers())); pstmt.setInt(7, convertToInt(car.hasTrailer())); + pstmt.setInt(8, FALSE_VALUE_FOR_DB); break; case TRUCK: Truck truck = (Truck) dataObj; pstmt.setInt(5, convertToInt(truck.isTransportsCargo())); + pstmt.setInt(6, FALSE_VALUE_FOR_DB); pstmt.setInt(7, convertToInt(truck.hasTrailer())); + pstmt.setInt(8, FALSE_VALUE_FOR_DB); break; case MOTORCYCLE: + pstmt.setInt(5, FALSE_VALUE_FOR_DB); + pstmt.setInt(6, FALSE_VALUE_FOR_DB); + pstmt.setInt(7, FALSE_VALUE_FOR_DB); pstmt.setInt(8, convertToInt(((Motorcycle) dataObj).hasCradle())); break; } @@ -349,8 +400,8 @@ private int convertToInt(boolean booleaVal) { private void delete(Long[] ids) { String idsStr = Stream.of(ids).map(String::valueOf).collect(Collectors.joining(",")); - String sql = String.format(DELETE, idsStr); - try (var conn = getConnection()) { + String sql = DELETE.formatted(idsStr); + try (var conn = Connector.getConnection()) { conn.setAutoCommit(false); conn.setTransactionIsolation(getTransactionIsolation()); try (var pstmt = conn.prepareStatement(sql)) { @@ -359,7 +410,6 @@ private void delete(Long[] ids) { + "\n Id list for delete: " + idsStr); } conn.commit(); - conn.setAutoCommit(true); } catch (SQLException e) { String errorMsg = "Error while removal several rows in one query"; @@ -367,6 +417,8 @@ private void delete(Long[] ids) { rollbackAndLog(conn, e, errorMsg); deleteOneByOne(conn, ids); + } finally { + conn.setAutoCommit(true); } } catch (SQLException e) { throw new IllegalStateException("Sql DELETE operation error : ", e); @@ -378,13 +430,13 @@ private void deleteOneByOne(Connection conn, Long[] ids) throws SQLException { var errorsWithIds = new ArrayList(); conn.setAutoCommit(true); - String sql = String.format(DELETE, "?"); + String sql = DELETE.formatted("?"); for (Long id : ids) { try (var pstmt = conn.prepareStatement(sql)) { pstmt.setLong(1, id); if (pstmt.executeUpdate() <= 0) { - errorsWithIds.add(id + IConsts.EMPTY_STRING); + errorsWithIds.add(id + Const.EMPTY_STRING); } } catch (SQLException e) { mainExeption = addException(mainExeption, e, @@ -399,7 +451,7 @@ private void deleteOneByOne(Connection conn, Long[] ids) throws SQLException { .append("\nCan't delete objects with ids:\n") .append(String.join("\n", errorsWithIds)); - throw LogUtils.logAndCreateSqlException(sb.toString(), getLogger(), mainExeption); + throw LogUtils.logAndCreateSqlException(sb.toString(), LOG, mainExeption); } } diff --git a/src/main/java/home/db/dao/Dao.java b/src/main/java/home/db/dao/Dao.java new file mode 100644 index 0000000..f7360a3 --- /dev/null +++ b/src/main/java/home/db/dao/Dao.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.dao; + +import java.sql.SQLException; +import java.util.List; + +import home.Settings; +import home.db.DbType; +import home.model.AbstractVehicle; + +public final class Dao { + + private static IDao dao; + private static DbType previousDbType; + + public static List readAll() throws SQLException { + return getDao().readAll(); + } + + @Deprecated(forRemoval = true) // because it uses only in test + public static AbstractVehicle readOne(long id) throws SQLException { + return getDao().readOne(id); + } + + public static void saveAllChanges() throws SQLException { + getDao().saveAllChanges(); + } + + public static void saveAs() throws SQLException { + getDao().saveAs(); + } + + private static IDao getDao() throws SQLException { + DbType currentDbType = Settings.getDatabaseType(); + + if (dao != null && previousDbType == currentDbType) { + return dao; + } + + dao = switch (currentDbType) { + case SQLite -> new SQLiteDao(); + case PostgreSQL -> new PgDao(); + }; + previousDbType = currentDbType; + + return dao; + } + + private Dao() { + } +} diff --git a/src/main/java/home/db/dao/DaoConst.java b/src/main/java/home/db/dao/DaoConst.java new file mode 100644 index 0000000..39b20c9 --- /dev/null +++ b/src/main/java/home/db/dao/DaoConst.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.dao; + +import java.util.Map; + +final class DaoConst { + + // Table name + private static final String DATA_TABLE = "vehicle"; + + // The names of the columns in the table + static final String ID = "id"; + static final String TYPE = "type"; + static final String COLOR = "color"; + static final String NUMBER = "number"; + static final String DATE_TIME = "date_time"; + static final String IS_TRANSPORTS_CARGO = "is_transports_cargo"; + static final String IS_TRANSPORTS_PASSENGERS = "is_transports_passengers"; + static final String HAS_TRAILER = "has_trailer"; + static final String HAS_CRADLE = "has_cradle"; + + // Map with values of placeholders for using in NamedFormatter + // (key - is placeholder, value - is value for placeholder) + static final Map PLACEHOLDER_VALUES = Map.ofEntries( + // Table name + Map.entry("table_name", DATA_TABLE), + // The names of the columns in the table + Map.entry("col_id", ID), + Map.entry("col_type", TYPE), + Map.entry("col_color", COLOR), + Map.entry("col_number", NUMBER), + Map.entry("col_date_time", DATE_TIME), + Map.entry("col_is_transports_cargo", IS_TRANSPORTS_CARGO), + Map.entry("col_is_transports_passengers", IS_TRANSPORTS_PASSENGERS), + Map.entry("col_has_trailer", HAS_TRAILER), + Map.entry("col_has_cradle", HAS_CRADLE)); + + private DaoConst() { + } +} diff --git a/src/main/java/home/db/dao/DaoSQLite.java b/src/main/java/home/db/dao/DaoSQLite.java deleted file mode 100644 index e544d6b..0000000 --- a/src/main/java/home/db/dao/DaoSQLite.java +++ /dev/null @@ -1,41 +0,0 @@ -package home.db.dao; - -import java.sql.Connection; -import java.sql.SQLException; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import home.db.Connector; - -public final class DaoSQLite extends AbstractDao { - - private static final Logger LOG = LoggerFactory.getLogger(DaoSQLite.class); - - private static IDao instance; - - private DaoSQLite() { - } - - public static IDao getInstance() { - if (instance == null) { - instance = new DaoSQLite(); - } - return instance; - } - - @Override - protected Connection getConnection() throws SQLException { - return Connector.getConnectionToSQLite(); - } - - @Override - protected int getTransactionIsolation() { - return Connection.TRANSACTION_SERIALIZABLE; - } - - @Override - protected Logger getLogger() { - return LOG; - } -} diff --git a/src/main/java/home/db/dao/IDao.java b/src/main/java/home/db/dao/IDao.java index 587d0ea..9e16214 100644 --- a/src/main/java/home/db/dao/IDao.java +++ b/src/main/java/home/db/dao/IDao.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.db.dao; import java.sql.SQLException; @@ -5,7 +20,7 @@ import home.model.AbstractVehicle; -public sealed interface IDao permits AbstractDao { +sealed interface IDao permits AbstractDao { List readAll() throws SQLException; diff --git a/src/main/java/home/db/dao/IDaoConsts.java b/src/main/java/home/db/dao/IDaoConsts.java deleted file mode 100644 index 3fba12b..0000000 --- a/src/main/java/home/db/dao/IDaoConsts.java +++ /dev/null @@ -1,22 +0,0 @@ -package home.db.dao; - -interface IDaoConsts { - - // Table name - String VEHICLE = "vehicle"; - - // The names of the columns in the table - String ID = "id"; - String TYPE = "type"; - String COLOR = "color"; - String NUMBER = "number"; - String IS_TRANSPORTS_CARGO = "is_transports_cargo"; - String IS_TRANSPORTS_PASSENGERS = "is_transports_passengers"; - String HAS_TRAILER = "has_trailer"; - String HAS_CRADLE = "has_cradle"; - String DATE_TIME = "date_time"; - - // Data types - String INTEGER = "INTEGER"; - String TEXT = "TEXT"; -} diff --git a/src/main/java/home/db/dao/PgDao.java b/src/main/java/home/db/dao/PgDao.java new file mode 100644 index 0000000..512a894 --- /dev/null +++ b/src/main/java/home/db/dao/PgDao.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.dao; + +import java.sql.Connection; + +final class PgDao extends AbstractDao { + + @Override + protected int getTransactionIsolation() { + return Connection.TRANSACTION_READ_COMMITTED; + } +} diff --git a/src/main/java/home/db/dao/SQLiteDao.java b/src/main/java/home/db/dao/SQLiteDao.java new file mode 100644 index 0000000..a8c4112 --- /dev/null +++ b/src/main/java/home/db/dao/SQLiteDao.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.dao; + +import java.sql.Connection; + +final class SQLiteDao extends AbstractDao { + + @Override + protected int getTransactionIsolation() { + return Connection.TRANSACTION_SERIALIZABLE; + } +} diff --git a/src/main/java/home/db/init/AbstractDbInitializer.java b/src/main/java/home/db/init/AbstractDbInitializer.java new file mode 100644 index 0000000..752b9d1 --- /dev/null +++ b/src/main/java/home/db/init/AbstractDbInitializer.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.init; + +import java.sql.SQLException; + +import home.db.conn.Connector; + +abstract sealed class AbstractDbInitializer permits PgDbInitializer, SQLiteDbInitializer { + + protected void createTableIfNotExists() throws SQLException { + try (var conn = Connector.getConnection(); + var stmt = conn.createStatement()) { + stmt.execute(getTableCreationQuery()); + } + } + + protected abstract String getTableCreationQuery(); +} diff --git a/src/main/java/home/db/init/DbInitializer.java b/src/main/java/home/db/init/DbInitializer.java new file mode 100644 index 0000000..bd25db2 --- /dev/null +++ b/src/main/java/home/db/init/DbInitializer.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.init; + +import java.io.File; +import java.io.IOException; +import java.sql.SQLException; + +import home.Settings; +import home.db.DbType; + +public final class DbInitializer { + + private static AbstractDbInitializer initializer; + private static DbType previousDbType; + + public static void createDbFileIfNotExists(File file) throws IOException, SQLException { + Settings.saveDatabaseType(DbType.SQLite.name()); + ((SQLiteDbInitializer) getInitializer()).createDbFileIfNotExists(file); + } + + public static void createTableIfNotExists() throws SQLException { + getInitializer().createTableIfNotExists(); + } + + private static AbstractDbInitializer getInitializer() throws SQLException { + DbType currentDbType = Settings.getDatabaseType(); + + if (initializer != null && previousDbType == currentDbType) { + return initializer; + } + + initializer = switch (currentDbType) { + case SQLite -> new SQLiteDbInitializer(); + case PostgreSQL -> new PgDbInitializer(); + }; + previousDbType = currentDbType; + + return initializer; + } + + private DbInitializer() { + } +} diff --git a/src/main/java/home/db/init/PgDbInitializer.java b/src/main/java/home/db/init/PgDbInitializer.java new file mode 100644 index 0000000..af70826 --- /dev/null +++ b/src/main/java/home/db/init/PgDbInitializer.java @@ -0,0 +1,47 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.init; + +final class PgDbInitializer extends AbstractDbInitializer { + + private static final String CREATE_TABLE_QUERY = """ + CREATE SEQUENCE IF NOT EXISTS public.vehicle_id_seq + AS bigint + START WITH 1 + INCREMENT BY 1 + NO MAXVALUE + NO MINVALUE + CACHE 1; + + CREATE TABLE IF NOT EXISTS public.vehicle ( + id bigint DEFAULT nextval('public.vehicle_id_seq'::regclass) NOT NULL, + type character varying(50), + color text, + number character varying(50), + is_transports_cargo integer, + is_transports_passengers integer, + has_trailer integer, + has_cradle integer, + date_time bigint, + CONSTRAINT vehicle_pkey PRIMARY KEY (id) + ); + """; + + @Override + protected String getTableCreationQuery() { + return CREATE_TABLE_QUERY; + } +} diff --git a/src/main/java/home/db/init/SQLiteDbInitializer.java b/src/main/java/home/db/init/SQLiteDbInitializer.java new file mode 100644 index 0000000..8ad4a43 --- /dev/null +++ b/src/main/java/home/db/init/SQLiteDbInitializer.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db.init; + +import java.io.File; +import java.io.IOException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import home.Settings; + +final class SQLiteDbInitializer extends AbstractDbInitializer { + + private static final Logger LOG = LoggerFactory.getLogger(SQLiteDbInitializer.class); + + private static final String CREATE_TABLE_QUERY = """ + CREATE TABLE IF NOT EXISTS vehicle ( + 'id' INTEGER PRIMARY KEY AUTOINCREMENT, + 'type' TEXT, + 'color' TEXT, + 'number' TEXT, + 'is_transports_cargo' INTEGER, + 'is_transports_passengers' INTEGER, + 'has_trailer' INTEGER, + 'has_cradle' INTEGER, + 'date_time' INTEGER) + """; + + @Override + protected String getTableCreationQuery() { + return CREATE_TABLE_QUERY; + } + + void createDbFileIfNotExists(File file) throws IOException { + try { + if (!file.exists()) { + file.createNewFile(); + } + Settings.saveDatabase(file.getAbsolutePath()); + } catch (IOException e) { + LOG.error("Error while creating the database file.", e); + throw new IOException("Error while creating the database file.", e); + } + } +} diff --git a/src/main/java/home/file/FileHandler.java b/src/main/java/home/file/FileHandler.java index 9650e99..752bbeb 100644 --- a/src/main/java/home/file/FileHandler.java +++ b/src/main/java/home/file/FileHandler.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file; import java.io.BufferedReader; diff --git a/src/main/java/home/file/IExporter.java b/src/main/java/home/file/IExporter.java index fa483b3..fcf19cc 100644 --- a/src/main/java/home/file/IExporter.java +++ b/src/main/java/home/file/IExporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file; public interface IExporter { diff --git a/src/main/java/home/file/IImporter.java b/src/main/java/home/file/IImporter.java index fc15d1c..668c135 100644 --- a/src/main/java/home/file/IImporter.java +++ b/src/main/java/home/file/IImporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file; import java.io.File; diff --git a/src/main/java/home/file/Tag.java b/src/main/java/home/file/Tag.java index 0d0e08b..af7d0ab 100644 --- a/src/main/java/home/file/Tag.java +++ b/src/main/java/home/file/Tag.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file; public enum Tag { @@ -23,13 +38,14 @@ public String getTagName() { return tagName; } - public static Tag getTag(String tagName) { + public static Tag getTag(String tagName, String errorMsg) { String tagFormatted = tagName.strip(); for (Tag tag : Tag.values()) { if (tagFormatted.equals(tag.getTagName())) { return tag; } } - return null; + + throw new IllegalArgumentException(errorMsg.formatted(tagName)); } } diff --git a/src/main/java/home/file/csv/CsvConst.java b/src/main/java/home/file/csv/CsvConst.java new file mode 100644 index 0000000..d742476 --- /dev/null +++ b/src/main/java/home/file/csv/CsvConst.java @@ -0,0 +1,46 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.file.csv; + +import home.file.Tag; + +final class CsvConst { + + static final int TYPE_IDX = 0; + static final int COLOR_IDX = 1; + static final int NUMBER_IDX = 2; + static final int DATE_IDX = 3; + static final int HAS_TRAILER_IDX = 4; + static final int IS_TRANSPORTS_PASSENGERS_IDX = 5; + static final int IS_TRANSPORTS_CARGO_IDX = 6; + static final int HAS_CRADLE_IDX = 7; + + static final String[] CSV_HEADER = { + Tag.TYPE.getTagName(), + Tag.COLOR.getTagName(), + Tag.NUMBER.getTagName(), + Tag.DATE.getTagName(), + Tag.HAS_TRAILER.getTagName(), + Tag.IS_TRANSPORTS_PASSENGERS.getTagName(), + Tag.IS_TRANSPORTS_CARGO.getTagName(), + Tag.HAS_CRADLE.getTagName() + }; + + static final int CSV_ROW_SIZE = CSV_HEADER.length; + + private CsvConst() { + } +} diff --git a/src/main/java/home/file/csv/CsvExporter.java b/src/main/java/home/file/csv/CsvExporter.java index 812dd10..efddbe9 100644 --- a/src/main/java/home/file/csv/CsvExporter.java +++ b/src/main/java/home/file/csv/CsvExporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.csv; import java.io.IOException; @@ -36,7 +51,7 @@ public String exportAllDataObjsToString() { List dataObjStorage = Storage.INSTANCE.getAll(); var convertedDataObjs = new ArrayList(dataObjStorage.size() + 1); - convertedDataObjs.add(ICsvConsts.CSV_HEADER); + convertedDataObjs.add(CsvConst.CSV_HEADER); for (AbstractVehicle dataObj : dataObjStorage) { convertedDataObjs.add(convertDataObjToArray(dataObj)); } @@ -54,38 +69,38 @@ public String exportAllDataObjsToString() { } private String[] convertDataObjToArray(AbstractVehicle dataObj) { - var array = new String[ICsvConsts.CSV_ROW_SIZE]; + var array = new String[CsvConst.CSV_ROW_SIZE]; VehicleType type = dataObj.getType(); - array[ICsvConsts.TYPE_IDX] = type.getType(); - array[ICsvConsts.COLOR_IDX] = dataObj.getColor(); - array[ICsvConsts.NUMBER_IDX] = dataObj.getNumber(); - array[ICsvConsts.DATE_IDX] = Utils.getFormattedDate(dataObj.getDateTime()); + array[CsvConst.TYPE_IDX] = type.getType(); + array[CsvConst.COLOR_IDX] = dataObj.getColor(); + array[CsvConst.NUMBER_IDX] = dataObj.getNumber(); + array[CsvConst.DATE_IDX] = Utils.getFormattedDate(dataObj.getDateTime()); String hasTrailerStr = FALSE; if (type.in(VehicleType.CAR, VehicleType.TRUCK)) { hasTrailerStr = Boolean.toString(((AbstractVehicleWithTrailer) dataObj).hasTrailer()); } - array[ICsvConsts.HAS_TRAILER_IDX] = hasTrailerStr; + array[CsvConst.HAS_TRAILER_IDX] = hasTrailerStr; String isTransportsPassengersStr = FALSE; if (VehicleType.CAR == type) { isTransportsPassengersStr = Boolean.toString(((Car) dataObj).isTransportsPassengers()); } - array[ICsvConsts.IS_TRANSPORTS_PASSENGERS_IDX] = isTransportsPassengersStr; + array[CsvConst.IS_TRANSPORTS_PASSENGERS_IDX] = isTransportsPassengersStr; String isTransportsCargoStr = FALSE; if (VehicleType.TRUCK == type) { isTransportsCargoStr = Boolean.toString(((Truck) dataObj).isTransportsCargo()); } - array[ICsvConsts.IS_TRANSPORTS_CARGO_IDX] = isTransportsCargoStr; + array[CsvConst.IS_TRANSPORTS_CARGO_IDX] = isTransportsCargoStr; String hasCradle = FALSE; if (VehicleType.MOTORCYCLE == type) { hasCradle = Boolean.toString(((Motorcycle) dataObj).hasCradle()); } - array[ICsvConsts.HAS_CRADLE_IDX] = hasCradle; + array[CsvConst.HAS_CRADLE_IDX] = hasCradle; return array; } diff --git a/src/main/java/home/file/csv/CsvImporter.java b/src/main/java/home/file/csv/CsvImporter.java index 744633f..6dcaa05 100644 --- a/src/main/java/home/file/csv/CsvImporter.java +++ b/src/main/java/home/file/csv/CsvImporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.csv; import java.io.File; @@ -61,7 +76,7 @@ private List parse(List rawDataObjs) { private void checkElementsCountInRawDataObjs(List rawDataObjs) { for (String[] rawDataObj : rawDataObjs) { - if (ICsvConsts.CSV_ROW_SIZE != rawDataObj.length) { + if (CsvConst.CSV_ROW_SIZE != rawDataObj.length) { throw new IllegalArgumentException("Incorrect count of elements in : [%s]" .formatted(String.join(", ", rawDataObj))); } @@ -70,17 +85,14 @@ private void checkElementsCountInRawDataObjs(List rawDataObjs) { private List getRawDataObjsWithoutHeader(List rawDataObjs) { String[] header = rawDataObjs.get(0); - return Arrays.equals(header, ICsvConsts.CSV_HEADER) + return Arrays.equals(header, CsvConst.CSV_HEADER) ? rawDataObjs.subList(1, rawDataObjs.size()) : rawDataObjs; } private AbstractVehicle convertToDataObj(String[] rawDataObj) { - String type = rawDataObj[ICsvConsts.TYPE_IDX]; + String type = rawDataObj[CsvConst.TYPE_IDX]; VehicleType vehicleType = VehicleType.getVehicleType(type); - if (vehicleType == null) { - throw new IllegalArgumentException("Wrong vehicle type received : " + type); - } AbstractVehicle dataObj = switch (vehicleType) { case CAR -> new Car(); @@ -88,31 +100,31 @@ private AbstractVehicle convertToDataObj(String[] rawDataObj) { case MOTORCYCLE -> new Motorcycle(); }; - for (int tagIdx = ICsvConsts.COLOR_IDX; ICsvConsts.HAS_CRADLE_IDX >= tagIdx; tagIdx++) { + for (int tagIdx = CsvConst.COLOR_IDX; CsvConst.HAS_CRADLE_IDX >= tagIdx; tagIdx++) { String value = rawDataObj[tagIdx]; switch (tagIdx) { - case ICsvConsts.COLOR_IDX -> dataObj.setColor(value); - case ICsvConsts.NUMBER_IDX -> dataObj.setNumber(value); - case ICsvConsts.DATE_IDX -> dataObj.setDateTime(Utils.getLongFromFormattedDate(value)); - case ICsvConsts.HAS_TRAILER_IDX -> { + case CsvConst.COLOR_IDX -> dataObj.setColor(value); + case CsvConst.NUMBER_IDX -> dataObj.setNumber(value); + case CsvConst.DATE_IDX -> dataObj.setDateTime(Utils.getLongFromFormattedDate(value)); + case CsvConst.HAS_TRAILER_IDX -> { if (vehicleType.in(VehicleType.CAR, VehicleType.TRUCK)) { boolean hasTrailer = Boolean.parseBoolean(value); ((AbstractVehicleWithTrailer) dataObj).setHasTrailer(hasTrailer); } } - case ICsvConsts.IS_TRANSPORTS_PASSENGERS_IDX -> { + case CsvConst.IS_TRANSPORTS_PASSENGERS_IDX -> { if (VehicleType.CAR == vehicleType) { boolean isTransportsPassengers = Boolean.parseBoolean(value); ((Car) dataObj).setTransportsPassengers(isTransportsPassengers); } } - case ICsvConsts.IS_TRANSPORTS_CARGO_IDX -> { + case CsvConst.IS_TRANSPORTS_CARGO_IDX -> { if (VehicleType.TRUCK == vehicleType) { boolean isTransportsCargo = Boolean.parseBoolean(value); ((Truck) dataObj).setTransportsCargo(isTransportsCargo); } } - case ICsvConsts.HAS_CRADLE_IDX -> { + case CsvConst.HAS_CRADLE_IDX -> { if (VehicleType.MOTORCYCLE == vehicleType) { boolean hasCradle = Boolean.parseBoolean(value); ((Motorcycle) dataObj).setHasCradle(hasCradle); diff --git a/src/main/java/home/file/csv/ICsvConsts.java b/src/main/java/home/file/csv/ICsvConsts.java deleted file mode 100644 index 6202e61..0000000 --- a/src/main/java/home/file/csv/ICsvConsts.java +++ /dev/null @@ -1,28 +0,0 @@ -package home.file.csv; - -import home.file.Tag; - -interface ICsvConsts { - - int TYPE_IDX = 0; - int COLOR_IDX = 1; - int NUMBER_IDX = 2; - int DATE_IDX = 3; - int HAS_TRAILER_IDX = 4; - int IS_TRANSPORTS_PASSENGERS_IDX = 5; - int IS_TRANSPORTS_CARGO_IDX = 6; - int HAS_CRADLE_IDX = 7; - - String[] CSV_HEADER = { - Tag.TYPE.getTagName(), - Tag.COLOR.getTagName(), - Tag.NUMBER.getTagName(), - Tag.DATE.getTagName(), - Tag.HAS_TRAILER.getTagName(), - Tag.IS_TRANSPORTS_PASSENGERS.getTagName(), - Tag.IS_TRANSPORTS_CARGO.getTagName(), - Tag.HAS_CRADLE.getTagName() - }; - - int CSV_ROW_SIZE = CSV_HEADER.length; -} diff --git a/src/main/java/home/file/json_yaml/AbstractJsonYamlExporter.java b/src/main/java/home/file/json_yaml/AbstractJsonYamlExporter.java index 12b6a6d..49db0dd 100644 --- a/src/main/java/home/file/json_yaml/AbstractJsonYamlExporter.java +++ b/src/main/java/home/file/json_yaml/AbstractJsonYamlExporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.json_yaml; import java.util.LinkedHashMap; diff --git a/src/main/java/home/file/json_yaml/AbstractJsonYamlImporter.java b/src/main/java/home/file/json_yaml/AbstractJsonYamlImporter.java index 334f48c..0e62baa 100644 --- a/src/main/java/home/file/json_yaml/AbstractJsonYamlImporter.java +++ b/src/main/java/home/file/json_yaml/AbstractJsonYamlImporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.json_yaml; import java.util.Map; @@ -31,9 +46,6 @@ protected void checkRootTagName(String rootTagName) { protected AbstractVehicle convertToDataObj(Map rawDataStringMap) { String type = removeRequiredParam(Tag.TYPE, rawDataStringMap); VehicleType vehicleType = VehicleType.getVehicleType(type); - if (vehicleType == null) { - throw new IllegalArgumentException("Wrong vehicle type received : " + type); - } AbstractVehicle dataObj = switch (vehicleType) { case CAR -> new Car(); @@ -45,10 +57,7 @@ protected AbstractVehicle convertToDataObj(Map rawDataStringMap) String tagName = tagData.getKey(); String tagValue = tagData.getValue(); - Tag tag = Tag.getTag(tagName); - if (tag == null) { - throw new IllegalArgumentException("Incorrect tag name : " + tagName); - } + Tag tag = Tag.getTag(tagName, "Incorrect tag name : %s"); switch (tag) { case COLOR -> dataObj.setColor(tagValue); diff --git a/src/main/java/home/file/json_yaml/JsonExporter.java b/src/main/java/home/file/json_yaml/JsonExporter.java index 541cf8b..b7a71ae 100644 --- a/src/main/java/home/file/json_yaml/JsonExporter.java +++ b/src/main/java/home/file/json_yaml/JsonExporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.json_yaml; import java.util.ArrayList; diff --git a/src/main/java/home/file/json_yaml/JsonImporter.java b/src/main/java/home/file/json_yaml/JsonImporter.java index 3c9c0b6..e4deedc 100644 --- a/src/main/java/home/file/json_yaml/JsonImporter.java +++ b/src/main/java/home/file/json_yaml/JsonImporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.json_yaml; import java.io.File; diff --git a/src/main/java/home/file/json_yaml/YamlExporter.java b/src/main/java/home/file/json_yaml/YamlExporter.java index 46e357e..f85f23b 100644 --- a/src/main/java/home/file/json_yaml/YamlExporter.java +++ b/src/main/java/home/file/json_yaml/YamlExporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.json_yaml; import java.util.ArrayList; diff --git a/src/main/java/home/file/json_yaml/YamlImporter.java b/src/main/java/home/file/json_yaml/YamlImporter.java index 42bc788..8d01faa 100644 --- a/src/main/java/home/file/json_yaml/YamlImporter.java +++ b/src/main/java/home/file/json_yaml/YamlImporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.json_yaml; import java.io.File; @@ -25,7 +40,7 @@ public final class YamlImporter extends AbstractJsonYamlImporter { @Override public List importDataObjsFromFile(File file) { try (InputStream inputStream = new FileInputStream(file)) { - Yaml yaml = new Yaml(); + var yaml = new Yaml(); Map allData = yaml.load(inputStream); List dataObjs = parse(allData); return dataObjs; diff --git a/src/main/java/home/file/ser/AbstractSerImporter.java b/src/main/java/home/file/ser/AbstractSerImporter.java index 50467a6..bcb0d94 100644 --- a/src/main/java/home/file/ser/AbstractSerImporter.java +++ b/src/main/java/home/file/ser/AbstractSerImporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.ser; import java.io.IOException; diff --git a/src/main/java/home/file/ser/BserExporter.java b/src/main/java/home/file/ser/BserExporter.java index 2b5fcf1..236ac01 100644 --- a/src/main/java/home/file/ser/BserExporter.java +++ b/src/main/java/home/file/ser/BserExporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.ser; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/home/file/ser/BserImporter.java b/src/main/java/home/file/ser/BserImporter.java index c91411e..a99de3b 100644 --- a/src/main/java/home/file/ser/BserImporter.java +++ b/src/main/java/home/file/ser/BserImporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.ser; import java.io.ByteArrayInputStream; diff --git a/src/main/java/home/file/ser/SerExporter.java b/src/main/java/home/file/ser/SerExporter.java index 4ad5b9c..18c5766 100644 --- a/src/main/java/home/file/ser/SerExporter.java +++ b/src/main/java/home/file/ser/SerExporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.ser; import java.io.BufferedOutputStream; diff --git a/src/main/java/home/file/ser/SerImporter.java b/src/main/java/home/file/ser/SerImporter.java index 125feac..94ca652 100644 --- a/src/main/java/home/file/ser/SerImporter.java +++ b/src/main/java/home/file/ser/SerImporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.ser; import java.io.BufferedInputStream; diff --git a/src/main/java/home/file/xml/XmlExporter.java b/src/main/java/home/file/xml/XmlExporter.java index 70625b0..6b98c10 100644 --- a/src/main/java/home/file/xml/XmlExporter.java +++ b/src/main/java/home/file/xml/XmlExporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.xml; import java.io.ByteArrayOutputStream; diff --git a/src/main/java/home/file/xml/XmlImporter.java b/src/main/java/home/file/xml/XmlImporter.java index 67d37f5..b10f9e3 100644 --- a/src/main/java/home/file/xml/XmlImporter.java +++ b/src/main/java/home/file/xml/XmlImporter.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file.xml; import java.io.File; @@ -47,10 +62,7 @@ public List importDataObjsFromFile(File file) { String tagName = reader.getName().getLocalPart(); - Tag tag = Tag.getTag(tagName); - if (tag == null) { - throw new IllegalArgumentException("Wrong start of tag name : " + tagName); - } + Tag tag = Tag.getTag(tagName, "Wrong start of tag name : %s"); switch (tag) { case VEHICLES: @@ -127,10 +139,7 @@ public List importDataObjsFromFile(File file) { String tagName = reader.getName().getLocalPart(); - Tag tag = Tag.getTag(tagName); - if (tag == null) { - throw new IllegalArgumentException("Wrong end of tag name : " + tagName); - } + Tag tag = Tag.getTag(tagName, "Wrong end of tag name : %s"); if (Tag.VEHICLE == tag) { dataObjs.add(dataObj); @@ -170,11 +179,7 @@ private void chcekAttribute(XMLStreamReader reader) { private AbstractVehicle createDataObj(XMLStreamReader reader) { String type = reader.getAttributeValue(0); - VehicleType vehicleType = VehicleType.getVehicleType(type); - if (vehicleType == null) { - throw new IllegalArgumentException("Wrong vehicle type received : " + type); - } return switch (vehicleType) { case CAR -> new Car(); diff --git a/src/main/java/home/gui/ColorSchema.java b/src/main/java/home/gui/ColorSchema.java index e6e6617..de940d3 100644 --- a/src/main/java/home/gui/ColorSchema.java +++ b/src/main/java/home/gui/ColorSchema.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui; import javax.swing.UIManager; @@ -36,6 +51,7 @@ public static ColorSchema getColorSchema(String colorSchemaName) { return colorSchema; } } - return null; + + throw new IllegalArgumentException("Incorrect style name : " + colorSchemaName); } } diff --git a/src/main/java/home/gui/DataActionInGui.java b/src/main/java/home/gui/DataActionInGui.java index b310352..9e34689 100644 --- a/src/main/java/home/gui/DataActionInGui.java +++ b/src/main/java/home/gui/DataActionInGui.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui; import java.util.List; @@ -5,7 +20,7 @@ import home.Storage; import home.model.AbstractVehicle; -public class DataActionInGui { +public final class DataActionInGui { public static void init(List dataObjs) { Storage.INSTANCE.initDataObjs(dataObjs); diff --git a/src/main/java/home/gui/DbOperation.java b/src/main/java/home/gui/DbOperation.java new file mode 100644 index 0000000..e5e866d --- /dev/null +++ b/src/main/java/home/gui/DbOperation.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui; + +public enum DbOperation { + + CREATE_OR_OPEN_FILE_DATABASE(GuiConst.CREATE_OR_OPEN_FILE_DATABASE), + CONNECT_TO_SERVER_DATABASE(GuiConst.CONNECT_TO_SERVER_DATABASE), + SAVE(GuiConst.SAVE), + SAVE_AS(GuiConst.SAVE_AS); + + private String operationText; + + private DbOperation(String operationText) { + this.operationText = operationText; + } + + public String getOperatioText() { + return operationText; + } +} diff --git a/src/main/java/home/gui/DialogCaller.java b/src/main/java/home/gui/DialogCaller.java index 0d9107b..66cd244 100644 --- a/src/main/java/home/gui/DialogCaller.java +++ b/src/main/java/home/gui/DialogCaller.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui; import java.lang.reflect.Constructor; @@ -8,7 +23,7 @@ import org.slf4j.LoggerFactory; import home.Storage; -import home.gui.component.dialog.AbstractDialog; +import home.gui.component.dialog.AbstractDialogVehicle; import home.gui.component.dialog.DialogCar; import home.gui.component.dialog.DialogMotorcycle; import home.gui.component.dialog.DialogTruck; @@ -16,7 +31,7 @@ import home.model.VehicleType; import home.utils.LogUtils; -public final class DialogCaller { +final class DialogCaller { private static final Logger LOG = LoggerFactory.getLogger(DialogCaller.class); @@ -24,7 +39,7 @@ public final class DialogCaller { private static final int OBJ_DIALOG_HEIGHT = 350; @SuppressWarnings("unchecked") - static void showObjDialog(JFrame frame, + static void showObjDialog(JFrame frame, AbstractVehicle dataObj, int tblRowOfSelectedDataObj) { Class dialogClass = null; VehicleType objType = dataObj.getType(); @@ -38,12 +53,12 @@ static void showObjDialog(JFrame frame, showObjDialog(frame, dialogClass, dataObj, tblRowOfSelectedDataObj); } - static void showObjDialog(JFrame frame, + static void showObjDialog(JFrame frame, Class dialogClass) { showObjDialog(frame, dialogClass, null, Storage.NO_ROW_IS_SELECTED); } - private static void showObjDialog(JFrame frame, + private static void showObjDialog(JFrame frame, Class dialogClass, AbstractVehicle dataObj, int tblRowOfSelectedDataObj) { Constructor constructor; try { diff --git a/src/main/java/home/gui/Gui.java b/src/main/java/home/gui/Gui.java index 40b963f..c650e40 100644 --- a/src/main/java/home/gui/Gui.java +++ b/src/main/java/home/gui/Gui.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui; import java.awt.BorderLayout; @@ -31,23 +46,26 @@ import home.Main; import home.Settings; -import home.Settings.Setting; import home.Storage; import home.gui.component.CustomJButton; import home.gui.component.CustomJFileChooserImpExp.DataFormat; import home.gui.component.CustomJFrame; +import home.gui.component.CustomJLabel; import home.gui.component.CustomJPanel; import home.gui.component.CustomJPanel.PanelType; import home.gui.component.CustomJTable; +import home.gui.component.CustomJTableDataModel; import home.gui.component.dialog.DialogCar; import home.gui.component.dialog.DialogMotorcycle; import home.gui.component.dialog.DialogTruck; -import home.gui.listener.CreateOrOpenActionListener; +import home.gui.listener.ConnectToServerDatabaseActionListener; +import home.gui.listener.CreateOrOpenFileDatabaseActionListener; import home.gui.listener.ExportImportActionListener; import home.gui.listener.SaveActionListener; import home.model.AbstractVehicle; -import home.utils.ThreadUtil; import home.utils.LogUtils; +import home.utils.ThreadUtil; +import home.utils.Utils; public enum Gui { @@ -59,8 +77,8 @@ public enum Gui { private JLabel dbLabel; - private JTable table; - private AbstractTableModel tableModel; + private CustomJTable table; + private AbstractTableModel tableDataModel; private JScrollPane tableScrollPane; private JButton btnCar; @@ -71,10 +89,10 @@ public enum Gui { private JPanel panelTable; private JPanel panelButton; private JMenuBar menuBar; - private JFrame frame; + private CustomJFrame frame; public void refreshTable() { - tableModel.fireTableDataChanged(); + tableDataModel.fireTableDataChanged(); } public void setDbLabel(String label) { @@ -94,20 +112,16 @@ public void buildGui() { private void setStyle(String style) { try { ColorSchema colorSchema = ColorSchema.getColorSchema(style); - if (colorSchema == null) { - throw new IllegalArgumentException("Incorrect style name : " + style); - } UIManager.setLookAndFeel(colorSchema.getLookAndFeelClassName()); } catch (Exception e) { try { UIManager.setLookAndFeel(UIManager.getCrossPlatformLookAndFeelClassName()); - Settings.writeSetting(Setting.STYLE, - ColorSchema.CROSSPLATFORM.name().toLowerCase(Locale.ROOT)); - LogUtils.logAndShowError(LOG, frame, - "Error while set the system color scheme.\n" - + ColorSchema.CROSSPLATFORM.getNameForGui() - + " color scheme will be used.\nError: " - + e.getMessage(), + Settings.saveStyle(ColorSchema.CROSSPLATFORM.name().toLowerCase(Locale.ROOT)); + LogUtils.logAndShowError(LOG, frame, """ + Error while set the system color scheme. + %s color scheme will be used. + Error: %s""" + .formatted(ColorSchema.CROSSPLATFORM.getNameForGui(), e.getMessage()), "System color scheme error", e); } catch (Exception ex) { JFrame.setDefaultLookAndFeelDecorated(true); @@ -119,10 +133,11 @@ private void setStyle(String style) { } private void createTable() { - dbLabel = new JLabel(Settings.hasPathToDbFile() ? Settings.getDbFilePath() - : IGuiConsts.CHOOSE_DB_FILE); + dbLabel = CustomJLabel.createSmall(Utils.generateDbDescription()); + + tableDataModel = new CustomJTableDataModel(Storage.INSTANCE.getAll()); - table = CustomJTable.create(); + table = CustomJTable.create(tableDataModel, Settings.isAutoResizeTableWidth()); table.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent mouseEvent) { @@ -136,24 +151,22 @@ public void mousePressed(MouseEvent mouseEvent) { }); tableScrollPane = new JScrollPane(table); - - tableModel = (AbstractTableModel) table.getModel(); } private void createButtons() { - btnCar = CustomJButton.create(IGuiConsts.CAR); + btnCar = CustomJButton.create(GuiConst.CAR); btnCar.addActionListener(actionEvent -> DialogCaller .showObjDialog(frame, DialogCar.class)); - btnTruck = CustomJButton.create(IGuiConsts.TRUCK); + btnTruck = CustomJButton.create(GuiConst.TRUCK); btnTruck.addActionListener(actionEvent -> DialogCaller .showObjDialog(frame, DialogTruck.class)); - btnMotorcycle = CustomJButton.create(IGuiConsts.MOTORCYCLE); + btnMotorcycle = CustomJButton.create(GuiConst.MOTORCYCLE); btnMotorcycle.addActionListener(actionEvent -> DialogCaller .showObjDialog(frame, DialogMotorcycle.class)); - btnDelete = CustomJButton.create(IGuiConsts.DELETE); + btnDelete = CustomJButton.create(GuiConst.DELETE); btnDelete.addActionListener(actionEvent -> { ThreadUtil.runInThread(() -> { Thread.currentThread().setName("-> delete from storage"); @@ -169,11 +182,11 @@ private void createButtons() { } private void createPanels() { - panelTable = CustomJPanel.create(PanelType.FRAME_TABLE_PANEL); + panelTable = CustomJPanel.create(PanelType.MAIN_FRAME_TABLE_PANEL); panelTable.add(dbLabel, BorderLayout.NORTH); panelTable.add(tableScrollPane, BorderLayout.CENTER); - panelButton = CustomJPanel.create(PanelType.FRAME_BUTTON_PANEL); + panelButton = CustomJPanel.create(PanelType.MAIN_FRAME_BUTTON_PANEL); panelButton.add(btnCar); panelButton.add(btnTruck); panelButton.add(btnMotorcycle); @@ -183,16 +196,24 @@ private void createPanels() { private void createMenu() { menuBar = new JMenuBar(); - JMenuItem createOrOpenItem = createMenuItem(IGuiConsts.CREATE_OR_OPEN, - new CreateOrOpenActionListener(frame, dbLabel, LOG)); - JMenuItem saveItem = createMenuItem(IGuiConsts.SAVE, + JMenuItem createOrOpenFileDatabaseItem = createMenuItem( + DbOperation.CREATE_OR_OPEN_FILE_DATABASE.getOperatioText(), + new CreateOrOpenFileDatabaseActionListener(frame, dbLabel, LOG)); + JMenuItem connectToServerDatabaseItem = createMenuItem( + DbOperation.CONNECT_TO_SERVER_DATABASE.getOperatioText(), + new ConnectToServerDatabaseActionListener(frame, dbLabel, LOG)); + JMenuItem saveItem = createMenuItem( + DbOperation.SAVE.getOperatioText(), new SaveActionListener(frame, dbLabel, false, LOG)); - JMenuItem saveAsItem = createMenuItem(IGuiConsts.SAVE_AS, + JMenuItem saveAsItem = createMenuItem( + DbOperation.SAVE_AS.getOperatioText(), new SaveActionListener(frame, dbLabel, true, LOG)); JMenu importItem = createImportExportDropdownMenu(true); JMenu exportItem = createImportExportDropdownMenu(false); - var fileMenu = new JMenu(IGuiConsts.FILE); - fileMenu.add(createOrOpenItem); + var fileMenu = new JMenu(GuiConst.FILE); + fileMenu.add(createOrOpenFileDatabaseItem); + fileMenu.add(new JSeparator()); + fileMenu.add(connectToServerDatabaseItem); fileMenu.add(new JSeparator()); fileMenu.add(saveItem); fileMenu.add(saveAsItem); @@ -203,11 +224,11 @@ private void createMenu() { menuBar.add(creatStyleMenu()); - JMenuItem aboutItem = createMenuItem(IGuiConsts.ABOUT, + JMenuItem aboutItem = createMenuItem(GuiConst.ABOUT, actionEvent -> JOptionPane.showMessageDialog( - frame, IGuiConsts.ABOUT_TEXT.formatted(Main.appVersion), - IGuiConsts.ABOUT_TITLE, JOptionPane.INFORMATION_MESSAGE)); - var helpMenu = new JMenu(IGuiConsts.HELP); + frame, GuiConst.ABOUT_TEXT.formatted(Main.appVersion), + GuiConst.ABOUT_TITLE, JOptionPane.INFORMATION_MESSAGE)); + var helpMenu = new JMenu(GuiConst.HELP); helpMenu.add(aboutItem); menuBar.add(helpMenu); } @@ -219,7 +240,7 @@ private JMenuItem createMenuItem(String name, ActionListener actionListener) { } private JMenu createImportExportDropdownMenu(boolean isImport) { - var dropdownMenu = new JMenu(isImport ? IGuiConsts.IMPORT_FROM : IGuiConsts.EXPORT_TO); + var dropdownMenu = new JMenu(isImport ? GuiConst.IMPORT_FROM : GuiConst.EXPORT_TO); for (DataFormat dataFormat : DataFormat.values()) { dropdownMenu.add(createMenuItem(dataFormat.getExtension(), new ExportImportActionListener(dataFormat, isImport, frame, LOG))); @@ -228,15 +249,20 @@ private JMenu createImportExportDropdownMenu(boolean isImport) { } private JMenu creatStyleMenu() { - var styleMenu = new JMenu(IGuiConsts.STYLE); + var styleMenu = new JMenu(GuiConst.STYLE); + + styleMenu.add(createCheckBoxResizeTblMenuItem()); + styleMenu.add(new JSeparator()); + var checkBoxItems = new ArrayList(); for (ColorSchema colorSchema : ColorSchema.values()) { - styleMenu.add(createCheckBoxMenuItem(colorSchema, checkBoxItems)); + styleMenu.add(createCheckBoxColorSchemaMenuItem(colorSchema, checkBoxItems)); } + return styleMenu; } - private JCheckBoxMenuItem createCheckBoxMenuItem(ColorSchema colorSchema, + private JCheckBoxMenuItem createCheckBoxColorSchemaMenuItem(ColorSchema colorSchema, List checkBoxItems) { var checkBoxMenuItem = new JCheckBoxMenuItem(colorSchema.getNameForGui()); checkBoxMenuItem.setSelected( @@ -258,7 +284,7 @@ private void styleSelectAction(ActionEvent actionEvent, var selectedItem = (JCheckBoxMenuItem) actionEvent.getSource(); selectedItem.setSelected(true); - Settings.writeSetting(Setting.STYLE, selectedItem.getText().toLowerCase(Locale.ROOT)); + Settings.saveStyle(selectedItem.getText().toLowerCase(Locale.ROOT)); setStyle(Settings.getStyle()); SwingUtilities.updateComponentTreeUI(frame); @@ -268,24 +294,35 @@ private void styleSelectAction(ActionEvent actionEvent, } } + private JCheckBoxMenuItem createCheckBoxResizeTblMenuItem() { + var checkBoxMenuItem = new JCheckBoxMenuItem(GuiConst.AUTO_RESIZE_TABLE_WIDTH); + checkBoxMenuItem.setSelected(Settings.isAutoResizeTableWidth()); + + checkBoxMenuItem.addActionListener(actionEvent -> { + try { + boolean isAutoResizeTableWidthCurrent = Settings.isAutoResizeTableWidth(); + boolean isAutoResizeTableWidthNew = !isAutoResizeTableWidthCurrent; + + Settings.saveAutoResizeTableWidth(isAutoResizeTableWidthNew); + + checkBoxMenuItem.setSelected(isAutoResizeTableWidthNew); + table.setAutoResize(isAutoResizeTableWidthNew); + + SwingUtilities.updateComponentTreeUI(frame); + } catch (Exception e) { + LogUtils.logAndShowError(LOG, frame, + "Error while changing auto resizing mode of table.", "Style error", e); + } + }); + + return checkBoxMenuItem; + } + private void createFrame() { frame = CustomJFrame.create(Main.appName); frame.setJMenuBar(menuBar); frame.getContentPane().add(panelTable, BorderLayout.CENTER); frame.getContentPane().add(panelButton, BorderLayout.EAST); - makeFrameVisible(); - } - - /** - * Creating and displaying a form. When launched via - * "SwingUtilities.invokeLater(new Runnable(){...}" the frame will be created - * and displayed after all expected events have been processed, i.e. the frame - * will be created and displayed when all resources are ready. This is - * necessary, so that all elements are guaranteed to be displayed in the window - * (if you do "frame.setVisible(true)" from the main thread, then there is a - * chance that some element will not be displayed in the window). - */ - private void makeFrameVisible() { - SwingUtilities.invokeLater(() -> frame.setVisible(true)); + frame.makeFrameVisible(); } } diff --git a/src/main/java/home/gui/GuiConst.java b/src/main/java/home/gui/GuiConst.java new file mode 100644 index 0000000..ddbccc9 --- /dev/null +++ b/src/main/java/home/gui/GuiConst.java @@ -0,0 +1,107 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui; + +public final class GuiConst { + + public static final String DATE_FORMAT = "yyyy.MM.dd | HH:mm:ss"; + + // label format "postgresql://127.0.0.1:5432/database_name" + public static final String DB_LABEL_FORMAT = "%s://%s:%d/%s"; + + // Table column names + public static final String TYPE = "Type"; + public static final String COLOR = "Color"; + public static final String NUMBER = "Number"; + public static final String DATE = "Date"; + public static final String DELETION_MARK = "Deletion mark"; + + // Button names + static final String CAR = "Car"; + static final String TRUCK = "Truck"; + static final String MOTORCYCLE = "Motorcycle"; + static final String DELETE = "Delete"; + public static final String OK = "Ok"; + public static final String TEST_CONNECTION = "Test connection"; + public static final String SAVE_AND_CONNECT = "Save and connect"; + public static final String CANCEL = "Cancel"; + public static final String HAS_TRAILER = "has trailer"; + public static final String TRANSPORTS_PASSENGERS = "transports passengers"; + public static final String TRANSPORTS_CARGO = "transports cargo"; + public static final String HAS_CRADLE = "has cradle"; + + // Menu names + static final String FILE = "File"; + static final String IMPORT_FROM = "Import from ..."; + static final String EXPORT_TO = "Export to ..."; + static final String STYLE = "Style"; + static final String HELP = "Help"; + static final String ABOUT = "About"; + public static final String CREATE_OR_OPEN_FILE_DATABASE = "Create/Open file database"; + public static final String CONNECT_TO_SERVER_DATABASE = "Connect to server database"; + public static final String SAVE = "Save"; + public static final String SAVE_AS = "Save as ... (to file)"; + public static final String AUTO_RESIZE_TABLE_WIDTH = "Auto resize table width"; + + // DB label + public static final String DATABASE_NOT_SELECTED = "Database not selected"; + + // DB Connection dialog labels + public static final String HOST = "host"; + public static final String PORT = "port"; + public static final String DB_NAME = "database name"; + public static final String USER = "user"; + public static final String PASS = "password"; + public static final String SHOW_PASS = "show password"; + public static final String DB_TYPE = "database type"; + + // About dialog text + static final String ABOUT_TITLE = "About"; + static final String ABOUT_TEXT = "Test application.\nVersion: %s"; + + // Save dialog text + public static final String SAVE_TITLE = "Saving"; + public static final String SAVE_TEXT = "Saved successfully"; + + // Save to already exists dialog text + public static final String ALREADY_EXISTS_TITLE = "Such file already exists"; + public static final String ALREADY_EXISTS_TEXT = "File with such name already exists, text another name."; + + // Continue previous database dialog text + public static final String PREVIOUS_DATABASE_TITLE = "Previous database"; + public static final String PREVIOUS_DATABASE_TEXT = "Continue with previous database?\n\nPrevious database:\n%s"; + + // Removed previous connection dialog text + public static final String REMOVED_PREVIOUS_CONNECTION_TITLE = "Removed previous connection"; + public static final String REMOVED_PREVIOUS_CONNECTION_TEXT = "Connection to previous database was removed."; + + // Operation confirm dialog text + public static final String OPERATION_CONFIRM_TITLE = "Operation confirm"; + public static final String OPERATION_CONFIRM_TEXT = "Do you want to %s ?"; + + // Connection test successful dialog text + public static final String CONNECTION_TEST_SUCCESSFUL_TITLE = "Connection test"; + public static final String CONNECTION_TEST_SUCCESSFUL_TEXT = "Connection successful!"; + + // Connection test error message + public static final String CONNECTION_TEST_ERROR_TEXT = "Connection test error:\n%s"; + + // Connection test supported error message + public static final String CONNECTION_TEST_SUPPORT_ERROR_TEXT = "Connection test for db %s is not supported"; + + private GuiConst() { + } +} diff --git a/src/main/java/home/gui/IGuiConsts.java b/src/main/java/home/gui/IGuiConsts.java deleted file mode 100644 index 2d1e2d9..0000000 --- a/src/main/java/home/gui/IGuiConsts.java +++ /dev/null @@ -1,53 +0,0 @@ -package home.gui; - -public interface IGuiConsts { - - String DATE_FORMAT = "yyyy.MM.dd | HH:mm:ss"; - - // Table column names - String TYPE = "Type"; - String COLOR = "Color"; - String NUMBER = "Number"; - String DATE = "Date"; - String DELETION_MARK = "Deletion mark"; - - // Button names - String CAR = "Car"; - String TRUCK = "Truck"; - String MOTORCYCLE = "Motorcycle"; - String DELETE = "Delete"; - String OK = "Ok"; - String CANCEL = "Cancel"; - String HAS_TRAILER = "has trailer"; - String TRANSPORTS_PASSENGERS = "transports passengers"; - String TRANSPORTS_CARGO = "transports cargo"; - String HAS_CRADLE = "has cradle"; - - // Menu names - String FILE = "File"; - String CREATE_OR_OPEN = "Create / Open"; - String SAVE = "Save"; - String SAVE_AS = "Save as ..."; - String IMPORT_FROM = "Import from ..."; - String EXPORT_TO = "Export to ..."; - String STYLE = "Style"; - String HELP = "Help"; - String ABOUT = "About"; - - // About dialog text - String ABOUT_TITLE = "About"; - String ABOUT_TEXT = "Test application.\nVersion: %s"; - - // Save dialog text - String SAVE_TITLE = "Saving"; - String SAVE_TEXT = "Saved successfully"; - - // Export/Import dialog text - String EXPORT_TITLE = "Exporting"; - String EXPORT_TEXT = "Exported successfully"; - String IMPORT_TITLE = "Importing"; - String IMPORT_TEXT = "Imported successfully"; - - // DB label - String CHOOSE_DB_FILE = "Choose SQLite DB file via [File] -> [Open]"; -} diff --git a/src/main/java/home/gui/component/CustomJButton.java b/src/main/java/home/gui/component/CustomJButton.java index c7d43fe..4eb3f4b 100644 --- a/src/main/java/home/gui/component/CustomJButton.java +++ b/src/main/java/home/gui/component/CustomJButton.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component; import javax.swing.JButton; diff --git a/src/main/java/home/gui/component/CustomJCheckBox.java b/src/main/java/home/gui/component/CustomJCheckBox.java new file mode 100644 index 0000000..048143a --- /dev/null +++ b/src/main/java/home/gui/component/CustomJCheckBox.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui.component; + +import java.awt.Color; +import java.awt.Font; +import java.awt.event.ItemListener; + +import javax.swing.JCheckBox; + +@SuppressWarnings("serial") +public final class CustomJCheckBox extends JCheckBox { + + private static final String FONT_NAME = "Courier"; + private static final int FONT_SIZE = 14; + private static final boolean IS_SELECTED = false; + + private CustomJCheckBox() { + } + + public static CustomJCheckBox create(String text, ItemListener itemListener) { + var checkBox = new CustomJCheckBox(); + checkBox.setText(text); + checkBox.setSelected(IS_SELECTED); + checkBox.setFont(new Font(FONT_NAME, Font.PLAIN, FONT_SIZE)); + checkBox.setForeground(Color.BLACK); + checkBox.addItemListener(itemListener); + return checkBox; + } +} diff --git a/src/main/java/home/gui/component/CustomJComboBox.java b/src/main/java/home/gui/component/CustomJComboBox.java new file mode 100644 index 0000000..80eadba --- /dev/null +++ b/src/main/java/home/gui/component/CustomJComboBox.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui.component; + +import java.awt.Color; +import java.awt.Font; +import java.util.EnumSet; + +import javax.swing.JComboBox; + +import home.db.DbType; + +@SuppressWarnings("serial") +public final class CustomJComboBox extends JComboBox { + + private static final String FONT_NAME = "Courier"; + private static final int FONT_SIZE = 14; + + private CustomJComboBox(String[] items) { + super(items); + } + + public static CustomJComboBox create(EnumSet dbTypes) { + var comboBox = new CustomJComboBox(dbTypes.stream() + .map(type -> type.name()).toArray(String[]::new)); + comboBox.setFont(new Font(FONT_NAME, Font.PLAIN, FONT_SIZE)); + comboBox.setForeground(Color.BLACK); + return comboBox; + } +} diff --git a/src/main/java/home/gui/component/CustomJFileChooserDb.java b/src/main/java/home/gui/component/CustomJFileChooserDb.java index c7f23b8..582cee3 100644 --- a/src/main/java/home/gui/component/CustomJFileChooserDb.java +++ b/src/main/java/home/gui/component/CustomJFileChooserDb.java @@ -1,8 +1,24 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component; import java.awt.Component; import java.io.File; import java.io.IOException; +import java.sql.SQLException; import java.util.Arrays; import javax.swing.JFileChooser; @@ -10,30 +26,17 @@ import javax.swing.filechooser.FileNameExtensionFilter; import home.Settings; -import home.db.DbInitializer; -import home.gui.IGuiConsts; +import home.db.conn.Connector; +import home.db.init.DbInitializer; +import home.gui.DbOperation; +import home.gui.GuiConst; +import home.gui.exception.CreateOpenSaveCancelException; import home.gui.exception.SaveAsCancelException; -import home.gui.exception.SaveAsToSameFileException; +import home.gui.exception.SaveToAlreadyExistsFileException; @SuppressWarnings("serial") public final class CustomJFileChooserDb extends JFileChooser { - public static enum ChooserDbOperation { - - CREATE_OR_OPEN(IGuiConsts.CREATE_OR_OPEN), - SAVE_AS(IGuiConsts.SAVE_AS); - - private String operationText; - - private ChooserDbOperation(String operationText) { - this.operationText = operationText; - } - - public String getOperatioText() { - return operationText; - } - } - private static final File APPLICATION_DIR = new File("."); private static final String EXTENSION_DESCRIPTIONS = "SQLite DB (*.db, *.sqlite, *.sqlite3)"; private static final String[] EXTENSIONS = { "db", "sqlite", "sqlite3" }; @@ -55,35 +58,42 @@ private CustomJFileChooserDb() { super(APPLICATION_DIR); } - public static void createAndShowChooser(Component parent, ChooserDbOperation operation) - throws IOException { + public static void createAndShowChooser(Component parent, DbOperation operation) + throws IOException, SQLException { var fileChooser = new CustomJFileChooserDb(); fileChooser.setFileFilter(new FileNameExtensionFilter( EXTENSION_DESCRIPTIONS, EXTENSIONS)); fileChooser.showChooser(parent, operation); } - private void showChooser(Component parent, ChooserDbOperation operation) throws IOException { + private void showChooser(Component parent, DbOperation operation) + throws IOException, SQLException { int chooserState = showDialog(parent, operation.getOperatioText()); if (JFileChooser.APPROVE_OPTION == chooserState) { //// [Create / Open] button pressed counterBeforeCreateDefaultFile = 0; File file = getSelectedFile(); file = addExtensionToFileIfNotExists(file); - if (ChooserDbOperation.SAVE_AS == operation) { - checkSaveAsFileLocation(file); + if (DbOperation.CREATE_OR_OPEN_FILE_DATABASE != operation) { + checkSaveToAlreadyExistsFile(file); } + Connector.resetConnectionDataAndSettings(); DbInitializer.createDbFileIfNotExists(file); - } else if (JFileChooser.APPROVE_OPTION != chooserState && ChooserDbOperation.SAVE_AS == operation) { + } else if (JFileChooser.APPROVE_OPTION != chooserState && DbOperation.SAVE_AS == operation) { //// [Cancel] button pressed while [Save as...] throw new SaveAsCancelException("Cancel SaveAs action."); - } else if (JFileChooser.APPROVE_OPTION != chooserState && !Settings.hasPathToDbFile()) { - //// [Cancel] button pressed during the action [Create / Open], + } else if (JFileChooser.APPROVE_OPTION != chooserState && !Settings.hasDatabase()) { + //// [Cancel] button pressed during the action [Create / Open] or [Save], //// while no database was selected before //// //// Condition of this block is necessary so that when entering the - //// [Create / Open] menu, you don't need to select database file, - //// if it already opened. + //// [Create / Open] or [Save] menu, you don't need to select database file, + //// if it already opened or if you don't want to do it. + + if (!isNeedToDoOperation(parent, operation)) { + //// If user don't want to do Create/Open/Save operation. + throw new CreateOpenSaveCancelException("Cancel %s action.".formatted(operation)); + } if (MAX_TRY_COUNT_BEFORE_CREATE_DEFAULT_FILE == counterBeforeCreateDefaultFile) { generateDefaultDbFile(parent); @@ -105,19 +115,26 @@ private File addExtensionToFileIfNotExists(File file) { return new File(file.getAbsolutePath() + DEFAULT_EXTENSION); } - private void checkSaveAsFileLocation(File file) { + private void checkSaveToAlreadyExistsFile(File file) { if (file.exists()) { - throw new SaveAsToSameFileException("File '" + throw new SaveToAlreadyExistsFileException("File '" + file.getAbsolutePath() + "' already exists."); } } - private void generateDefaultDbFile(Component parent) throws IOException { + private static boolean isNeedToDoOperation(Component parent, DbOperation operation) { + int dialogResult = JOptionPane.showConfirmDialog(parent, + GuiConst.OPERATION_CONFIRM_TEXT.formatted(operation.getOperatioText()), + GuiConst.OPERATION_CONFIRM_TITLE, JOptionPane.YES_NO_OPTION); + return dialogResult == JOptionPane.YES_OPTION; + } + + private void generateDefaultDbFile(Component parent) throws IOException, SQLException { String defaultFilePath = getCurrentDirectory().getAbsolutePath() + File.separator + DEFAULT_PREFIX + System.currentTimeMillis() + DEFAULT_EXTENSION; JOptionPane.showMessageDialog(parent, - String.format(WILL_CREATE_DEFAULT_STORAGE, defaultFilePath), + WILL_CREATE_DEFAULT_STORAGE.formatted(defaultFilePath), DEFAULT_STORAGE, JOptionPane.WARNING_MESSAGE); DbInitializer.createDbFileIfNotExists(new File(defaultFilePath)); } diff --git a/src/main/java/home/gui/component/CustomJFileChooserImpExp.java b/src/main/java/home/gui/component/CustomJFileChooserImpExp.java index 682c945..f5e1449 100644 --- a/src/main/java/home/gui/component/CustomJFileChooserImpExp.java +++ b/src/main/java/home/gui/component/CustomJFileChooserImpExp.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component; import java.awt.Component; @@ -26,11 +41,12 @@ import home.file.xml.XmlImporter; import home.gui.DataActionInGui; import home.model.AbstractVehicle; +import home.utils.ThreadUtil; @SuppressWarnings("serial") public final class CustomJFileChooserImpExp extends JFileChooser { - public static enum DataFormat { + public enum DataFormat { XML("xml", "XML (*.xml)"), YAML("yaml", "YAML (*.yaml)"), @@ -79,40 +95,44 @@ private void showChooser(Component parent, DataFormat dataFormat, return; } - File file = getSelectedFile(); - file = addExtensionToFileIfNotExists(file, dataFormat.getExtension()); - - if (isImport) { - IImporter importer = switch (dataFormat) { - case XML -> new XmlImporter(); - case YAML -> new YamlImporter(); - case JSON -> new JsonImporter(); - case CSV -> new CsvImporter(); - case BSER -> new BserImporter(); - case SER -> new SerImporter(); - }; - List dataObjs = importer.importDataObjsFromFile(file); - DataActionInGui.add(dataObjs); - } else { - IExporter exporter = switch (dataFormat) { - case XML -> new XmlExporter(); - case YAML -> new YamlExporter(); - case JSON -> new JsonExporter(); - case CSV -> new CsvExporter(); - case BSER -> new BserExporter(); - case SER -> new SerExporter(); - }; - - if (DataFormat.SER == dataFormat) { - ((SerExporter) exporter).exportAllDataObjsToFile(file); + ThreadUtil.runInThread(() -> { + Thread.currentThread().setName("-> export/import operation"); + + File file = getSelectedFile(); + file = addExtensionToFileIfNotExists(file, dataFormat.getExtension()); + + if (isImport) { + IImporter importer = switch (dataFormat) { + case XML -> new XmlImporter(); + case YAML -> new YamlImporter(); + case JSON -> new JsonImporter(); + case CSV -> new CsvImporter(); + case BSER -> new BserImporter(); + case SER -> new SerImporter(); + }; + List dataObjs = importer.importDataObjsFromFile(file); + DataActionInGui.add(dataObjs); } else { - String text = exporter.exportAllDataObjsToString(); - FileHandler.writeStringToFile(file, text); + IExporter exporter = switch (dataFormat) { + case XML -> new XmlExporter(); + case YAML -> new YamlExporter(); + case JSON -> new JsonExporter(); + case CSV -> new CsvExporter(); + case BSER -> new BserExporter(); + case SER -> new SerExporter(); + }; + + if (DataFormat.SER == dataFormat) { + ((SerExporter) exporter).exportAllDataObjsToFile(file); + } else { + String text = exporter.exportAllDataObjsToString(); + FileHandler.writeStringToFile(file, text); + } } - } - JOptionPane.showMessageDialog(parent, direction + " successfully", - direction, JOptionPane.INFORMATION_MESSAGE); + JOptionPane.showMessageDialog(parent, direction + " successfully", + direction, JOptionPane.INFORMATION_MESSAGE); + }); } private File addExtensionToFileIfNotExists(File file, String extension) { diff --git a/src/main/java/home/gui/component/CustomJFrame.java b/src/main/java/home/gui/component/CustomJFrame.java index 3b92fc2..fbf0bdf 100644 --- a/src/main/java/home/gui/component/CustomJFrame.java +++ b/src/main/java/home/gui/component/CustomJFrame.java @@ -1,9 +1,25 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component; import java.awt.BorderLayout; import java.awt.Dimension; import javax.swing.JFrame; +import javax.swing.SwingUtilities; @SuppressWarnings("serial") public final class CustomJFrame extends JFrame { @@ -30,4 +46,17 @@ public static CustomJFrame create(String title) { frame.setLayout(new BorderLayout(GAP, GAP)); return frame; } + + /** + * Creating and displaying a frame. When launched via + * "SwingUtilities.invokeLater(new Runnable(){...}" the frame will be created + * and displayed after all expected events have been processed, i.e. the frame + * will be created and displayed when all resources are ready. This is + * necessary, so that all elements are guaranteed to be displayed in the window + * (if you do "frame.setVisible(true)" from the main thread, then there is a + * chance that some element will not be displayed in the window). + */ + public void makeFrameVisible() { + SwingUtilities.invokeLater(() -> setVisible(true)); + } } \ No newline at end of file diff --git a/src/main/java/home/gui/component/CustomJLabel.java b/src/main/java/home/gui/component/CustomJLabel.java index 559000a..a9b7ba7 100644 --- a/src/main/java/home/gui/component/CustomJLabel.java +++ b/src/main/java/home/gui/component/CustomJLabel.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component; import java.awt.Color; @@ -9,15 +24,24 @@ public final class CustomJLabel extends JLabel { private static final String FONT_NAME = "Courier"; - private static final int FONT_SIZE = 14; + private static final int FONT_SIZE_NORMAL = 14; + private static final int FONT_SIZE_SMALL = 12; private CustomJLabel(String text) { super(text); } public static CustomJLabel create(String text) { + return create(text, FONT_SIZE_NORMAL); + } + + public static CustomJLabel createSmall(String text) { + return create(text, FONT_SIZE_SMALL); + } + + private static CustomJLabel create(String text, int fontSize) { var label = new CustomJLabel(text); - label.setFont(new Font(FONT_NAME, Font.BOLD, FONT_SIZE)); + label.setFont(new Font(FONT_NAME, Font.BOLD, fontSize)); label.setForeground(Color.BLACK); return label; } diff --git a/src/main/java/home/gui/component/CustomJPanel.java b/src/main/java/home/gui/component/CustomJPanel.java index a114799..676bc15 100644 --- a/src/main/java/home/gui/component/CustomJPanel.java +++ b/src/main/java/home/gui/component/CustomJPanel.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component; import java.awt.BorderLayout; @@ -9,91 +24,56 @@ import javax.swing.JPanel; import javax.swing.border.EmptyBorder; -@SuppressWarnings("serial") -public final class CustomJPanel extends JPanel { +import home.gui.component.CustomJPanel.PanelType; + +public final class CustomJPanel { public enum PanelType { - FRAME_TABLE_PANEL, - FRAME_BUTTON_PANEL, - DIALOG_TEXT_FIELDS_PANEL, - DIALOG_BUTTON_PANEL - } + // main window panels + MAIN_FRAME_TABLE_PANEL, + MAIN_FRAME_BUTTON_PANEL, - // main table panel sizes - public static final int FRAME_TBL_PANEL_PREF_WIDTH = 302; - public static final int FRAME_TBL_PANEL_PREF_HEIGHT = 402; - public static final int FRAME_TBL_PANEL_MIN_WIDTH = 202; - public static final int FRAME_TBL_PANEL_MIN_HEIGHT = 102; - public static final int FRAME_TBL_PANEL_BORDER_LAYOUT_GAP = 2; + // vehicle dialog panels + VEHICLE_DIALOG_TEXT_FIELDS_PANEL, + VEHICLE_DIALOG_BUTTON_PANEL, - // main button panel sizes - public static final int FRAME_BTN_PANEL_PREF_WIDTH = 152; - public static final int FRAME_BTN_PANEL_PREF_HEIGHT = 402; - public static final int FRAME_BTN_PANEL_MIN_WIDTH = 102; - public static final int FRAME_BTN_PANEL_MIN_HEIGHT = 102; - public static final int FRAME_BTN_PANEL_GRID_LAYOUT_ROWS = 8; - public static final int FRAME_BTN_PANEL_GRID_LAYOUT_COLUMNS = 1; - public static final int FRAME_BTN_PANEL_GRID_LAYOUT_GAP = 10; - - // dialog text fields panel sizes - public static final int DIALOG_TXT_FIELDS_PANEL_WIDTH = 450; - public static final int DIALOG_TXT_FIELDS_PANEL_HEIGHT = 300; - public static final int DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_ROWS = 8; - public static final int DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_COLUMNS = 1; - public static final int DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_GAP = 10; - - // dialog button panel sizes - public static final int DIALOG_BTN_PANEL_WIDTH = 450; - public static final int DIALOG_BTN_PANEL_HEIGHT = 50; - public static final int DIALOG_BTN_PANEL_FLOW_LAYOUT_H_GAP = 10; - public static final int DIALOG_BTN_PANEL_FLOW_LAYOUT_V_GAP = 2; + // connection dialog panels + CONNECTION_DIALOG_TEXT_FIELDS_PANEL, + CONNECTION_DIALOG_BUTTON_PANEL; + } - public static final int EMPTY_BORDER_SIZE = 10; + public static AbstractCustomJPanel create(PanelType panelType) { + AbstractCustomJPanel panel = switch (panelType) { + case MAIN_FRAME_TABLE_PANEL -> new MainFrameTableJPanel(); + case MAIN_FRAME_BUTTON_PANEL -> new MainFrameButtonJPanel(); + case VEHICLE_DIALOG_TEXT_FIELDS_PANEL -> new VehicleDialogTextFieldsJPanel(); + case VEHICLE_DIALOG_BUTTON_PANEL -> new VehicleDialogButtonJPanel(); + case CONNECTION_DIALOG_TEXT_FIELDS_PANEL -> new ConnectionDialogTextFieldsJPanel(); + case CONNECTION_DIALOG_BUTTON_PANEL -> new ConnectionDialogButtonJPanel(); + }; + panel.fillPanelParams(); + return panel; + } private CustomJPanel() { } +} - public static CustomJPanel create(PanelType panelType) { - var panel = new CustomJPanel(); - panel.setPanelParams(panelType); - return panel; - } +@SuppressWarnings("serial") +abstract sealed class AbstractCustomJPanel extends + JPanel permits MainFrameTableJPanel, MainFrameButtonJPanel, + VehicleDialogTextFieldsJPanel, VehicleDialogButtonJPanel, + ConnectionDialogTextFieldsJPanel, ConnectionDialogButtonJPanel { + + private static final int EMPTY_BORDER_SIZE = 10; - private void setPanelParams(PanelType panelType) { - String panelName = panelType.name(); - switch (panelType) { - case FRAME_TABLE_PANEL -> setPanelParams(panelName, - FRAME_TBL_PANEL_PREF_WIDTH, FRAME_TBL_PANEL_PREF_HEIGHT, - FRAME_TBL_PANEL_MIN_WIDTH, FRAME_TBL_PANEL_MIN_HEIGHT, - new BorderLayout(FRAME_TBL_PANEL_BORDER_LAYOUT_GAP, - FRAME_TBL_PANEL_BORDER_LAYOUT_GAP)); - - case FRAME_BUTTON_PANEL -> setPanelParams(panelName, - FRAME_BTN_PANEL_PREF_WIDTH, FRAME_BTN_PANEL_PREF_HEIGHT, - FRAME_BTN_PANEL_MIN_WIDTH, FRAME_BTN_PANEL_MIN_HEIGHT, - new GridLayout(FRAME_BTN_PANEL_GRID_LAYOUT_ROWS, - FRAME_BTN_PANEL_GRID_LAYOUT_COLUMNS, - FRAME_BTN_PANEL_GRID_LAYOUT_GAP, - FRAME_BTN_PANEL_GRID_LAYOUT_GAP)); - - case DIALOG_TEXT_FIELDS_PANEL -> setPanelParams(panelName, - DIALOG_TXT_FIELDS_PANEL_WIDTH, DIALOG_TXT_FIELDS_PANEL_HEIGHT, - new GridLayout(DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_ROWS, - DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_COLUMNS, - DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_GAP, - DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_GAP)); - - case DIALOG_BUTTON_PANEL -> setPanelParams(panelName, - DIALOG_BTN_PANEL_WIDTH, DIALOG_BTN_PANEL_HEIGHT, - new FlowLayout(FlowLayout.CENTER, - DIALOG_BTN_PANEL_FLOW_LAYOUT_H_GAP, - DIALOG_BTN_PANEL_FLOW_LAYOUT_V_GAP)); - } - } - - private void setPanelParams(String name, int width, int height, + protected abstract void fillPanelParams(); + + protected abstract PanelType getPanelType(); + + protected void setPanelParams(int width, int height, int minWidth, int minHeight, LayoutManager layout) { - setName(name); + setName(getPanelType().name()); setSize(width, height); setPreferredSize(new Dimension(width, height)); setMinimumSize(new Dimension(minWidth, minHeight)); @@ -102,7 +82,155 @@ private void setPanelParams(String name, int width, int height, EMPTY_BORDER_SIZE, EMPTY_BORDER_SIZE)); } - private void setPanelParams(String name, int width, int height, LayoutManager layout) { - setPanelParams(name, width, height, width, height, layout); + protected void setPanelParams(int width, int height, LayoutManager layout) { + setPanelParams(width, height, width, height, layout); + } +} + +@SuppressWarnings("serial") +final class MainFrameTableJPanel extends AbstractCustomJPanel { + + // main table panel sizes + private static final int FRAME_TBL_PANEL_PREF_WIDTH = 300; + private static final int FRAME_TBL_PANEL_PREF_HEIGHT = 400; + private static final int FRAME_TBL_PANEL_MIN_WIDTH = 200; + private static final int FRAME_TBL_PANEL_MIN_HEIGHT = 100; + private static final int FRAME_TBL_PANEL_BORDER_LAYOUT_GAP = 2; + + @Override + protected PanelType getPanelType() { + return PanelType.MAIN_FRAME_TABLE_PANEL; + } + + @Override + protected void fillPanelParams() { + setPanelParams(FRAME_TBL_PANEL_PREF_WIDTH, FRAME_TBL_PANEL_PREF_HEIGHT, + FRAME_TBL_PANEL_MIN_WIDTH, FRAME_TBL_PANEL_MIN_HEIGHT, + new BorderLayout(FRAME_TBL_PANEL_BORDER_LAYOUT_GAP, + FRAME_TBL_PANEL_BORDER_LAYOUT_GAP)); + } +} + +@SuppressWarnings("serial") +final class MainFrameButtonJPanel extends AbstractCustomJPanel { + + // main button panel sizes + private static final int FRAME_BTN_PANEL_PREF_WIDTH = 150; + private static final int FRAME_BTN_PANEL_PREF_HEIGHT = 400; + private static final int FRAME_BTN_PANEL_MIN_WIDTH = 100; + private static final int FRAME_BTN_PANEL_MIN_HEIGHT = 100; + private static final int FRAME_BTN_PANEL_GRID_LAYOUT_ROWS = 8; + private static final int FRAME_BTN_PANEL_GRID_LAYOUT_COLUMNS = 1; + private static final int FRAME_BTN_PANEL_GRID_LAYOUT_GAP = 10; + + @Override + protected PanelType getPanelType() { + return PanelType.MAIN_FRAME_BUTTON_PANEL; + } + + @Override + protected void fillPanelParams() { + setPanelParams(FRAME_BTN_PANEL_PREF_WIDTH, FRAME_BTN_PANEL_PREF_HEIGHT, + FRAME_BTN_PANEL_MIN_WIDTH, FRAME_BTN_PANEL_MIN_HEIGHT, + new GridLayout(FRAME_BTN_PANEL_GRID_LAYOUT_ROWS, + FRAME_BTN_PANEL_GRID_LAYOUT_COLUMNS, + FRAME_BTN_PANEL_GRID_LAYOUT_GAP, + FRAME_BTN_PANEL_GRID_LAYOUT_GAP)); } } + +@SuppressWarnings("serial") +final class VehicleDialogTextFieldsJPanel extends AbstractCustomJPanel { + + // vehicle dialog text fields panel sizes + private static final int DIALOG_TXT_FIELDS_PANEL_WIDTH = 450; + private static final int DIALOG_TXT_FIELDS_PANEL_HEIGHT = 300; + private static final int DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_ROWS = 8; + private static final int DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_COLUMNS = 1; + private static final int DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_GAP = 10; + + @Override + protected PanelType getPanelType() { + return PanelType.VEHICLE_DIALOG_TEXT_FIELDS_PANEL; + } + + @Override + protected void fillPanelParams() { + setPanelParams(DIALOG_TXT_FIELDS_PANEL_WIDTH, DIALOG_TXT_FIELDS_PANEL_HEIGHT, + new GridLayout(DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_ROWS, + DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_COLUMNS, + DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_GAP, + DIALOG_TXT_FIELDS_PANEL_GRID_LAYOUT_GAP)); + } +} + +@SuppressWarnings("serial") +final class VehicleDialogButtonJPanel extends AbstractCustomJPanel { + + // vehicle dialog button panel sizes + public static final int DIALOG_BTN_PANEL_WIDTH = 450; + public static final int DIALOG_BTN_PANEL_HEIGHT = 50; + public static final int DIALOG_BTN_PANEL_FLOW_LAYOUT_H_GAP = 10; + public static final int DIALOG_BTN_PANEL_FLOW_LAYOUT_V_GAP = 2; + + @Override + protected PanelType getPanelType() { + return PanelType.VEHICLE_DIALOG_BUTTON_PANEL; + } + + @Override + protected void fillPanelParams() { + setPanelParams(DIALOG_BTN_PANEL_WIDTH, DIALOG_BTN_PANEL_HEIGHT, + new FlowLayout(FlowLayout.CENTER, + DIALOG_BTN_PANEL_FLOW_LAYOUT_H_GAP, + DIALOG_BTN_PANEL_FLOW_LAYOUT_V_GAP)); + } +} + +@SuppressWarnings("serial") +final class ConnectionDialogTextFieldsJPanel extends AbstractCustomJPanel { + + // connection dialog text fields panel sizes + private static final int CONNECTION_TXT_FIELDS_PANEL_WIDTH = 450; + private static final int CONNECTION_TXT_FIELDS_PANEL_HEIGHT = 600; + private static final int CONNECTION_TXT_FIELDS_PANEL_GRID_LAYOUT_ROWS = 13; + private static final int CONNECTION_TXT_FIELDS_PANEL_GRID_LAYOUT_COLUMNS = 2; + private static final int CONNECTION_TXT_FIELDS_PANEL_GRID_LAYOUT_GAP = 10; + + @Override + protected PanelType getPanelType() { + return PanelType.CONNECTION_DIALOG_TEXT_FIELDS_PANEL; + } + + @Override + protected void fillPanelParams() { + setPanelParams(CONNECTION_TXT_FIELDS_PANEL_WIDTH, CONNECTION_TXT_FIELDS_PANEL_HEIGHT, + new GridLayout(CONNECTION_TXT_FIELDS_PANEL_GRID_LAYOUT_ROWS, + CONNECTION_TXT_FIELDS_PANEL_GRID_LAYOUT_COLUMNS, + CONNECTION_TXT_FIELDS_PANEL_GRID_LAYOUT_GAP, + CONNECTION_TXT_FIELDS_PANEL_GRID_LAYOUT_GAP)); + } +} + +@SuppressWarnings("serial") +final class ConnectionDialogButtonJPanel extends AbstractCustomJPanel { + + // connection dialog button panel sizes + private static final int CONNECTION_BTN_PANEL_WIDTH = 450; + private static final int CONNECTION_BTN_PANEL_HEIGHT = 50; + private static final int CONNECTION_BTN_PANEL_FLOW_LAYOUT_H_GAP = 10; + private static final int CONNECTION_BTN_PANEL_FLOW_LAYOUT_V_GAP = 2; + + @Override + protected PanelType getPanelType() { + return PanelType.CONNECTION_DIALOG_BUTTON_PANEL; + } + + @Override + protected void fillPanelParams() { + setPanelParams(CONNECTION_BTN_PANEL_WIDTH, CONNECTION_BTN_PANEL_HEIGHT, + new FlowLayout(FlowLayout.CENTER, + CONNECTION_BTN_PANEL_FLOW_LAYOUT_H_GAP, + CONNECTION_BTN_PANEL_FLOW_LAYOUT_V_GAP)); + } +} \ No newline at end of file diff --git a/src/main/java/home/gui/component/CustomJPasswordField.java b/src/main/java/home/gui/component/CustomJPasswordField.java new file mode 100644 index 0000000..223ce16 --- /dev/null +++ b/src/main/java/home/gui/component/CustomJPasswordField.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui.component; + +import java.awt.Color; +import java.awt.Font; + +import javax.swing.JPasswordField; + +@SuppressWarnings("serial") +public final class CustomJPasswordField extends JPasswordField { + + private static final String FONT_NAME = "Courier"; + private static final int FONT_SIZE = 14; + private static final char ECHO_CHARACTER_TO_DISPLAY = '*'; + + private CustomJPasswordField() { + } + + public static CustomJPasswordField create(int columns) { + var passwordFiled = new CustomJPasswordField(); + passwordFiled.setColumns(columns); + passwordFiled.setFont(new Font(FONT_NAME, Font.PLAIN, FONT_SIZE)); + passwordFiled.setForeground(Color.BLACK); + passwordFiled.setEchoChar(ECHO_CHARACTER_TO_DISPLAY); + return passwordFiled; + } + + public void showPassword(boolean showPass) { + if (showPass) { + setEchoChar((char) 0); + } else { + setEchoChar(ECHO_CHARACTER_TO_DISPLAY); + } + } +} diff --git a/src/main/java/home/gui/component/CustomJTable.java b/src/main/java/home/gui/component/CustomJTable.java index 66f8e74..21409dd 100644 --- a/src/main/java/home/gui/component/CustomJTable.java +++ b/src/main/java/home/gui/component/CustomJTable.java @@ -1,11 +1,25 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component; import javax.swing.JTable; import javax.swing.ListSelectionModel; +import javax.swing.table.AbstractTableModel; import javax.swing.table.TableColumn; -import home.Storage; - @SuppressWarnings("serial") public final class CustomJTable extends JTable { @@ -21,12 +35,13 @@ public final class CustomJTable extends JTable { private CustomJTable() { } - public static CustomJTable create() { + public static CustomJTable create(AbstractTableModel tableDataModel, + boolean isAutoResizeTableWidth) { var table = new CustomJTable(); - table.setModel(new CustomJTableDataModel(Storage.INSTANCE.getAll())); + table.setModel(tableDataModel); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); - // table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + table.setAutoResize(isAutoResizeTableWidth); table.setColMinWidth(CustomJTableDataModel.TYPE_COL_IDX, TYPE_MIN_WIDTH); table.setColMinWidth(CustomJTableDataModel.COLOR_COL_IDX, COLOR_MIN_WIDTH); @@ -50,4 +65,12 @@ private void setColWidths(int colPosition, int minWidth, int maxWidth, int prefW tblCol.setMaxWidth(maxWidth); tblCol.setPreferredWidth(prefWidth); } + + public void setAutoResize(boolean isAutoResizeTableWidth) { + if (isAutoResizeTableWidth) { + setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + } else { + setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + } + } } diff --git a/src/main/java/home/gui/component/CustomJTableDataModel.java b/src/main/java/home/gui/component/CustomJTableDataModel.java index 2b17db8..831ce37 100644 --- a/src/main/java/home/gui/component/CustomJTableDataModel.java +++ b/src/main/java/home/gui/component/CustomJTableDataModel.java @@ -1,16 +1,31 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component; import java.util.List; import javax.swing.table.AbstractTableModel; -import home.IConsts; -import home.gui.IGuiConsts; +import home.Const; +import home.gui.GuiConst; import home.model.AbstractVehicle; import home.utils.Utils; @SuppressWarnings("serial") -final class CustomJTableDataModel extends AbstractTableModel { +public final class CustomJTableDataModel extends AbstractTableModel { private static final int COLUMNS_COUNT = 5; @@ -22,7 +37,7 @@ final class CustomJTableDataModel extends AbstractTableModel { private final List dataObjs; - CustomJTableDataModel(List dataObjs) { + public CustomJTableDataModel(List dataObjs) { this.dataObjs = dataObjs; } @@ -46,12 +61,12 @@ public int getColumnCount() { @Override public String getColumnName(int columnIndex) { String columnName = switch (columnIndex) { - case TYPE_COL_IDX -> IGuiConsts.TYPE; - case COLOR_COL_IDX -> IGuiConsts.COLOR; - case NUMBER_COL_IDX -> IGuiConsts.NUMBER; - case DATE_COL_IDX -> IGuiConsts.DATE; - case DEL_MARK_COL_IDX -> IGuiConsts.DELETION_MARK; - default -> IConsts.EMPTY_STRING; + case TYPE_COL_IDX -> GuiConst.TYPE; + case COLOR_COL_IDX -> GuiConst.COLOR; + case NUMBER_COL_IDX -> GuiConst.NUMBER; + case DATE_COL_IDX -> GuiConst.DATE; + case DEL_MARK_COL_IDX -> GuiConst.DELETION_MARK; + default -> Const.EMPTY_STRING; }; return columnName; } @@ -71,7 +86,7 @@ public Object getValueAt(int rowIndex, int columnIndex) { case NUMBER_COL_IDX -> dataObj.getNumber(); case DATE_COL_IDX -> Utils.getFormattedDate(dataObj.getDateTime()); case DEL_MARK_COL_IDX -> dataObj.isMarkedForDelete(); - default -> IConsts.EMPTY_STRING; + default -> Const.EMPTY_STRING; }; return cellValue; diff --git a/src/main/java/home/gui/component/CustomJTextField.java b/src/main/java/home/gui/component/CustomJTextField.java index ca39b55..fbac371 100644 --- a/src/main/java/home/gui/component/CustomJTextField.java +++ b/src/main/java/home/gui/component/CustomJTextField.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component; import java.awt.Color; diff --git a/src/main/java/home/gui/component/CustomJXDatePicker.java b/src/main/java/home/gui/component/CustomJXDatePicker.java index fb0d88a..0b80db4 100644 --- a/src/main/java/home/gui/component/CustomJXDatePicker.java +++ b/src/main/java/home/gui/component/CustomJXDatePicker.java @@ -1,3 +1,25 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2012 Charlie Hubbard + * + * Distributed under LGPLv3. License can be found here: + * http://www.gnu.org/licenses/lgpl-3.0.txt + * This is provided as is. If you have questions please direct them to + * charlie.hubbard at gmail dot you know what. + *******************************************************************************/ package home.gui.component; import java.awt.Color; @@ -19,15 +41,8 @@ import org.jdesktop.swingx.JXDatePicker; import org.jdesktop.swingx.calendar.SingleDaySelectionModel; -import home.gui.IGuiConsts; +import home.gui.GuiConst; -/** - * This is licensed under LGPL. License can be found here: - * http://www.gnu.org/licenses/lgpl-3.0.txt - * - * This is provided as is. If you have questions please direct them to - * charlie.hubbard at gmail dot you know what. - */ @SuppressWarnings("serial") public final class CustomJXDatePicker extends JXDatePicker { @@ -41,7 +56,7 @@ private CustomJXDatePicker() { public static CustomJXDatePicker creat(Date date) { var datePicker = new CustomJXDatePicker(); datePicker.getMonthView().setSelectionModel(new SingleDaySelectionModel()); - datePicker.setFormats(IGuiConsts.DATE_FORMAT); + datePicker.setFormats(GuiConst.DATE_FORMAT); datePicker.setTimeFormat(DateFormat.getTimeInstance(DateFormat.MEDIUM)); datePicker.setDate(date); return datePicker; diff --git a/src/main/java/home/gui/component/dialog/AbstractCustomJDialog.java b/src/main/java/home/gui/component/dialog/AbstractCustomJDialog.java index 7b7a349..77a0b4a 100644 --- a/src/main/java/home/gui/component/dialog/AbstractCustomJDialog.java +++ b/src/main/java/home/gui/component/dialog/AbstractCustomJDialog.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component.dialog; import java.awt.BorderLayout; @@ -9,11 +24,12 @@ import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; import javax.swing.WindowConstants; @SuppressWarnings("serial") abstract sealed class AbstractCustomJDialog - extends JDialog permits AbstractDialog { + extends JDialog permits AbstractDialogVehicle, DialogDbConnection { private static final int GAP_BETWEEN_COMPONENTS = 2; @@ -52,8 +68,25 @@ private void addHotKeyForClose() { @Override public void actionPerformed(ActionEvent e) { - dispose(); + actionOnHotKeyForClose(); } }); } + + protected void actionOnHotKeyForClose() { + dispose(); + } + + /** + * Creating and displaying a dialog. When launched via + * "SwingUtilities.invokeLater(new Runnable(){...}" the dialog will be created + * and displayed after all expected events have been processed, i.e. the dialog + * will be created and displayed when all resources are ready. This is + * necessary, so that all elements are guaranteed to be displayed in the window + * (if you do "setVisible(true)" from the main thread, then there is a chance + * that some element will not be displayed in the window). + */ + protected void makeDialogVisible() { + SwingUtilities.invokeLater(() -> setVisible(true)); + } } diff --git a/src/main/java/home/gui/component/dialog/AbstractDialogTrailer.java b/src/main/java/home/gui/component/dialog/AbstractDialogTrailer.java index 113e3cd..52eacde 100644 --- a/src/main/java/home/gui/component/dialog/AbstractDialogTrailer.java +++ b/src/main/java/home/gui/component/dialog/AbstractDialogTrailer.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component.dialog; import javax.swing.JCheckBox; -import home.gui.IGuiConsts; +import home.gui.GuiConst; import home.model.AbstractVehicle; import home.model.AbstractVehicleWithTrailer; @SuppressWarnings("serial") abstract sealed class AbstractDialogTrailer - extends AbstractDialog permits DialogCar,DialogTruck { + extends AbstractDialogVehicle permits DialogCar, DialogTruck { private JCheckBox chkHasTrailer; @@ -20,7 +35,7 @@ protected AbstractDialogTrailer(String title, int width, int height, @Override protected void createDataComponents() { super.createDataComponents(); - chkHasTrailer = new JCheckBox(IGuiConsts.HAS_TRAILER); + chkHasTrailer = new JCheckBox(GuiConst.HAS_TRAILER); if (!isNewDataObj) { chkHasTrailer.setSelected(((AbstractVehicleWithTrailer) dataObj).hasTrailer()); diff --git a/src/main/java/home/gui/component/dialog/AbstractDialog.java b/src/main/java/home/gui/component/dialog/AbstractDialogVehicle.java similarity index 72% rename from src/main/java/home/gui/component/dialog/AbstractDialog.java rename to src/main/java/home/gui/component/dialog/AbstractDialogVehicle.java index 3db5946..d6efa2a 100644 --- a/src/main/java/home/gui/component/dialog/AbstractDialog.java +++ b/src/main/java/home/gui/component/dialog/AbstractDialogVehicle.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component.dialog; import java.awt.BorderLayout; @@ -8,12 +23,11 @@ import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; -import javax.swing.SwingUtilities; import org.jdesktop.swingx.JXDatePicker; import home.gui.DataActionInGui; -import home.gui.IGuiConsts; +import home.gui.GuiConst; import home.gui.component.CustomJButton; import home.gui.component.CustomJLabel; import home.gui.component.CustomJPanel; @@ -23,8 +37,8 @@ import home.model.AbstractVehicle; @SuppressWarnings("serial") -public abstract sealed class AbstractDialog - extends AbstractCustomJDialog permits AbstractDialogTrailer,DialogMotorcycle { +public abstract sealed class AbstractDialogVehicle + extends AbstractCustomJDialog permits AbstractDialogTrailer, DialogMotorcycle { private static final int TEXT_FIELD_COLUMN_NUMBERS = 9; @@ -46,7 +60,7 @@ public abstract sealed class AbstractDialog protected JPanel panelTextFields; private JPanel panelButtons; - public AbstractDialog(String title, int width, int height, + public AbstractDialogVehicle(String title, int width, int height, AbstractVehicle dataObj, int tblRowOfSelectedDataObj) { super(title, width, height); this.tblRowOfSelectedDataObj = tblRowOfSelectedDataObj; @@ -72,9 +86,9 @@ public void buildDialog() { } protected void createDataComponents() { - lblColor = CustomJLabel.create(IGuiConsts.COLOR); - lblNumber = CustomJLabel.create(IGuiConsts.NUMBER); - lblDate = CustomJLabel.create(IGuiConsts.DATE); + lblColor = CustomJLabel.create(GuiConst.COLOR); + lblNumber = CustomJLabel.create(GuiConst.NUMBER); + lblDate = CustomJLabel.create(GuiConst.DATE); tfColor = CustomJTextField.create(TEXT_FIELD_COLUMN_NUMBERS); tfNumber = CustomJTextField.create(TEXT_FIELD_COLUMN_NUMBERS); @@ -89,7 +103,7 @@ protected void createDataComponents() { } private void createButtons() { - btnSave = CustomJButton.create(IGuiConsts.OK); + btnSave = CustomJButton.create(GuiConst.OK); btnSave.addActionListener(actionEvent -> { fillDataObj(); if (checkObjFilling()) { @@ -97,12 +111,12 @@ private void createButtons() { dispose(); } }); - btnCancel = CustomJButton.create(IGuiConsts.CANCEL); + btnCancel = CustomJButton.create(GuiConst.CANCEL); btnCancel.addActionListener(actionEvent -> dispose()); } protected void createPanels() { - panelTextFields = CustomJPanel.create(PanelType.DIALOG_TEXT_FIELDS_PANEL); + panelTextFields = CustomJPanel.create(PanelType.VEHICLE_DIALOG_TEXT_FIELDS_PANEL); panelTextFields.add(lblColor); panelTextFields.add(tfColor); panelTextFields.add(lblNumber); @@ -110,7 +124,7 @@ protected void createPanels() { panelTextFields.add(lblDate); panelTextFields.add(tfDate); - panelButtons = CustomJPanel.create(PanelType.DIALOG_BUTTON_PANEL); + panelButtons = CustomJPanel.create(PanelType.VEHICLE_DIALOG_BUTTON_PANEL); panelButtons.add(btnSave); panelButtons.add(btnCancel); } @@ -121,19 +135,6 @@ private void createDialog() { makeDialogVisible(); } - /** - * Creating and displaying a form. When launched via - * "SwingUtilities.invokeLater(new Runnable(){...}" the dialog will be created - * and displayed after all expected events have been processed, i.e. the dialog - * will be created and displayed when all resources are ready. This is - * necessary, so that all elements are guaranteed to be displayed in the window - * (if you do "setVisible(true)" from the main thread, then there is a chance - * that some element will not be displayed in the window). - */ - private void makeDialogVisible() { - SwingUtilities.invokeLater(() -> setVisible(true)); - } - protected void fillDataObj() { dataObj.setColor(tfColor.getText()); dataObj.setNumber(tfNumber.getText()); diff --git a/src/main/java/home/gui/component/dialog/DialogCar.java b/src/main/java/home/gui/component/dialog/DialogCar.java index 20446f1..2f7d45e 100644 --- a/src/main/java/home/gui/component/dialog/DialogCar.java +++ b/src/main/java/home/gui/component/dialog/DialogCar.java @@ -1,8 +1,23 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component.dialog; import javax.swing.JCheckBox; -import home.gui.IGuiConsts; +import home.gui.GuiConst; import home.model.AbstractVehicle; import home.model.Car; import home.model.VehicleType; @@ -19,7 +34,7 @@ public DialogCar(int width, int height, AbstractVehicle dataObj, int tblRowOfSel @Override protected void createDataComponents() { super.createDataComponents(); - chkPassengers = new JCheckBox(IGuiConsts.TRANSPORTS_PASSENGERS); + chkPassengers = new JCheckBox(GuiConst.TRANSPORTS_PASSENGERS); if (!isNewDataObj) { chkPassengers.setSelected(((Car) dataObj).isTransportsPassengers()); diff --git a/src/main/java/home/gui/component/dialog/DialogDbConnection.java b/src/main/java/home/gui/component/dialog/DialogDbConnection.java new file mode 100644 index 0000000..f18de2c --- /dev/null +++ b/src/main/java/home/gui/component/dialog/DialogDbConnection.java @@ -0,0 +1,315 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui.component.dialog; + +import java.awt.BorderLayout; +import java.awt.event.ItemEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.IOException; +import java.sql.SQLException; +import java.util.EnumSet; + +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import home.Const; +import home.Settings; +import home.Settings.Setting; +import home.db.DbType; +import home.db.conn.Connector; +import home.gui.GuiConst; +import home.gui.component.CustomJButton; +import home.gui.component.CustomJCheckBox; +import home.gui.component.CustomJComboBox; +import home.gui.component.CustomJLabel; +import home.gui.component.CustomJPanel; +import home.gui.component.CustomJPanel.PanelType; +import home.gui.component.CustomJPasswordField; +import home.gui.component.CustomJTextField; +import home.utils.ThreadUtil; + +@SuppressWarnings("serial") +public final class DialogDbConnection extends AbstractCustomJDialog { + + private enum ProcessingType { + + SAVE_AND_CONNECT(GuiConst.SAVE_AND_CONNECT), + TEST(GuiConst.TEST_CONNECTION); + + private final String title; + + private ProcessingType(String title) { + this.title = title; + } + + private String getTitle() { + return title; + } + } + + private static final int OBJ_DIALOG_WIDTH = 450; + private static final int OBJ_DIALOG_HEIGHT = 600; + + private static final int TEXT_FIELD_COLUMN_NUMBERS = 9; + + private static final String DEFAULT_HOST = "127.0.0.1"; + private static final String DEFAULT_POST = "5432"; + + private JLabel lblHost; + private JLabel lblPort; + private JLabel lblDbName; + private JLabel lblUser; + private JLabel lblPass; + private JLabel lblDbType; + + private JTextField tfHost; + private JTextField tfPort; + private JTextField tfDbName; + private JTextField tfUser; + private CustomJPasswordField tfPass; + private JCheckBox cbShowPass; + private JComboBox cbDbType; + + private JButton btnTestConnection; + private JButton btnSaveConnection; + private JButton btnCancel; + + protected JPanel panelTextFields; + private JPanel panelButtons; + + private final Runnable additionalActionsOnSave; + private WindowAdapter additionalActionsOnSaveListener; + + public DialogDbConnection(String title, Runnable additionalActionsOnSave) { + super(title, OBJ_DIALOG_WIDTH, OBJ_DIALOG_HEIGHT); + this.additionalActionsOnSave = additionalActionsOnSave; + } + + public void buildDialog() { + init(); + + createDataComponents(); + createButtons(); + createPanels(); + createDialog(); + + fillDataComponents(); + addListeners(); + } + + private void createDataComponents() { + lblHost = CustomJLabel.create(GuiConst.HOST); + lblPort = CustomJLabel.create(GuiConst.PORT); + lblDbName = CustomJLabel.create(GuiConst.DB_NAME); + lblUser = CustomJLabel.create(GuiConst.USER); + lblPass = CustomJLabel.create(GuiConst.PASS); + lblDbType = CustomJLabel.create(GuiConst.DB_TYPE); + + tfHost = CustomJTextField.create(TEXT_FIELD_COLUMN_NUMBERS); + tfPort = CustomJTextField.create(TEXT_FIELD_COLUMN_NUMBERS); + tfDbName = CustomJTextField.create(TEXT_FIELD_COLUMN_NUMBERS); + tfUser = CustomJTextField.create(TEXT_FIELD_COLUMN_NUMBERS); + tfPass = CustomJPasswordField.create(TEXT_FIELD_COLUMN_NUMBERS); + cbShowPass = CustomJCheckBox.create(GuiConst.SHOW_PASS, + itemEvent -> tfPass.showPassword(itemEvent.getStateChange() == ItemEvent.SELECTED)); + cbDbType = CustomJComboBox.create(EnumSet.of(DbType.PostgreSQL)); + } + + private void createButtons() { + btnTestConnection = CustomJButton.create(ProcessingType.TEST.getTitle()); + btnTestConnection.addActionListener(actionEvent -> processConnSettings(ProcessingType.TEST)); + btnSaveConnection = CustomJButton.create(ProcessingType.SAVE_AND_CONNECT.getTitle()); + btnSaveConnection.addActionListener(actionEvent -> { + if (processConnSettings(ProcessingType.SAVE_AND_CONNECT)) { + dispose(); + } + }); + btnCancel = CustomJButton.create(GuiConst.CANCEL); + btnCancel.addActionListener(actionEvent -> closeDialogWithoutAdditionalActions()); + } + + private boolean processConnSettings(ProcessingType settingsProcessingType) { + try { + String host = tfHost.getText().strip(); + String port = tfPort.getText().strip(); + String dbName = tfDbName.getText().strip(); + String user = tfUser.getText().strip(); + String pass = new String(tfPass.getPassword()); + String dbTypeStr = cbDbType.getItemAt(cbDbType.getSelectedIndex()); + + checkFields(host, port, dbName, user, pass, dbTypeStr); + + DbType dbType = DbType.getDbType(dbTypeStr); + + switch (settingsProcessingType) { + case SAVE_AND_CONNECT -> { + Connector.resetConnectionDataAndSettings(); + Settings.saveDbConnSettings(host, port, dbName, user, pass, dbType.name()); + } + case TEST -> { + if (Connector.testConnection(host, Integer.parseInt(port), dbName, user, pass, dbType)) { + JOptionPane.showMessageDialog(null, GuiConst.CONNECTION_TEST_SUCCESSFUL_TEXT, + GuiConst.CONNECTION_TEST_SUCCESSFUL_TITLE, JOptionPane.INFORMATION_MESSAGE); + } + } + } + + return true; + } catch (SQLException | IOException e) { + JOptionPane.showMessageDialog(this, + "Connection error:\n%s".formatted(e.getMessage()), + "Error", JOptionPane.ERROR_MESSAGE); + return false; + } + } + + private void checkFields(String host, String port, String dbName, String user, + String pass, String dbType) throws SQLException { + if (hasEmptyFields(host, port, dbName, user, pass, dbType)) { + throw new SQLException("Not all fields are filled."); + } + + try { + Integer.parseInt(port); + } catch (NumberFormatException e) { + throw new SQLException("Incorrect value of parameter %s: %s" + .formatted(Setting.PORT, port)); + } + } + + public boolean hasEmptyFields() { + return hasEmptyFields(tfHost.getText().strip(), tfPort.getText().strip(), + tfDbName.getText().strip(), tfUser.getText().strip(), + new String(tfPass.getPassword()).strip(), + cbDbType.getItemAt(cbDbType.getSelectedIndex())); + } + + private boolean hasEmptyFields(String host, String port, String dbName, + String user, String pass, String dbType) { + return host.isBlank() || port.isBlank() || dbName.isBlank() + || user.isBlank() || pass.isBlank() || dbType.isBlank(); + } + + private void closeDialogWithoutAdditionalActions() { + removeWindowListener(additionalActionsOnSaveListener); + dispose(); + } + + private void createPanels() { + panelTextFields = CustomJPanel.create(PanelType.CONNECTION_DIALOG_TEXT_FIELDS_PANEL); + panelTextFields.add(lblHost); + panelTextFields.add(tfHost); + panelTextFields.add(lblPort); + panelTextFields.add(tfPort); + panelTextFields.add(lblDbName); + panelTextFields.add(tfDbName); + panelTextFields.add(lblUser); + panelTextFields.add(tfUser); + panelTextFields.add(lblPass); + panelTextFields.add(tfPass); + panelTextFields.add(cbShowPass); + panelTextFields.add(lblDbType); + panelTextFields.add(cbDbType); + + panelButtons = CustomJPanel.create(PanelType.CONNECTION_DIALOG_BUTTON_PANEL); + panelButtons.add(btnTestConnection); + panelButtons.add(btnSaveConnection); + panelButtons.add(btnCancel); + } + + private void createDialog() { + getContentPane().add(panelTextFields, BorderLayout.CENTER); + getContentPane().add(panelButtons, BorderLayout.SOUTH); + makeDialogVisible(); + } + + private void fillDataComponents() { + try { + DbType dbType = Settings.getDatabaseType(); + if (dbType == DbType.PostgreSQL) { + String host = Settings.getHost(); + String dbName = Settings.getDatabase(); + String user = Settings.getUser(); + String pass = Settings.getPassword(); + if (!host.isBlank() && !dbName.isBlank() && !user.isBlank() && !pass.isBlank()) { + tfHost.setText(host.strip()); + tfDbName.setText(dbName.strip()); + tfUser.setText(user.strip()); + tfPass.setText(pass.strip()); + cbDbType.setSelectedItem(dbType.name()); + + String port; + try { + port = Integer.toString(Settings.getPort()); + } catch (Exception e) { + port = Const.EMPTY_STRING; + } + tfPort.setText(port); + } + } else { + fillDataComponentsWithDefaultValues(); + } + } catch (SQLException e) { + fillDataComponentsWithDefaultValues(); + } + } + + private void fillDataComponentsWithDefaultValues() { + tfHost.setText(DEFAULT_HOST); + tfPort.setText(DEFAULT_POST); + cbDbType.setSelectedItem(DbType.PostgreSQL.name()); + } + + private void addListeners() { + additionalActionsOnSaveListener = new AdditionalActionsOnSaveListener(additionalActionsOnSave); + addWindowListener(additionalActionsOnSaveListener); + addWindowListener(new NoAdditinalActionsOnEscapeListener()); + } + + @Override + protected void actionOnHotKeyForClose() { + closeDialogWithoutAdditionalActions(); + } + + private static final class AdditionalActionsOnSaveListener extends WindowAdapter { + + private final Runnable additionalActionsOnSave; + + private AdditionalActionsOnSaveListener(Runnable additionalActionsOnSave) { + this.additionalActionsOnSave = additionalActionsOnSave; + } + + @Override + public void windowClosed(WindowEvent event) { + ThreadUtil.runInThread(additionalActionsOnSave); + super.windowClosed(event); + } + } + + private static final class NoAdditinalActionsOnEscapeListener extends WindowAdapter { + + @Override + public void windowClosing(WindowEvent event) { + ((DialogDbConnection) event.getWindow()).closeDialogWithoutAdditionalActions(); + } + } +} diff --git a/src/main/java/home/gui/component/dialog/DialogMotorcycle.java b/src/main/java/home/gui/component/dialog/DialogMotorcycle.java index 0dfe33e..ef67c6e 100644 --- a/src/main/java/home/gui/component/dialog/DialogMotorcycle.java +++ b/src/main/java/home/gui/component/dialog/DialogMotorcycle.java @@ -1,14 +1,29 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component.dialog; import javax.swing.JCheckBox; -import home.gui.IGuiConsts; +import home.gui.GuiConst; import home.model.AbstractVehicle; import home.model.Motorcycle; import home.model.VehicleType; @SuppressWarnings("serial") -public final class DialogMotorcycle extends AbstractDialog { +public final class DialogMotorcycle extends AbstractDialogVehicle { private JCheckBox chkCradle; @@ -19,7 +34,7 @@ public DialogMotorcycle(int width, int height, AbstractVehicle dataObj, int tblR @Override protected void createDataComponents() { super.createDataComponents(); - chkCradle = new JCheckBox(IGuiConsts.HAS_CRADLE); + chkCradle = new JCheckBox(GuiConst.HAS_CRADLE); if (!isNewDataObj) { chkCradle.setSelected(((Motorcycle) dataObj).hasCradle()); diff --git a/src/main/java/home/gui/component/dialog/DialogTruck.java b/src/main/java/home/gui/component/dialog/DialogTruck.java index d7f55dd..7c05f6e 100644 --- a/src/main/java/home/gui/component/dialog/DialogTruck.java +++ b/src/main/java/home/gui/component/dialog/DialogTruck.java @@ -1,8 +1,23 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.component.dialog; import javax.swing.JCheckBox; -import home.gui.IGuiConsts; +import home.gui.GuiConst; import home.model.AbstractVehicle; import home.model.Truck; import home.model.VehicleType; @@ -19,7 +34,7 @@ public DialogTruck(int width, int height, AbstractVehicle dataObj, int tblRowOfS @Override protected void createDataComponents() { super.createDataComponents(); - chkCargo = new JCheckBox(IGuiConsts.TRANSPORTS_CARGO); + chkCargo = new JCheckBox(GuiConst.TRANSPORTS_CARGO); if (!isNewDataObj) { chkCargo.setSelected(((Truck) dataObj).isTransportsCargo()); diff --git a/src/main/java/home/gui/exception/CreateOpenSaveCancelException.java b/src/main/java/home/gui/exception/CreateOpenSaveCancelException.java new file mode 100644 index 0000000..68e1fc9 --- /dev/null +++ b/src/main/java/home/gui/exception/CreateOpenSaveCancelException.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui.exception; + +public final class CreateOpenSaveCancelException extends RuntimeException { + + private static final long serialVersionUID = 3561574118447534122L; + + public CreateOpenSaveCancelException(String message) { + super(message); + } +} diff --git a/src/main/java/home/gui/exception/SaveAsCancelException.java b/src/main/java/home/gui/exception/SaveAsCancelException.java index 7d7fa2a..efc2f3c 100644 --- a/src/main/java/home/gui/exception/SaveAsCancelException.java +++ b/src/main/java/home/gui/exception/SaveAsCancelException.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.exception; public final class SaveAsCancelException extends RuntimeException { diff --git a/src/main/java/home/gui/exception/SaveAsToSameFileException.java b/src/main/java/home/gui/exception/SaveAsToSameFileException.java deleted file mode 100644 index 7372357..0000000 --- a/src/main/java/home/gui/exception/SaveAsToSameFileException.java +++ /dev/null @@ -1,10 +0,0 @@ -package home.gui.exception; - -public final class SaveAsToSameFileException extends RuntimeException { - - private static final long serialVersionUID = -4526765533335630589L; - - public SaveAsToSameFileException(String message) { - super(message); - } -} diff --git a/src/main/java/home/gui/exception/SaveToAlreadyExistsFileException.java b/src/main/java/home/gui/exception/SaveToAlreadyExistsFileException.java new file mode 100644 index 0000000..050f872 --- /dev/null +++ b/src/main/java/home/gui/exception/SaveToAlreadyExistsFileException.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui.exception; + +public final class SaveToAlreadyExistsFileException extends RuntimeException { + + private static final long serialVersionUID = -4526765533335630589L; + + public SaveToAlreadyExistsFileException(String message) { + super(message); + } +} diff --git a/src/main/java/home/gui/listener/ConnectToServerDatabaseActionListener.java b/src/main/java/home/gui/listener/ConnectToServerDatabaseActionListener.java new file mode 100644 index 0000000..b5b32b3 --- /dev/null +++ b/src/main/java/home/gui/listener/ConnectToServerDatabaseActionListener.java @@ -0,0 +1,76 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui.listener; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.sql.SQLException; + +import javax.swing.JLabel; + +import org.slf4j.Logger; + +import home.Settings; +import home.db.conn.Connector; +import home.db.dao.Dao; +import home.db.init.DbInitializer; +import home.gui.DataActionInGui; +import home.gui.component.dialog.DialogDbConnection; +import home.utils.LogUtils; +import home.utils.Utils; + +/** + * Action listener for working with server databases. + * + * Supported: PostgreSql. + * + */ +public final class ConnectToServerDatabaseActionListener implements ActionListener { + + private final Component parent; + private final JLabel dbLabel; + private final Logger log; + + public ConnectToServerDatabaseActionListener(Component parent, JLabel dbLabel, Logger log) { + this.parent = parent; + this.dbLabel = dbLabel; + this.log = log; + } + + @Override + public void actionPerformed(ActionEvent event) { + Thread.currentThread().setName("-> connect to server database"); + + var dialogDbConnction = new DialogDbConnection("Connect to server database", () -> { + try { + String dbTypeStr = Settings.getDatabaseType().name(); + Thread.currentThread().setName("-> connect to %s database".formatted(dbTypeStr)); + if (Connector.testCurrentConnection()) { + DbInitializer.createTableIfNotExists(); + DataActionInGui.init(Dao.readAll()); + dbLabel.setText(Utils.generateDbDescription()); + } + } catch (SQLException e) { + LogUtils.logAndShowError(log, parent, + "Error while read selected DB.\n" + e.getLocalizedMessage(), + "Read selected DB error", e); + } + }); + + dialogDbConnction.buildDialog(); + } +} diff --git a/src/main/java/home/gui/listener/CreateOrOpenActionListener.java b/src/main/java/home/gui/listener/CreateOrOpenActionListener.java deleted file mode 100644 index 14bd9a0..0000000 --- a/src/main/java/home/gui/listener/CreateOrOpenActionListener.java +++ /dev/null @@ -1,54 +0,0 @@ -package home.gui.listener; - -import java.awt.Component; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.io.IOException; -import java.sql.SQLException; - -import javax.swing.JLabel; - -import org.slf4j.Logger; - -import home.Settings; -import home.db.DbInitializer; -import home.db.dao.DaoSQLite; -import home.gui.DataActionInGui; -import home.gui.component.CustomJFileChooserDb; -import home.gui.component.CustomJFileChooserDb.ChooserDbOperation; -import home.utils.ThreadUtil; -import home.utils.LogUtils; - -public final class CreateOrOpenActionListener implements ActionListener { - - private final Component parent; - private final JLabel dbLabel; - private final Logger log; - - public CreateOrOpenActionListener(Component parent, JLabel dbLabel, Logger log) { - this.parent = parent; - this.dbLabel = dbLabel; - this.log = log; - } - - @Override - public void actionPerformed(ActionEvent event) { - ThreadUtil.runInThread(() -> { - Thread.currentThread().setName("-> create or open database"); - try { - CustomJFileChooserDb.createAndShowChooser(parent, - ChooserDbOperation.CREATE_OR_OPEN); - DbInitializer.createTableIfNotExists(); - DataActionInGui.init(DaoSQLite.getInstance().readAll()); - dbLabel.setText(Settings.getDbFilePath()); - } catch (IOException e) { - LogUtils.logAndShowError(log, parent, "Error while create/open DB file.", - "Create/Open file error.", e); - } catch (SQLException e) { - LogUtils.logAndShowError(log, parent, - "Error while read selected DB file.\n" + e.getLocalizedMessage(), - "Read selected DB error", e); - } - }); - } -} diff --git a/src/main/java/home/gui/listener/CreateOrOpenFileDatabaseActionListener.java b/src/main/java/home/gui/listener/CreateOrOpenFileDatabaseActionListener.java new file mode 100644 index 0000000..9beb171 --- /dev/null +++ b/src/main/java/home/gui/listener/CreateOrOpenFileDatabaseActionListener.java @@ -0,0 +1,88 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.gui.listener; + +import java.awt.Component; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.IOException; +import java.sql.SQLException; + +import javax.swing.JLabel; + +import org.slf4j.Logger; + +import home.Settings; +import home.db.dao.Dao; +import home.db.init.DbInitializer; +import home.gui.DataActionInGui; +import home.gui.DbOperation; +import home.gui.component.CustomJFileChooserDb; +import home.gui.exception.CreateOpenSaveCancelException; +import home.utils.LogUtils; +import home.utils.ThreadUtil; + +/** + * Action listener for working with databases contained in a file. + * + * Supported: SQLite. + */ +public final class CreateOrOpenFileDatabaseActionListener implements ActionListener { + + private final Component parent; + private final JLabel dbLabel; + private final Logger log; + + public CreateOrOpenFileDatabaseActionListener(Component parent, JLabel dbLabel, Logger log) { + this.parent = parent; + this.dbLabel = dbLabel; + this.log = log; + } + + @Override + public void actionPerformed(ActionEvent event) { + try { + CustomJFileChooserDb.createAndShowChooser(parent, + DbOperation.CREATE_OR_OPEN_FILE_DATABASE); + createReadDataTable(); + } catch (CreateOpenSaveCancelException e) { + // to do nothing + return; + } catch (IOException e) { + LogUtils.logAndShowError(log, parent, "Error while create/open DB file.", + "Create/Open file error.", e); + } catch (SQLException e) { + LogUtils.logAndShowError(log, parent, + "Error while read selected DB.\n" + e.getLocalizedMessage(), + "Read selected DB error", e); + } + } + + private void createReadDataTable() { + ThreadUtil.runInThread(() -> { + Thread.currentThread().setName("-> create/read data table"); + try { + DbInitializer.createTableIfNotExists(); + DataActionInGui.init(Dao.readAll()); + dbLabel.setText(Settings.getDatabase()); + } catch (SQLException e) { + LogUtils.logAndShowError(log, parent, + "Error while create/read data table.\n" + e.getLocalizedMessage(), + "Create/read data table error", e); + } + }); + } +} diff --git a/src/main/java/home/gui/listener/ExportImportActionListener.java b/src/main/java/home/gui/listener/ExportImportActionListener.java index 1739e4b..d469103 100644 --- a/src/main/java/home/gui/listener/ExportImportActionListener.java +++ b/src/main/java/home/gui/listener/ExportImportActionListener.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.listener; import java.awt.Component; @@ -8,7 +23,6 @@ import home.gui.component.CustomJFileChooserImpExp; import home.gui.component.CustomJFileChooserImpExp.DataFormat; -import home.utils.ThreadUtil; import home.utils.LogUtils; public final class ExportImportActionListener implements ActionListener { @@ -28,13 +42,10 @@ public ExportImportActionListener(DataFormat dataFormat, @Override public void actionPerformed(ActionEvent event) { - ThreadUtil.runInThread(() -> { - Thread.currentThread().setName("-> export/import operation"); - try { - CustomJFileChooserImpExp.createAndShowChooser(parent, dataFomat, isImport); - } catch (Exception e) { - LogUtils.logAndShowError(log, parent, e.getMessage(), "Export/Import error", e); - } - }); + try { + CustomJFileChooserImpExp.createAndShowChooser(parent, dataFomat, isImport); + } catch (Exception e) { + LogUtils.logAndShowError(log, parent, e.getMessage(), "Export/Import error", e); + } } } \ No newline at end of file diff --git a/src/main/java/home/gui/listener/SaveActionListener.java b/src/main/java/home/gui/listener/SaveActionListener.java index 7c0544d..a256a56 100644 --- a/src/main/java/home/gui/listener/SaveActionListener.java +++ b/src/main/java/home/gui/listener/SaveActionListener.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.gui.listener; import java.awt.Component; @@ -12,16 +27,18 @@ import org.slf4j.Logger; import home.Settings; -import home.db.DbInitializer; -import home.db.dao.DaoSQLite; +import home.db.dao.Dao; +import home.db.init.DbInitializer; import home.gui.DataActionInGui; -import home.gui.IGuiConsts; +import home.gui.DbOperation; +import home.gui.GuiConst; import home.gui.component.CustomJFileChooserDb; -import home.gui.component.CustomJFileChooserDb.ChooserDbOperation; +import home.gui.exception.CreateOpenSaveCancelException; import home.gui.exception.SaveAsCancelException; -import home.gui.exception.SaveAsToSameFileException; +import home.gui.exception.SaveToAlreadyExistsFileException; import home.utils.LogUtils; import home.utils.ThreadUtil; +import home.utils.Utils; public final class SaveActionListener implements ActionListener { @@ -40,35 +57,65 @@ public SaveActionListener(Component parent, JLabel dbLabel, @Override public void actionPerformed(ActionEvent event) { + try { + if (isSaveAs) { + try { + CustomJFileChooserDb.createAndShowChooser(parent, DbOperation.SAVE_AS); + } catch (SaveToAlreadyExistsFileException e) { + JOptionPane.showMessageDialog(parent, GuiConst.ALREADY_EXISTS_TEXT, + GuiConst.ALREADY_EXISTS_TITLE, JOptionPane.ERROR_MESSAGE); + return; + } catch (SaveAsCancelException e) { + // to do nothing + return; + } + } else { + try { + if (!Settings.hasDatabase()) { + CustomJFileChooserDb.createAndShowChooser(parent, DbOperation.SAVE); + DbInitializer.createTableIfNotExists(); + } + } catch (SaveToAlreadyExistsFileException e) { + JOptionPane.showMessageDialog(parent, GuiConst.ALREADY_EXISTS_TEXT, + GuiConst.ALREADY_EXISTS_TITLE, JOptionPane.ERROR_MESSAGE); + return; + } catch (CreateOpenSaveCancelException e) { + // to do nothing + return; + } + } + + saveChangesToDb(); + } catch (IOException e) { + LogUtils.logAndShowError(log, parent, "Error while create/open DB file.", + "Create/Open file error.", e); + } catch (SQLException e) { + LogUtils.logAndShowError(log, parent, + "Error while work with DB.\n" + e.getMessage(), + "Work with DB error", e); + } + } + + private void saveChangesToDb() { ThreadUtil.runInThread(() -> { Thread.currentThread().setName("-> save changes to database"); + try { if (isSaveAs) { - try { - CustomJFileChooserDb.createAndShowChooser(parent, - ChooserDbOperation.SAVE_AS); - DbInitializer.createTableIfNotExists(); - DaoSQLite.getInstance().saveAs(); - } catch (SaveAsToSameFileException e) { - DaoSQLite.getInstance().saveAllChanges(); - } catch (SaveAsCancelException e) { - // to do nothing - return; - } + DbInitializer.createTableIfNotExists(); + Dao.saveAs(); } else { - DaoSQLite.getInstance().saveAllChanges(); + Dao.saveAllChanges(); } - DataActionInGui.init(DaoSQLite.getInstance().readAll()); - dbLabel.setText(Settings.getDbFilePath()); - JOptionPane.showMessageDialog(parent, IGuiConsts.SAVE_TEXT, - IGuiConsts.SAVE_TITLE, JOptionPane.INFORMATION_MESSAGE); - } catch (IOException e) { - LogUtils.logAndShowError(log, parent, "Error while create/open DB file.", - "Create/Open file error.", e); + DataActionInGui.init(Dao.readAll()); + dbLabel.setText(Utils.generateDbDescription()); + + JOptionPane.showMessageDialog(parent, GuiConst.SAVE_TEXT, + GuiConst.SAVE_TITLE, JOptionPane.INFORMATION_MESSAGE); } catch (SQLException e) { LogUtils.logAndShowError(log, parent, - "Error while work with DB file.\n" + e.getMessage(), + "Error while work with DB.\n" + e.getMessage(), "Work with DB error", e); } }); diff --git a/src/main/java/home/model/AbstractVehicle.java b/src/main/java/home/model/AbstractVehicle.java index 0b36b3b..2e15eb7 100644 --- a/src/main/java/home/model/AbstractVehicle.java +++ b/src/main/java/home/model/AbstractVehicle.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.model; import java.io.Serializable; @@ -7,7 +22,7 @@ import java.util.TimeZone; public abstract sealed class AbstractVehicle - implements Serializable permits AbstractVehicleWithTrailer,Motorcycle { + implements Serializable permits AbstractVehicleWithTrailer, Motorcycle { private static final long serialVersionUID = 4228103618146673801L; diff --git a/src/main/java/home/model/AbstractVehicleWithTrailer.java b/src/main/java/home/model/AbstractVehicleWithTrailer.java index 8549c17..e985228 100644 --- a/src/main/java/home/model/AbstractVehicleWithTrailer.java +++ b/src/main/java/home/model/AbstractVehicleWithTrailer.java @@ -1,9 +1,24 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.model; import java.util.Objects; public abstract sealed class AbstractVehicleWithTrailer - extends AbstractVehicle permits Car,Truck { + extends AbstractVehicle permits Car, Truck { private static final long serialVersionUID = 5771617467340614253L; diff --git a/src/main/java/home/model/Car.java b/src/main/java/home/model/Car.java index abe077a..e544019 100644 --- a/src/main/java/home/model/Car.java +++ b/src/main/java/home/model/Car.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.model; import java.util.Objects; diff --git a/src/main/java/home/model/Motorcycle.java b/src/main/java/home/model/Motorcycle.java index ed121b9..b5c2935 100644 --- a/src/main/java/home/model/Motorcycle.java +++ b/src/main/java/home/model/Motorcycle.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.model; import java.util.Objects; diff --git a/src/main/java/home/model/Truck.java b/src/main/java/home/model/Truck.java index f0a69e9..5fa01ac 100644 --- a/src/main/java/home/model/Truck.java +++ b/src/main/java/home/model/Truck.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.model; import java.util.Objects; diff --git a/src/main/java/home/model/VehicleType.java b/src/main/java/home/model/VehicleType.java index c1564a4..0c01aaf 100644 --- a/src/main/java/home/model/VehicleType.java +++ b/src/main/java/home/model/VehicleType.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.model; public enum VehicleType { @@ -23,7 +38,8 @@ public static VehicleType getVehicleType(String type) { return vehicleType; } } - return null; + + throw new IllegalArgumentException("Wrong vehicle type received : " + type); } public boolean in(VehicleType... vehicleTypes) { diff --git a/src/main/java/home/utils/CustomProperties.java b/src/main/java/home/utils/CustomProperties.java new file mode 100644 index 0000000..1f1ee9e --- /dev/null +++ b/src/main/java/home/utils/CustomProperties.java @@ -0,0 +1,242 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.utils; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HexFormat; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; + +/** + * Properties without date comment for + * {@link java.util.Properties#store(OutputStream, String) store} method and + * with sorting of keys + */ +public final class CustomProperties extends Properties { + + private static final long serialVersionUID = 693357491710031108L; + + //// code for deleting a comment + + @Override + public void store(OutputStream out, String comments) + throws IOException { + throw new UnsupportedOperationException("This method will not be implemented " + + "for this class. Please use 'CustomProperties.store(OutputStream out)' instead."); + } + + public void store(OutputStream out) throws IOException { + store0(new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8.toString())), + null, false, true); + } + + /** + * Modification of parent method: + * {@link java.util.Properties#store0(BufferedWriter, String, boolean) store0} + */ + private void store0(BufferedWriter bw, String comments, boolean addDate, boolean escUnicode) + throws IOException { + if (comments != null) { + writeComments(bw, comments); + } + if (addDate) { + bw.write("#" + new Date().toString()); + bw.newLine(); + } + synchronized (this) { + for (Entry e : entrySet()) { + String key = (String) e.getKey(); + String val = (String) e.getValue(); + key = saveConvert(key, true, escUnicode); + /* + * No need to escape embedded and trailing spaces for value, hence pass false to + * flag. + */ + val = saveConvert(val, false, escUnicode); + bw.write(key + "=" + val); + bw.newLine(); + } + } + bw.flush(); + } + + /** + * Copy of parent method: + * {@link java.util.Properties#writeComments(BufferedWriter, String) + * writeComments} + */ + private static void writeComments(BufferedWriter bw, String comments) throws IOException { + HexFormat hex = HexFormat.of().withUpperCase(); + bw.write("#"); + int len = comments.length(); + int current = 0; + int last = 0; + while (current < len) { + char c = comments.charAt(current); + if (c > '\u00ff' || c == '\n' || c == '\r') { + if (last != current) { + bw.write(comments.substring(last, current)); + } + if (c > '\u00ff') { + bw.write("\\u"); + bw.write(hex.toHexDigits(c)); + } else { + bw.newLine(); + if (c == '\r' && + current != len - 1 && + comments.charAt(current + 1) == '\n') { + current++; + } + if (current == len - 1 || + (comments.charAt(current + 1) != '#' && + comments.charAt(current + 1) != '!')) { + bw.write("#"); + } + } + last = current + 1; + } + current++; + } + if (last != current) { + bw.write(comments.substring(last, current)); + } + bw.newLine(); + } + + /** + * Converts unicodes to encoded \uxxxx and escapes special characters with a + * preceding slash + * + * Copy of parent method: + * {@link java.util.Properties#saveConvert(String, boolean, boolean) + * saveConvert} + */ + private String saveConvert(String theString, boolean escapeSpace, boolean escapeUnicode) { + int len = theString.length(); + int bufLen = len * 2; + if (bufLen < 0) { + bufLen = Integer.MAX_VALUE; + } + StringBuilder outBuffer = new StringBuilder(bufLen); + HexFormat hex = HexFormat.of().withUpperCase(); + for (int x = 0; x < len; x++) { + char aChar = theString.charAt(x); + // Handle common case first, selecting largest block that + // avoids the specials below + if ((aChar > 61) && (aChar < 127)) { + if (aChar == '\\') { + outBuffer.append('\\'); + outBuffer.append('\\'); + continue; + } + outBuffer.append(aChar); + continue; + } + switch (aChar) { + case ' ': + if (x == 0 || escapeSpace) { + outBuffer.append('\\'); + } + outBuffer.append(' '); + break; + case '\t': + outBuffer.append('\\'); + outBuffer.append('t'); + break; + case '\n': + outBuffer.append('\\'); + outBuffer.append('n'); + break; + case '\r': + outBuffer.append('\\'); + outBuffer.append('r'); + break; + case '\f': + outBuffer.append('\\'); + outBuffer.append('f'); + break; + case '=': // $FALL-THROUGH$ + case ':': // $FALL-THROUGH$ + case '#': // $FALL-THROUGH$ + case '!': + outBuffer.append('\\'); + outBuffer.append(aChar); + break; + default: + if (((aChar < 0x0020) || (aChar > 0x007e)) & escapeUnicode) { + outBuffer.append("\\u"); + outBuffer.append(hex.toHexDigits(aChar)); + } else { + outBuffer.append(aChar); + } + } + } + return outBuffer.toString(); + } + + //// key sort code + + /** + * Modification of parent method: {@link java.util.Properties#keys() keys} + */ + @Override + public synchronized Enumeration keys() { + return Collections.enumeration(getKeys()); + } + + /** + * Modification of parent method: {@link java.util.Properties#keySet() keySet} + */ + @Override + public Set keySet() { + return Collections.unmodifiableSet(getKeys()); + } + + private Set getKeys() { + return new TreeSet(super.keySet()); + } + + /** + * Modification of parent method: {@link java.util.Properties#entrySet() + * entrySet} + */ + @Override + public Set> entrySet() { + Set> set1 = super.entrySet(); + Set> set2 = new LinkedHashSet>(set1.size()); + + Iterator> iterator = set1.stream() + .sorted((o1, o2) -> o1.getKey().toString().compareTo(o2.getKey().toString())) + .iterator(); + + while (iterator.hasNext()) { + set2.add(iterator.next()); + } + + return set2; + } +} \ No newline at end of file diff --git a/src/main/java/home/utils/LogUtils.java b/src/main/java/home/utils/LogUtils.java index c536179..b73325f 100644 --- a/src/main/java/home/utils/LogUtils.java +++ b/src/main/java/home/utils/LogUtils.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.utils; import java.awt.Component; @@ -15,7 +30,7 @@ public final class LogUtils { // location of the log files is configured in file 'log4j2.xml' private static final Path LOG_FILE = Paths.get("logs", "app.log"); - public static void logAndShowError(Logger log, Component parentComponent, + public static void logAndShowError(Logger log, Component parentComponent, String msg, String title, Throwable t) { String threadName = "[THREAD: " + Thread.currentThread().getName() + " ] "; diff --git a/src/main/java/home/utils/NamedFormatter.java b/src/main/java/home/utils/NamedFormatter.java new file mode 100644 index 0000000..818510c --- /dev/null +++ b/src/main/java/home/utils/NamedFormatter.java @@ -0,0 +1,79 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2022 Baeldung + * + * Distributed under MIT License + *******************************************************************************/ +package home.utils; + +import java.util.ArrayList; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Formatter for work with strings with named placeholders. + */ +public final class NamedFormatter { + + // regex for ${some_word} (${word} or ${wo_rd} or ${word_123} or ${word123}), + // where group 0 is "${some_word}" and group 1 is "some_word" + private static final String REGEX = "[$][{](\\w+)}"; + private static final Pattern PATTERN = Pattern.compile(REGEX); + + /** + * Formats given string: replaces placeholders in + * {@code strWithNamedPlaceholders} by values from {@code placeholdersValues} + * map. + * + *
+     * {@code
+     * String templateWithNamedPlaceholder = "few ${placeholder} words ${other_placeholder}";
+     * Map params = Map.ofEntries(
+     *         Map.entry("placeholder", "value of this placeholder"),
+     *         Map.entry("other_placeholder", 123));
+     * }
+     * String result = NamedFormatter.format(templateWithNamedPlaceholder, params);
+     * // result : few value of this placeholder words 123
+     * 
+ * + * @param strWithNamedPlaceholders string containing named placeholders + * @param placeholdersValues map with values for placeholders + * @return formatted string + */ + public static String format(String strWithNamedPlaceholders, + Map placeholdersValues) { + var standartTemplate = new StringBuilder(strWithNamedPlaceholders); + var standartTemplateValues = new ArrayList(); + + Matcher matcher = PATTERN.matcher(strWithNamedPlaceholders); + + while (matcher.find()) { + String placeholderName = matcher.group(1); + String placeholder = "${" + placeholderName + '}'; + int placeholderIdx = standartTemplate.indexOf(placeholder); + if (placeholderIdx != -1) { + standartTemplate.replace(placeholderIdx, placeholderIdx + placeholder.length(), "%s"); + standartTemplateValues.add(placeholdersValues.get(placeholderName)); + } + } + + return standartTemplate.toString().formatted(standartTemplateValues.toArray()); + } + + private NamedFormatter() { + } +} diff --git a/src/main/java/home/utils/ThreadUtil.java b/src/main/java/home/utils/ThreadUtil.java index 6fa3c85..5be0bda 100644 --- a/src/main/java/home/utils/ThreadUtil.java +++ b/src/main/java/home/utils/ThreadUtil.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.utils; import java.util.List; @@ -21,6 +36,15 @@ public final class ThreadUtil { } public static void runInThread(Runnable runnable) { + // For work "Thread.setDefaultUncaughtExceptionHandler(handler)" (in + // Main.setUncaughtExceptionProcessing()) must be used + // "EXECUTOR.execute()" but not "EXECUTOR.submit()". + // + // Because exceptions thrown from tasks make it to the uncaught exception + // handler only for tasks submitted with execute; for tasks submitted with + // submit, any thrown exception, checked or not, is considered to be part of the + // task’s return status. If a task submitted with submit terminates with an + // exception, it is rethrown by Future.get, wrapped in an ExecutionException. EXECUTOR.execute(runnable); } diff --git a/src/main/java/home/utils/Utils.java b/src/main/java/home/utils/Utils.java index e19afe8..b484680 100644 --- a/src/main/java/home/utils/Utils.java +++ b/src/main/java/home/utils/Utils.java @@ -1,12 +1,30 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.utils; +import java.sql.SQLException; import java.time.Instant; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.TimeZone; -import home.gui.IGuiConsts; +import home.Settings; +import home.db.DbType; +import home.gui.GuiConst; public final class Utils { @@ -15,19 +33,36 @@ public static String getFormattedDate(long dateTimeInMilliseconds) { Instant.ofEpochMilli(dateTimeInMilliseconds), TimeZone.getDefault().toZoneId()); DateTimeFormatter dateFormatter = DateTimeFormatter - .ofPattern(IGuiConsts.DATE_FORMAT, Locale.ROOT); + .ofPattern(GuiConst.DATE_FORMAT, Locale.ROOT); return dateTime.format(dateFormatter); } public static long getLongFromFormattedDate(String formattedDate) { DateTimeFormatter dateFormatter = DateTimeFormatter - .ofPattern(IGuiConsts.DATE_FORMAT, Locale.ROOT); + .ofPattern(GuiConst.DATE_FORMAT, Locale.ROOT); long millisecondsSinceEpoch = LocalDateTime.parse(formattedDate, dateFormatter) .atZone(TimeZone.getDefault().toZoneId()) .toInstant().toEpochMilli(); return millisecondsSinceEpoch; } + public static String generateDbDescription() { + try { + if (Settings.hasDatabase()) { + return Settings.getDatabaseType() == DbType.SQLite ? Settings.getDatabase() + : GuiConst.DB_LABEL_FORMAT.formatted( + Settings.getDatabaseType().name().toLowerCase(Locale.ROOT), + Settings.getHost(), + Settings.getPort(), + Settings.getDatabase()); + } else { + return GuiConst.DATABASE_NOT_SELECTED; + } + } catch (SQLException e) { + return GuiConst.DATABASE_NOT_SELECTED; + } + } + private Utils() { } } diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml index 68c4562..2017d07 100644 --- a/src/main/resources/log4j2.xml +++ b/src/main/resources/log4j2.xml @@ -16,7 +16,8 @@ - @@ -31,7 +32,7 @@ - + diff --git a/src/test/java/home/db/AbstractDbTest.java b/src/test/java/home/db/AbstractDbTest.java new file mode 100644 index 0000000..8990458 --- /dev/null +++ b/src/test/java/home/db/AbstractDbTest.java @@ -0,0 +1,253 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; +import java.nio.file.Files; +import java.sql.SQLException; +import java.util.Map; +import java.util.function.Supplier; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.TestInstance; + +import home.Settings; +import home.Settings.Setting; +import home.db.conn.Connector; +import home.db.init.DbInitializer; + +// "@TestInstance(TestInstance.Lifecycle.PER_CLASS)" added to use "@BeforeAll" +// and "@AfterAll" without static modifier. +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract sealed class AbstractDbTest permits AbstractDmlTest, AbstractDdlTest { + + private static final IDbTestPreparer SQLITE_PREPARER = new SqliteTestPreparer(); + private static final IDbTestPreparer PG_PREPARER = new PgTestPreparer(); + + @BeforeAll + void init() throws Exception { + basePrepare(); + if (isAvailableWorkWithDb()) { + getPreparer().init(); + } + } + + // Needed for tests to work correctly with @SkipIfDbUnavailable. + private void basePrepare() throws Exception { + Settings.saveSettings(Map.ofEntries( + Map.entry(Setting.DATABASE_TYPE, getDbType().name()), + Map.entry(Setting.STYLE, Setting.STYLE.getDefaultValue()), + Map.entry(Setting.AUTO_RESIZE_TABLE_WIDTH, + Setting.AUTO_RESIZE_TABLE_WIDTH.getDefaultValue()))); + resetConnection(); + } + + protected abstract DbType getDbType(); + + protected void resetConnection() throws Exception { + Field connectionDataField = Connector.class.getDeclaredField("connectionData"); + connectionDataField.setAccessible(true); + connectionDataField.set(null, null); + } + + @AfterAll + void cleanUp() throws Exception { + if (isAvailableWorkWithDb()) { + getPreparer().cleanUp(); + } + } + + private boolean isAvailableWorkWithDb() throws Exception { + return getPreparer().isAvailableWorkWithDb(); + } + + protected String getTableName() throws Exception { + return getPreparer().getTableName(); + } + + private IDbTestPreparer getPreparer() throws SQLException { + DbType dbType = getDbType(); + return switch (dbType) { + case SQLite -> SQLITE_PREPARER; + case PostgreSQL -> PG_PREPARER; + default -> throw new SQLException("Unsupported database type : " + dbType); + }; + } + + protected String getDbFileName() { + return getValueWithTypeCheck(((SqliteTestPreparer) SQLITE_PREPARER)::getDbFileName); + } + + protected File getGeneratedDbFile() { + return getValueWithTypeCheck(((SqliteTestPreparer) SQLITE_PREPARER)::getGeneratedDbFile); + } + + private T getValueWithTypeCheck(Supplier getter) { + DbType dbType = getDbType(); + if (dbType == DbType.SQLite) { + return getter.get(); + } + + throw new IllegalArgumentException( + "%s is not file database (db file not used)".formatted(dbType)); + } +} + +sealed interface IDbTestPreparer permits PgTestPreparer, SqliteTestPreparer { + + void init() throws Exception; + + void cleanUp() throws Exception; + + boolean isAvailableWorkWithDb(); + + String getTableName() throws Exception; +} + +final class PgTestPreparer implements IDbTestPreparer { + + private static final String PG_TEST_SETTINGS = "pg_test_settings.properties"; + + private static final String PG_DATA_TABLE = "public.vehicle"; + + @Override + public void init() throws Exception { + Settings.readSettings(getPgTestSettingPath()); + try { + if (Connector.testCurrentConnection()) { + DbTestUtils.drop(PG_DATA_TABLE); + DbInitializer.createTableIfNotExists(); + } + } catch (SQLException e) { + fail("Errors while create table in DB.", e); + } + } + + private String getPgTestSettingPath() { + URL testSettingsUrl = AbstractDbTest.class.getResource(PG_TEST_SETTINGS); + return testSettingsUrl.getPath(); + } + + @Override + public void cleanUp() throws Exception { + DbTestUtils.drop(PG_DATA_TABLE); + Connector.resetConnectionDataAndSettings(); + } + + @Override + public boolean isAvailableWorkWithDb() { + try { + Settings.readSettings(getPgTestSettingPath()); + return Connector.testCurrentConnection(); + } catch (Exception e) { + return false; + } + } + + @Override + public String getTableName() throws Exception { + return PG_DATA_TABLE; + } +} + +final class SqliteTestPreparer extends AbstractFileDatabaseTestPreparer implements IDbTestPreparer { + + private static final String DB_FILE_NAME = "database.db"; + private static final String SQLITE_DATA_TABLE = "vehicle"; + + private static File generatedDbFile; + + @Override + public void init() throws Exception { + AbstractFileDatabaseTestPreparer.deletePreviousTmpFiles(getTmpPrefix(), DB_FILE_NAME); + try { + Settings.readSettings(); + generatedDbFile = File.createTempFile(getTmpPrefix(), DB_FILE_NAME); + DbInitializer.createDbFileIfNotExists(generatedDbFile); + DbInitializer.createTableIfNotExists(); + } catch (IOException e) { + fail("Errors while create DB file.", e); + } catch (SQLException e) { + fail("Errors while create table in DB.", e); + } + } + + @Override + public void cleanUp() throws Exception { + try { + Files.deleteIfExists(generatedDbFile.toPath()); + Connector.resetConnectionDataAndSettings(); + } catch (IOException e) { + fail("Errors while delete DB file.", e); + } + } + + @Override + public boolean isAvailableWorkWithDb() { + return AbstractFileDatabaseTestPreparer.isAvailableWorkWithTempFile(); + } + + @Override + public String getTableName() throws Exception { + return SQLITE_DATA_TABLE; + } + + String getDbFileName() { + return DB_FILE_NAME; + } + + File getGeneratedDbFile() { + return generatedDbFile; + } +} + +abstract sealed class AbstractFileDatabaseTestPreparer permits SqliteTestPreparer { + + private static final String TEMP_DIR_PROPERTY = "java.io.tmpdir"; + private static final String TMP_PREFIX = "tmp_"; + private static final String AVAILABILITY_CHECK_FILE_NAME = "availability_check.db"; + + String getTmpPrefix() { + return TMP_PREFIX; + } + + static boolean isAvailableWorkWithTempFile() { + try { + deletePreviousTmpFiles(TMP_PREFIX, AVAILABILITY_CHECK_FILE_NAME); + File generatedAvailabilityCheckFile = File.createTempFile(TMP_PREFIX, AVAILABILITY_CHECK_FILE_NAME); + return Files.deleteIfExists(generatedAvailabilityCheckFile.toPath()); + } catch (IOException e) { + return false; + } + } + + static void deletePreviousTmpFiles(String prefix, String suffix) { + File tmpDir = new File(System.getProperty(TEMP_DIR_PROPERTY)); + for (File file : tmpDir.listFiles()) { + String tmpFileName = file.getName(); + if (file.isFile() && tmpFileName.startsWith(prefix) + && tmpFileName.contains(suffix)) { + file.delete(); + } + } + } +} diff --git a/src/test/java/home/db/AbstractDdlTest.java b/src/test/java/home/db/AbstractDdlTest.java new file mode 100644 index 0000000..3efea18 --- /dev/null +++ b/src/test/java/home/db/AbstractDdlTest.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +abstract sealed class AbstractDdlTest extends AbstractDbTest permits PgDdlTest, SqliteDdlTest { + + @Test + @SkipIfDbUnavailable(dbTypes = { "PostgreSQL", "SQLite" }) + void checkCreatedTableTest() throws Exception { + String tableName = getTableName(); + assertTrue(DbTestUtils.isExists(tableName), + "relation '%s' does not exist".formatted(tableName)); + } +} diff --git a/src/test/java/home/db/AbstractDmlTest.java b/src/test/java/home/db/AbstractDmlTest.java new file mode 100644 index 0000000..74f011d --- /dev/null +++ b/src/test/java/home/db/AbstractDmlTest.java @@ -0,0 +1,202 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import home.Storage; +import home.db.dao.Dao; +import home.model.AbstractVehicle; +import home.model.Car; + +abstract sealed class AbstractDmlTest extends AbstractDbTest permits PgDmlTest, SqliteDmlTest { + + private static final int ID_2 = 2; + + @BeforeEach + @SkipIfDbUnavailable(dbTypes = { "PostgreSQL", "SQLite" }) + void clearDataTable() throws Exception { + DbTestUtils.truncate(getTableName(), getDbType()); + Storage.INSTANCE.initDataObjs(Collections.emptyList()); + } + + @Test + @SkipIfDbUnavailable(dbTypes = { "PostgreSQL", "SQLite" }) + void createDataObjTest() throws Exception { + // There are no ids yet in new data objects. Ids will be set in database. + // That is why we will set it later, from read database object. + // It's need for correct comparison. + List createdDataObjs = createThreeDataObjsWithoutId(); + saveDataObjsToDb(createdDataObjs); + + for (long i = 1; i <= createdDataObjs.size(); i++) { + AbstractVehicle dataObjFromDb = readDataObjFromDbById(i); + AbstractVehicle createdDataObj = createdDataObjs.get((int) (i - 1)); + + // As said before, here we set id from read database object. + createdDataObj.setId(dataObjFromDb.getId()); + + assertEquals(createdDataObj, dataObjFromDb, """ + Created object from DB does not match with expected. + object id: %d" + expected object: %s + actual object: %s + """.formatted(i, createdDataObj, dataObjFromDb)); + } + } + + @Test + @SkipIfDbUnavailable(dbTypes = { "PostgreSQL", "SQLite" }) + void updateDataObjTest() throws Exception { + List createdDataObjs = createThreeDataObjsWithoutId(); + saveDataObjsToDb(createdDataObjs); + + AbstractVehicle readedDataObj2 = readDataObjFromDbById(ID_2); + String newColor = "grey"; + readedDataObj2.setColor(newColor); + saveEditedDataObjToDb(readedDataObj2, ID_2 - 1); + + AbstractVehicle updatedDataObj2 = readDataObjFromDbById(ID_2); + + assertEquals(readedDataObj2, updatedDataObj2, "Updated object from DB does not match with expected."); + } + + @Test + @SkipIfDbUnavailable(dbTypes = { "PostgreSQL", "SQLite" }) + void deleteDataObjTest() throws Exception { + List createdDataObjs = createThreeDataObjsWithoutId(); + saveDataObjsToDb(createdDataObjs); + + AbstractVehicle readedDataObj2 = readDataObjFromDbById(ID_2); + deleteDataObjsFromDb(List.of(readedDataObj2)); + + // check the absence of dataObj with ID_2 in the DB + try { + readDataObjFromDbById(ID_2); + fail("Expected java.lang.IndexOutOfBoundsException to be thrown, but nothing was thrown."); + } catch (Exception e) { + String expected = "Index -2 out of bounds for length 2"; + String actual = e.getMessage().strip(); + assertTrue(actual.endsWith(expected), """ + Expected and actual error message does not match: + expected: %s + actual: %s + """.formatted(expected, actual)); + } + + assertEquals(2, DbTestUtils.count(getTableName()), "Expected and actual number of entries do not match."); + } + + private List createThreeDataObjsWithoutId() { + return List.of(createDataObjWithoutId("red", "k333or", true), + createDataObjWithoutId("yellow", "m444ps", false), + createDataObjWithoutId("green", "n555rt", true)); + } + + private AbstractVehicle createDataObjWithoutId(String color, String number, + boolean isTransportsPassengers) { + var car = new Car(); + car.setColor(color); + car.setNumber(number); + car.setDateTime(System.currentTimeMillis()); + car.setTransportsPassengers(isTransportsPassengers); + return car; + } + + private void saveDataObjsToDb(List dataObjs) throws Exception { + DbTestUtils.runAndWait(() -> { + // save created dataObj to internal application storage + Storage.INSTANCE.initDataObjs(dataObjs); + saveAndSyncData(); + return null; + }); + } + + private AbstractVehicle readDataObjFromDbById(long id) throws Exception { + return DbTestUtils.runAndWait(() -> { + // read dataObj from DB by id + AbstractVehicle dataObj = Dao.readOne(id); + // synchronize internal application storage with DB + Storage.INSTANCE.initDataObjs(Dao.readAll()); + return dataObj; + }); + } + + @Deprecated(forRemoval = true) // because it gets more data from the database than it needs + private AbstractVehicle readDataObjFromDbById(int id) throws Exception { + return DbTestUtils.runAndWait(() -> { + // read dataObj from DB by id + List dataObjs = Dao.readAll(); + AbstractVehicle dataObj = getDataObjByIdViaBinarySearch(dataObjs, id); + // synchronize internal application storage with DB + Storage.INSTANCE.initDataObjs(Dao.readAll()); + return dataObj; + }); + } + + private AbstractVehicle getDataObjByIdViaBinarySearch(List dataObjs, long id) { + Comparator comparatorById = (v1, v2) -> Long.compare(v1.getId(), v2.getId()); + + Collections.sort(dataObjs, comparatorById); + + var searchTemplate = new Car(); + searchTemplate.setId(id); + + int searchedObjIdx = Collections.binarySearch(dataObjs, searchTemplate, comparatorById); + + if (searchedObjIdx < 0) { + throw new IndexOutOfBoundsException("Index %d out of bounds for length %d" + .formatted(searchedObjIdx, dataObjs.size())); + } + + return dataObjs.get(searchedObjIdx); + } + + private void saveEditedDataObjToDb(AbstractVehicle dataObj, int rowOfSelectedDataObj) throws Exception { + DbTestUtils.runAndWait(() -> { + // replace old dataObj by edited one in internal application storage + Storage.INSTANCE.updateDataObj(dataObj, rowOfSelectedDataObj); + saveAndSyncData(); + return null; + }); + } + + private void deleteDataObjsFromDb(List dataObjs) throws Exception { + DbTestUtils.runAndWait(() -> { + // delete dataObjs from internal application storage + Storage.INSTANCE.deleteDataObjs(dataObjs); + saveAndSyncData(); + return null; + }); + } + + private void saveAndSyncData() throws Exception { + // save all changes of dataObj-s from internal application storage to DB + Dao.saveAllChanges(); + // synchronize internal application storage with DB + // (needed to sync IDs [DB -> internal_application_storage]) + Storage.INSTANCE.initDataObjs(Dao.readAll()); + } +} diff --git a/src/test/java/home/db/DbTest.java b/src/test/java/home/db/DbTest.java deleted file mode 100644 index 68bfcd2..0000000 --- a/src/test/java/home/db/DbTest.java +++ /dev/null @@ -1,112 +0,0 @@ -package home.db; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.fail; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.sql.SQLException; - -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import home.IConsts; -import home.Settings; -import home.Settings.Setting; -import home.Storage; -import home.db.dao.DaoSQLite; -import home.model.AbstractVehicle; -import home.model.Car; - -final class DbTest { - - private static final String TEMP_DIR_PROPERTY = "java.io.tmpdir"; - - private static final String TMP = "tmp_"; - private static final String DB_FILE_NAME = "database.db"; - - private static File generatedDbFile; - - @BeforeEach - void initializeTemporaryDbFile() { - deletePreviousTmpFiles(); - try { - generatedDbFile = File.createTempFile(TMP, DB_FILE_NAME); - DbInitializer.createDbFileIfNotExists(generatedDbFile); - DbInitializer.createTableIfNotExists(); - } catch (IOException e) { - fail("Errors while create DB file.", e); - } catch (SQLException e) { - fail("Errors while create table in DB.", e); - } - } - - private static void deletePreviousTmpFiles() { - File tmpDir = new File(System.getProperty(TEMP_DIR_PROPERTY)); - for (File file : tmpDir.listFiles()) { - String tmpFileName = file.getName(); - if (file.isFile() && tmpFileName.startsWith(TMP) - && tmpFileName.contains(DB_FILE_NAME)) { - file.delete(); - } - } - } - - @Test - void createDbFileTest() { - try (var sampleDbFileStream = getClass().getResourceAsStream(DB_FILE_NAME)) { - byte[] sampleDbFileByte = sampleDbFileStream.readAllBytes(); - assertNotNull(sampleDbFileByte, "Sample Db file is null."); - - byte[] generatedDbFileByte = Files.readAllBytes(generatedDbFile.toPath()); - assertNotNull(generatedDbFileByte, "Generated Db file is null."); - - assertArrayEquals(sampleDbFileByte, generatedDbFileByte); - } catch (IOException e) { - fail("Errors while read file.", e); - } - } - - @Test - void createReadDataTest() { - try { - // There is no id yet in new data object. Id will be set in database. - // That is why we will set it later, from read database object. - // It's need for correct comparison. - var createdDataObj = new Car(); - createdDataObj.setColor("green"); - createdDataObj.setNumber("n555rt"); - createdDataObj.setDateTime(System.currentTimeMillis()); - createdDataObj.setTransportsPassengers(true); - - Storage.INSTANCE.updateDataObj(createdDataObj, Storage.NO_ROW_IS_SELECTED); - DaoSQLite.getInstance().saveAllChanges(); - - long id = 1; - AbstractVehicle readedDataObj = DaoSQLite.getInstance().readOne(id); - - assertNotNull(readedDataObj, "Read data object is null."); - - // As said before, here we set id from read database object. - createdDataObj.setId(readedDataObj.getId()); - - assertEquals(createdDataObj, readedDataObj); - } catch (SQLException e) { - fail("Errors while work with DB.", e); - } - } - - @AfterEach - void removeTemporaryDbFile() { - try { - Files.deleteIfExists(generatedDbFile.toPath()); - Settings.writeSetting(Setting.DB_FILE_PATH, IConsts.EMPTY_STRING); - } catch (IOException e) { - fail("Errors while delete DB file.", e); - } - } -} diff --git a/src/test/java/home/db/DbTestUtils.java b/src/test/java/home/db/DbTestUtils.java new file mode 100644 index 0000000..699b219 --- /dev/null +++ b/src/test/java/home/db/DbTestUtils.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +import java.sql.SQLException; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import home.db.conn.Connector; + +final class DbTestUtils { + + private static final int DEFAULT_TIMEOUT = 5; // sec + + static final int NOT_OVERRIDE_QUERY_TIMEOUT = -1; + + static int count(String tableName) throws SQLException, InterruptedException { + try (var conn = Connector.getConnection(); + var stmt = conn.createStatement()) { + stmt.setQueryTimeout(DEFAULT_TIMEOUT); + try (var rs = stmt.executeQuery("SELECT COUNT(*) AS count FROM %s" + .formatted(tableName))) { + return rs.next() ? rs.getInt(1) : -1; + } + } + } + + static boolean isExists(String tableName) throws SQLException, InterruptedException { + try (var conn = Connector.getConnection(); + var stmt = conn.createStatement()) { + stmt.setQueryTimeout(DEFAULT_TIMEOUT); + try (var rs = stmt.executeQuery("SELECT 1 FROM %s".formatted(tableName))) { + return true; + } + } + } + + static void truncate(String tableName, DbType dbType) throws SQLException, InterruptedException { + switch (dbType) { + case SQLite -> + // SQLite doesn't have TRUNCATE, instead it uses DELETE without WHERE. + // In SQLite, to reset autoincrement, its entry is removed from the + // sqlite_sequence table. + execute("DELETE FROM %s".formatted(tableName), + "DELETE FROM sqlite_sequence WHERE name='%s'".formatted(tableName)); + case PostgreSQL -> + // The application logic uses the sequence name, which is usually + // the default in PostgreSQL: public.{tableName}_id_seq. + execute("TRUNCATE %s".formatted(tableName), + "ALTER SEQUENCE %s_id_seq RESTART WITH 1".formatted(tableName)); + default -> throw new SQLException("Unsupported database type : " + dbType); + } + } + + static void drop(String tableName) throws SQLException, InterruptedException { + execute("DROP TABLE IF EXISTS %s".formatted(tableName)); + } + + static void execute(String... queries) throws SQLException { + execute(DEFAULT_TIMEOUT, queries); + } + + static void execute(int timeout, String... queries) throws SQLException { + try (var conn = Connector.getConnection(); + var stmt = conn.createStatement()) { + + if (timeout >= 0) { + stmt.setQueryTimeout(timeout); + } + + for (String query : queries) { + stmt.executeUpdate(query); + } + } + } + + static T runAndWait(Callable task) throws InterruptedException { + return runAndWait(task, DEFAULT_TIMEOUT); + } + + private static T runAndWait(Callable task, int timeout) throws InterruptedException { + ExecutorService executor = Executors.newSingleThreadExecutor(); + + // uses method-"submit" to put all uncaught exceptions from the "Callable" + // into an ExecutionException + Future futureOfTask = executor.submit(task); + executor.shutdown(); + + try { + return futureOfTask.get(timeout, TimeUnit.SECONDS); + } catch (TimeoutException | InterruptedException e) { + futureOfTask.cancel(true); + Thread.currentThread().interrupt(); + throw new InterruptedException("time for processing (" + timeout + " seconds) is over"); + } catch (ExecutionException e) { + throw new IllegalStateException(e.getCause()); + } + } + + @Deprecated(forRemoval = true) + static void runAndWait(Runnable task) throws InterruptedException { + runAndWait(task, DEFAULT_TIMEOUT); + } + + @Deprecated(forRemoval = true) + private static void runAndWait(Runnable task, int timeout) throws InterruptedException { + ExecutorService executor = Executors.newSingleThreadExecutor(); + + // uses method-"execute" to use UncaughtExceptionHandler for handle uncaught + // exceptions inside "Runnable" + executor.execute(task); + executor.shutdown(); + + if (!executor.awaitTermination(timeout, TimeUnit.SECONDS)) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + throw new InterruptedException("time for processing (" + timeout + " seconds) is over"); + } + } + + private DbTestUtils() { + } +} diff --git a/src/test/java/home/db/PgDdlTest.java b/src/test/java/home/db/PgDdlTest.java new file mode 100644 index 0000000..b6a23f9 --- /dev/null +++ b/src/test/java/home/db/PgDdlTest.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +final class PgDdlTest extends AbstractDdlTest { + + @Override + protected DbType getDbType() { + return DbType.PostgreSQL; + } +} diff --git a/src/test/java/home/db/PgDmlTest.java b/src/test/java/home/db/PgDmlTest.java new file mode 100644 index 0000000..4896c8f --- /dev/null +++ b/src/test/java/home/db/PgDmlTest.java @@ -0,0 +1,221 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.lang.reflect.Field; +import java.sql.SQLException; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import org.junit.jupiter.api.Test; +import org.postgresql.util.PSQLException; + +import home.db.conn.Connector; + +final class PgDmlTest extends AbstractDmlTest { + + private static final int DEFAULT_TIMEOUT = 5; // sec + + private static final String PG_CONNECTION_DATA_HELPER_CLASS = "home.db.conn.PgConnectionDataHelper"; + private static final String QUERY_TIMEOUT_FIELD = "QUERY_TIMEOUT"; + private static final String LOCK_TIMEOUT_FIELD = "LOCK_TIMEOUT"; + + private static final String TEMPORARY_QUERY_TIMEOUT = "3000"; // ms + private static final String TEMPORARY_LOCK_TIMEOUT = "2000"; // ms + + // https://www.ibm.com/docs/en/db2woc?topic=messages-sqlstate + // 57 - Resource Not Available or Operator Intervention + // 57014 - Processing was cancelled as requested. + // + // https://www.postgresql.org/docs/current/errcodes-appendix.html + // 57 - Class 57 — Operator Intervention + // 57014 - query_canceled + private static final String QUERY_TIMEOUT_ERROR_CODE = "57014"; + // https://www.postgresql.org/docs/current/errcodes-appendix.html + // 57 - Class 55 — Object Not In Prerequisite State + // 55P03 - lock_not_available + private static final String LOCK_TIMEOUT_ERROR_CODE = "55P03"; + + private static final String EXPECTED_AND_ACTUAL_NOT_MATCH = """ + Expected and actual %s does not match: + expected: %s + actual: %s + """; + + @Override + protected DbType getDbType() { + return DbType.PostgreSQL; + } + + @Test + @SkipIfDbUnavailable(dbTypes = { "PostgreSQL" }) + void queryTimeoutTest() throws Exception { + String currentQueryTimeout = getCurrentQueryTimeout(); + + resetConnection(); + setQueryTimeout(TEMPORARY_QUERY_TIMEOUT); + + try (var conn = Connector.getConnection(); + var stmt = conn.createStatement(); + var rs = stmt.executeQuery( + "SELECT clock_timestamp(), pg_sleep(10), clock_timestamp()")) { + fail("Expected org.postgresql.util.PSQLException to be thrown, but nothing was thrown."); + } catch (PSQLException e) { + assertEquals(QUERY_TIMEOUT_ERROR_CODE, e.getSQLState()); + } + + setQueryTimeout(currentQueryTimeout); + resetConnection(); + } + + private String getCurrentQueryTimeout() throws Exception { + return getFieldValue(PG_CONNECTION_DATA_HELPER_CLASS, QUERY_TIMEOUT_FIELD, String.class); + } + + private void setQueryTimeout(String queryTimeout) throws Exception { + setFieldValue(queryTimeout, PG_CONNECTION_DATA_HELPER_CLASS, QUERY_TIMEOUT_FIELD); + } + + @Test + @SkipIfDbUnavailable(dbTypes = { "PostgreSQL" }) + void lockTimeoutTest() throws Exception { + String currentQueryTimeout = getCurrentQueryTimeout(); + String currentLockTimeout = getCurrentLockTimeout(); + + resetConnection(); + // query timeout is set so that the blocking request + // does not run longer than necessary + setQueryTimeout(TEMPORARY_QUERY_TIMEOUT); + setLockTimeout(TEMPORARY_LOCK_TIMEOUT); + + // PREPARE + DbTestUtils.execute( + "DROP TABLE IF EXISTS public.check_lock_timeout", + "CREATE TABLE public.check_lock_timeout (id integer, some_data integer)", + "INSERT INTO public.check_lock_timeout (id, some_data) VALUES (1, 11), (2, 22)"); + + ExecutorService executor = Executors.newFixedThreadPool(2); + + // LOCKER + Future futureExceptionOfLocker = executor.submit(() -> executeAndGetException( + "SELECT clock_timestamp(), id, pg_sleep(10) FROM public.check_lock_timeout")); + + TimeUnit.MILLISECONDS.sleep(300); + + // LOCKABLE + Future futureExceptionOfLockable = executor.submit(() -> executeAndGetException( + "DROP TABLE public.check_lock_timeout")); + + executor.shutdown(); + + try { + PSQLException exceptionOfLocker = futureExceptionOfLocker.get(DEFAULT_TIMEOUT, TimeUnit.SECONDS); + checkError(exceptionOfLocker, QUERY_TIMEOUT_ERROR_CODE, + List.of("выполнение оператора отменено из-за тайм-аута", + "canceling statement due to statement timeout")); + + PSQLException exceptionOfLockable = futureExceptionOfLockable.get(DEFAULT_TIMEOUT, TimeUnit.SECONDS); + checkError(exceptionOfLockable, LOCK_TIMEOUT_ERROR_CODE, + List.of("выполнение оператора отменено из-за тайм-аута блокировки", + "canceling statement due to lock timeout")); + } catch (TimeoutException | InterruptedException e) { + futureExceptionOfLocker.cancel(true); + futureExceptionOfLockable.cancel(true); + Thread.currentThread().interrupt(); + throw new IllegalStateException("time for processing (" + DEFAULT_TIMEOUT + " seconds) is over"); + } catch (ExecutionException e) { + throw new IllegalStateException(e.getCause()); + } + + if (!executor.awaitTermination(DEFAULT_TIMEOUT * 2, TimeUnit.SECONDS)) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + throw new InterruptedException("time for processing (" + DEFAULT_TIMEOUT + " seconds) is over"); + } + + setQueryTimeout(currentQueryTimeout); + setLockTimeout(currentLockTimeout); + resetConnection(); + } + + private PSQLException executeAndGetException(String query) throws SQLException { + try { + DbTestUtils.execute(DbTestUtils.NOT_OVERRIDE_QUERY_TIMEOUT, query); + return null; + } catch (PSQLException e) { + return e; + } + } + + private void checkError(PSQLException exceptionOfOperation, + String expectedErrorCode, List expectedErrorMsgs) { + if (exceptionOfOperation != null) { + boolean isEndsWithExpectedMsg = false; + String actualErrorMsg = exceptionOfOperation.getMessage(); + for (String expectedErrorMsg : expectedErrorMsgs) { + if (actualErrorMsg.endsWith(expectedErrorMsg)) { + isEndsWithExpectedMsg = true; + break; + } + } + + assertTrue(isEndsWithExpectedMsg, EXPECTED_AND_ACTUAL_NOT_MATCH + .formatted("error message", String.join(" \n\t|| ", expectedErrorMsgs), actualErrorMsg)); + + String actualErrorCode = exceptionOfOperation.getSQLState(); + assertEquals(expectedErrorCode, actualErrorCode, EXPECTED_AND_ACTUAL_NOT_MATCH + .formatted("error code", expectedErrorCode, actualErrorCode)); + } else { + fail("Expected org.postgresql.util.PSQLException to be thrown, but nothing was thrown."); + } + } + + private String getCurrentLockTimeout() throws Exception { + return getFieldValue(PG_CONNECTION_DATA_HELPER_CLASS, LOCK_TIMEOUT_FIELD, String.class); + } + + private void setLockTimeout(String lockTimeout) throws Exception { + setFieldValue(lockTimeout, PG_CONNECTION_DATA_HELPER_CLASS, LOCK_TIMEOUT_FIELD); + } + + private T getFieldValue(String className, String fieldName, Class returnType) throws Exception { + Field field = getClassField(className, fieldName); + @SuppressWarnings("unchecked") + var fieldValue = (T) field.get(null); + return fieldValue; + } + + private void setFieldValue(Object fieldValue, String className, String fieldName) throws Exception { + Field field = getClassField(className, fieldName); + field.set(null, fieldValue); + } + + private Field getClassField(String className, String fieldName) throws Exception { + Class clazz = Class.forName(className); + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + return field; + } +} diff --git a/src/test/java/home/db/SkipIfDbUnavailable.java b/src/test/java/home/db/SkipIfDbUnavailable.java new file mode 100644 index 0000000..4187034 --- /dev/null +++ b/src/test/java/home/db/SkipIfDbUnavailable.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.Optional; + +import org.junit.jupiter.api.extension.ConditionEvaluationResult; +import org.junit.jupiter.api.extension.ExecutionCondition; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.ExtensionContext; + +import home.Settings; +import home.db.conn.Connector; + +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(DbAvailabilityChecker.class) +@interface SkipIfDbUnavailable { + + String[] dbTypes(); +} + +final class DbAvailabilityChecker implements ExecutionCondition { + + @Override + public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext ctx) { + try { + Optional optional = findAnnotation( + ctx.getElement(), SkipIfDbUnavailable.class); + if (optional.isPresent()) { + SkipIfDbUnavailable annotation = optional.get(); + return getConditionEvaluationOfCurrentDb(annotation.dbTypes()); + } + } catch (Exception e) { + return ConditionEvaluationResult.disabled("Db is not available! " + + e.getMessage()); + } + + return ConditionEvaluationResult.enabled("Doesn't need to check availability of db for work."); + } + + private ConditionEvaluationResult getConditionEvaluationOfCurrentDb( + String[] dbTypesFromAnnotationStr) throws Exception { + DbType currentDbType = Settings.getDatabaseType(); + for (String dbTypeFromAnnotationStr : dbTypesFromAnnotationStr) { + if (currentDbType == DbType.getDbType(dbTypeFromAnnotationStr)) { + return isDbAvailableForWork(currentDbType) + ? ConditionEvaluationResult.enabled("%s is available for work!".formatted(currentDbType)) + : ConditionEvaluationResult.disabled("%s is not available for work!".formatted(currentDbType)); + } + } + + return ConditionEvaluationResult.enabled( + "Doesn't need to check availability of %s for work.".formatted(currentDbType)); + } + + private boolean isDbAvailableForWork(DbType dbType) throws Exception { + return switch (dbType) { + case PostgreSQL -> Connector.testCurrentConnection(); + case SQLite -> AbstractFileDatabaseTestPreparer.isAvailableWorkWithTempFile(); + default -> throw new IllegalArgumentException("Unexpected value: " + dbType); + }; + } +} diff --git a/src/test/java/home/db/SqliteDdlTest.java b/src/test/java/home/db/SqliteDdlTest.java new file mode 100644 index 0000000..8aae828 --- /dev/null +++ b/src/test/java/home/db/SqliteDdlTest.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +import java.nio.file.Files; + +import org.junit.jupiter.api.Test; + +final class SqliteDdlTest extends AbstractDdlTest { + + @Override + protected DbType getDbType() { + return DbType.SQLite; + } + + @Test + @SkipIfDbUnavailable(dbTypes = "SQLite") + void checkGeneratedDbFileTest() { + try (var sampleDbFileStream = getClass().getResourceAsStream(getDbFileName())) { + byte[] sampleDbFileByte = sampleDbFileStream.readAllBytes(); + assertTrue(sampleDbFileByte.length != 0, "Sample Db file is empty."); + + byte[] generatedDbFileByte = Files.readAllBytes(getGeneratedDbFile().toPath()); + assertTrue(generatedDbFileByte.length != 0, "Generated Db file is empty."); + + assertArrayEquals(sampleDbFileByte, generatedDbFileByte, + "Expected and generated SQLite DB file does not match."); + } catch (Exception e) { + fail("Errors while read file.", e); + } + } +} diff --git a/src/test/java/home/db/SqliteDmlTest.java b/src/test/java/home/db/SqliteDmlTest.java new file mode 100644 index 0000000..54dad56 --- /dev/null +++ b/src/test/java/home/db/SqliteDmlTest.java @@ -0,0 +1,24 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.db; + +final class SqliteDmlTest extends AbstractDmlTest { + + @Override + protected DbType getDbType() { + return DbType.SQLite; + } +} diff --git a/src/test/java/home/db/dao/AbstractDaoTest.java b/src/test/java/home/db/dao/DaoTest.java similarity index 65% rename from src/test/java/home/db/dao/AbstractDaoTest.java rename to src/test/java/home/db/dao/DaoTest.java index d7fa9de..c7a5070 100644 --- a/src/test/java/home/db/dao/AbstractDaoTest.java +++ b/src/test/java/home/db/dao/DaoTest.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.db.dao; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -6,34 +21,26 @@ import java.sql.SQLException; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -final class AbstractDaoTest { +final class DaoTest { - private static final Logger LOG = LoggerFactory.getLogger(AbstractDaoTest.class); + private static final Logger LOG = LoggerFactory.getLogger(DaoTest.class); private static final String userMsg = "MY_MESSAGE"; - private static DaoSQLite daoSQLite; - - @BeforeAll - static void init() { - daoSQLite = (DaoSQLite) DaoSQLite.getInstance(); - } - @ParameterizedTest(name = "[{0}] : {2}") @CsvSource(delimiter = ';', value = { // name...........|.results.......|.description "correct_execution; 1, 0, 3, -2, 4; Check results of correct batch execution.", "null_arg ; ; Check work with null args result.", }) - void existingType(String testName, String strBatchResults, String description) + void checkBatchSuccessTest(String testName, String strBatchResults, String description) throws SQLException { int[] intBatchResults = convertToIntArray(strBatchResults); - assertDoesNotThrow(() -> daoSQLite.checkBatchExecution(intBatchResults, userMsg, LOG)); + assertDoesNotThrow(() -> new SQLiteDao().checkBatchExecution(intBatchResults, userMsg, LOG)); } @ParameterizedTest(name = "[{0}]: {2}") @@ -45,15 +52,15 @@ void existingType(String testName, String strBatchResults, String description) void checkBatchFailTest(String testName, String strBatchResults, String erroMsg) { try { int[] intBatchResults = convertToIntArray(strBatchResults); - daoSQLite.checkBatchExecution(intBatchResults, userMsg, LOG); + new SQLiteDao().checkBatchExecution(intBatchResults, userMsg, LOG); fail("Expected java.sql.SQLException to be thrown, but nothing was thrown."); } catch (SQLException e) { String actual = e.getMessage().strip(); assertTrue(actual.endsWith(erroMsg), """ Expected and actual error message does not match: + expected: %s actual: %s - expected : %s - """.formatted(actual, erroMsg)); + """.formatted(erroMsg, actual)); } } diff --git a/src/test/java/home/file/AbstractFileTest.java b/src/test/java/home/file/AbstractFileTest.java index f5ebc04..f78baba 100644 --- a/src/test/java/home/file/AbstractFileTest.java +++ b/src/test/java/home/file/AbstractFileTest.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file; import java.net.URISyntaxException; diff --git a/src/test/java/home/file/ExporterTest.java b/src/test/java/home/file/ExporterTest.java index 140681b..757b78f 100644 --- a/src/test/java/home/file/ExporterTest.java +++ b/src/test/java/home/file/ExporterTest.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/home/file/ImporterTest.java b/src/test/java/home/file/ImporterTest.java index b52af87..abc6141 100644 --- a/src/test/java/home/file/ImporterTest.java +++ b/src/test/java/home/file/ImporterTest.java @@ -1,3 +1,18 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.file; import static org.junit.jupiter.api.Assertions.assertArrayEquals; diff --git a/src/test/java/home/model/VehicleTypeTest.java b/src/test/java/home/model/VehicleTypeTest.java index ba37ac4..c56d865 100644 --- a/src/test/java/home/model/VehicleTypeTest.java +++ b/src/test/java/home/model/VehicleTypeTest.java @@ -1,16 +1,29 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ package home.model; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import home.model.VehicleType; - final class VehicleTypeTest { @ParameterizedTest @@ -30,7 +43,8 @@ void existingType(String type) { @Test void notExistedType() { - assertNull(VehicleType.getVehicleType("notExistedType")); + assertThrows(IllegalArgumentException.class, + () -> VehicleType.getVehicleType("notExistedType")); } @Disabled("Just to show the disable function in the test") diff --git a/src/test/java/home/utils/NamedFormatterTest.java b/src/test/java/home/utils/NamedFormatterTest.java new file mode 100644 index 0000000..6cdb1a6 --- /dev/null +++ b/src/test/java/home/utils/NamedFormatterTest.java @@ -0,0 +1,85 @@ +/******************************************************************************* + * Copyright 2021-2024 Lenar Shamsutdinov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *******************************************************************************/ +package home.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; + +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +final class NamedFormatterTest { + + @ParameterizedTest(name = "checking :'${'{0}'}' ") + @ValueSource(strings = { + "paramname", + "param_name", + "paramname_123", + "paramname123", + }) + void formattingSuccessTest(String paramName) { + String expected = "Start value-of-placeholder text"; + + String template = "Start ${%s} text".formatted(paramName); + Map params = Map.of(paramName, "value-of-placeholder"); + String actual = NamedFormatter.format(template, params); + + assertEquals(expected, actual, """ + Expected and actual text does not match: + expected: %s + actual: %s + """.formatted(expected, actual)); + } + + @Test + void formattingSeveralEqualParamsTest() { + String expected = "Start value_text middle [123] end value_text"; + + String template = "Start ${param_1} middle [${param_2}] end ${param_1}"; + Map params = Map.ofEntries( + Map.entry("param_1", "value_text"), + Map.entry("param_2", 123)); + String actual = NamedFormatter.format(template, params); + + assertEquals(expected, actual, """ + Expected and actual text does not match: + expected: %s + actual: %s + """.formatted(expected, actual)); + } + + @Test + void formattingFailTest() { + String incorrectParamName = "param-name"; + + String expected = "Start value-of-placeholder text"; + + String template = "Start ${%s} text".formatted(incorrectParamName); + Map params = Map.of(incorrectParamName, "value-of-placeholder"); + String actual = NamedFormatter.format(template, params); + + if (expected.equals(actual)) { + fail(""" + Expected and actual text should be different: + expected: %s + actual: %s + """.formatted(expected, actual)); + } + } +} diff --git a/src/test/resources/home/db/pg_test_settings.properties b/src/test/resources/home/db/pg_test_settings.properties new file mode 100644 index 0000000..7dddf3d --- /dev/null +++ b/src/test/resources/home/db/pg_test_settings.properties @@ -0,0 +1,8 @@ +db.database=mirage +db.host=127.0.0.1 +db.password=mirage +db.port=5432 +db.type=PostgreSQL +db.user=mirage +gui.auto_resize_table_width=false +gui.style=crossplatform diff --git a/tools/clean_temp_files.bat b/tools/clean_temp_files.bat new file mode 100644 index 0000000..f86c8e0 --- /dev/null +++ b/tools/clean_temp_files.bat @@ -0,0 +1,68 @@ +rem Removes temporary project files from the project directory: logs, *.db, *.properties, *.log. + +@echo off + +chcp 1251 + +set "CURRENT_DIR=%~dp0" +set "SCRIPT=%0" +set "FUNCTION=%CURRENT_DIR%common_functions.bat" + +set "PROJ_DIR=%CURRENT_DIR%.." +set "LOGS_DIR=%PROJ_DIR%\logs" +set "DB_FILE=%PROJ_DIR%\*.db" +set "PROPERTIES_FILE=%PROJ_DIR%\*.properties" +set "LOG_FILE=%PROJ_DIR%\*.log" + +set "TOOLS_DIR=%PROJ_DIR%\tools" +set "TOOLS_LOGS_DIR=%TOOLS_DIR%\logs" +set "TOOLS_DB_FILE=%TOOLS_DIR%\*.db" +set "TOOLS_PROPERTIES_FILE=%TOOLS_DIR%\*.properties" +set "TOOLS_LOG_FILE=%TOOLS_DIR%\*.log" + +call "%FUNCTION%" fnc_log "Started: %SCRIPT%" + +call :fnc_check_and_delete_dir "%LOGS_DIR%" & +call :fnc_check_and_delete_file "%DB_FILE%" & +call :fnc_check_and_delete_file "%PROPERTIES_FILE%" & +call :fnc_check_and_delete_file "%LOG_FILE%" & +call :fnc_check_and_delete_dir "%TOOLS_LOGS_DIR%" & +call :fnc_check_and_delete_file "%TOOLS_DB_FILE%" & +call :fnc_check_and_delete_file "%TOOLS_PROPERTIES_FILE%" & +call :fnc_check_and_delete_file "%TOOLS_LOG_FILE%" & +call "%FUNCTION%" fnc_log "all temporary project files checked for existence and deleted" + +call :fnc_check_and_delete_dir "%TOOLS_LOGS_DIR%" & +call "%FUNCTION%" fnc_log "all temporary project files checked for existence and deleted" + +rem goes to the end of file if this script was launched from another bat-file +if [%1]==[runs_from_another_bat_file] ( + goto :eof +) + +rem It pauses and prints "Press any key to continue...". +pause +exit 0 + +rem ---f_u_n_c_t_i_o_n_s--- + +:fnc_check_and_delete_dir + rem checks for directory existence and deletes if it exists + if exist "%~1\" ( + rmdir /s /q "%~1" + call "%FUNCTION%" fnc_log "removed: %~1" + ) else ( + call "%FUNCTION%" fnc_log "didn't exist: %~1" + ) + exit /B 0 + +:fnc_check_and_delete_file + rem checks for file existence and deletes if it exists + if exist "%~1" ( + rem it is file + del /q /f "%~1" + call "%FUNCTION%" fnc_log "removed: %~1" + ) else ( + call "%FUNCTION%" fnc_log "didn't exist: %~1" + ) + exit /B 0 diff --git a/tools/clean_temp_files.sh b/tools/clean_temp_files.sh new file mode 100644 index 0000000..dadbca7 --- /dev/null +++ b/tools/clean_temp_files.sh @@ -0,0 +1,65 @@ +#!/bin/bash + +# Removes temporary project files from the project directory: logs, *.db, *.properties, *.log. + +#CURRENT_DIR=$(cd "`dirname $0`" && pwd) +CURRENT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +FUNCTIONS=${CURRENT_DIR}/common_functions.sh + +PROJ_DIR=${CURRENT_DIR}/.. +LOGS_DIR=${PROJ_DIR}/logs +DB_FILE=${PROJ_DIR}/*.db +PROPERTIES_FILE=${PROJ_DIR}/*.properties +LOG_FILE=${PROJ_DIR}/*.log + +TOOLS_LOGS_DIR=${CURRENT_DIR}/logs +TOOLS_DB_FILE=${CURRENT_DIR}/*.db +TOOLS_PROPERTIES_FILE=${CURRENT_DIR}/*.properties +TOOLS_LOG_FILE=${CURRENT_DIR}/*.log + +SCRIPT=${CURRENT_DIR}/$(basename "$0") + +# import common functions +source "${FUNCTIONS}" + +function fnc_check_and_delete_dir() { + # checks for directory existence and deletes if it exists + if [ -d "$1" ]; then + rm -rf "$1" + fnc_log "removed: $1" + else + fnc_log "didn't exist: $1" + fi +} + +function fnc_check_and_delete_file() { + # checks for file existence and deletes if it exists + if [ $(find "$1" -maxdepth 1 -wholename "$2" | wc -l) -gt 0 ]; then + find "$1" -maxdepth 1 -wholename "$2" -delete + fnc_log "removed: $2" + else + fnc_log "didn't exist: $2" + fi +} + +fnc_log "Started: $SCRIPT" + +fnc_check_and_delete_file "$PROJ_DIR" "$PROPERTIES_FILE" + +fnc_check_and_delete_dir "$LOGS_DIR" && +fnc_check_and_delete_file "$PROJ_DIR" "$DB_FILE" && +fnc_check_and_delete_file "$PROJ_DIR" "$PROPERTIES_FILE" && +fnc_check_and_delete_file "$PROJ_DIR" "$LOG_FILE" && +fnc_check_and_delete_dir "$TOOLS_LOGS_DIR" && +fnc_check_and_delete_file "$CURRENT_DIR" "$TOOLS_DB_FILE" && +fnc_check_and_delete_file "$CURRENT_DIR" "$TOOLS_PROPERTIES_FILE" && +fnc_check_and_delete_file "$CURRENT_DIR" "$TOOLS_LOG_FILE" && +fnc_log "all temporary project files checked for existence and deleted" + +# If this script was not launched from another sh-file, then +# it pauses, prints "press enter to exit" and exit. +if [ -z "$1" ] || [ "$1" != "runs_from_another_sh_file" ]; then + read -p "press enter to exit..." + exit 0 +fi + diff --git a/tools/common_functions.bat b/tools/common_functions.bat new file mode 100644 index 0000000..4e7f270 --- /dev/null +++ b/tools/common_functions.bat @@ -0,0 +1,26 @@ +rem Contains common functions + +@echo off + +set "CURRENT_DIR=%~dp0" +set "CLEAN_TEMP_FILES_SCRIPT=%CURRENT_DIR%clean_temp_files.bat" + +set "FUNCTION_NAME=%~1" +set "FUNCTION_ARGUMENT_1=%~2" + +call :%FUNCTION_NAME% +goto exit + +:fnc_log + rem logging with datetime in default format + echo %date% %time% ^| %FUNCTION_ARGUMENT_1% + echo. + goto:eof + +:fnc_clean_temp_files + rem runs script for remove temporary project files + call "%CLEAN_TEMP_FILES_SCRIPT%" runs_from_another_bat_file + goto:eof + +:exit + exit /b diff --git a/tools/common_functions.sh b/tools/common_functions.sh new file mode 100644 index 0000000..f8ee96a --- /dev/null +++ b/tools/common_functions.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Contains common functions + +#CURRENT_DIR=$(cd "`dirname $0`" && pwd) +CURRENT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" +CLEAN_TEMP_FILES_SCRIPT=${CURRENT_DIR}/clean_temp_files.sh + +DATE_FORMAT='+%Y-%m-%d %H:%M:%S' + +function fnc_get_date() { + # get datetime in given format + echo $(date -u "${DATE_FORMAT}") "UTC |" +} + +function fnc_log() { + # logging with datetime + local ex=$? + echo -e $(fnc_get_date) "${1}" +} + +function fnc_clean_temp_files() { + # runs script for remove temporary project files + "${CLEAN_TEMP_FILES_SCRIPT}" runs_from_another_sh_file +} + diff --git a/tools/run.bat b/tools/run.bat new file mode 100644 index 0000000..5aae5b7 --- /dev/null +++ b/tools/run.bat @@ -0,0 +1,59 @@ +rem Build and run (without tests) + +rem just skip tests execution; this also gives some warnings about what versions you can use each flag with +rem mvn clean package -DskipTests + +rem this skip compilation tests as well as execution +rem mvn clean package -Dmaven.test.skip=true +rem mvn clean package -Dmaven.test.skip + +cls + +@echo off + +chcp 1251 + +set "LANG=ru_RU.UTF-8" +set "LANGUAGE=ru" +set "LC_CTYPE=ru_RU.UTF-8" + +set "TZ=UTC" + +set "CURRENT_DIR=%~dp0" +set "SCRIPT=%0" +set "FUNCTION=%CURRENT_DIR%common_functions.bat" + +call "%FUNCTION%" fnc_log "Started: %SCRIPT%" + +set "PROJ_DIR=%CURRENT_DIR%.." +set "POM_FILE=%PROJ_DIR%\pom.xml" + +call "%FUNCTION%" fnc_clean_temp_files & +call "%FUNCTION%" fnc_log "temp files removed successfully" & +call mvn -f "%POM_FILE%" clean package -DskipTests & +call "%FUNCTION%" fnc_log "maven done successfully" + +set "TARGET_DIR=%PROJ_DIR%\target" +set "JAR_FILE=%TARGET_DIR%\vehicle-*.jar" + +rem getting the full name of jar-file +for %%f in ("%JAR_FILE%") do ( + set "JAR_FILE=%TARGET_DIR%\%%~nxf" +) + +set "JAVA_OPTS=-Xmx1024m" +set "JAVA_OPTS=%JAVA_OPTS% -Xms256m" +set "JAVA_OPTS=%JAVA_OPTS% -XX:-OmitStackTraceInFastThrow" + +set "JAVA_CMD=java %JAVA_OPTS% -jar ^"%JAR_FILE%^"" + +%JAVA_CMD% & +call "%FUNCTION%" fnc_log "%JAR_FILE% started successfully" & +call "%FUNCTION%" fnc_clean_temp_files & +call "%FUNCTION%" fnc_log "temp files removed successfully" & +pause & +exit 0 + +call "%FUNCTION%" fnc_log "run of %JAR_FILE% failed" & +pause & +exit 1 diff --git a/tools/run.sh b/tools/run.sh new file mode 100644 index 0000000..03e2157 --- /dev/null +++ b/tools/run.sh @@ -0,0 +1,71 @@ +#!/bin/bash + +# Build and run (without tests) + +# just skip tests execution; this also gives some warnings about what versions you can use each flag with +# mvn clean package -DskipTests + +# this skip compilation tests as well as execution +# mvn clean package -Dmaven.test.skip=true +# mvn clean package -Dmaven.test.skip + +clear + +export LANG=ru_RU.UTF-8 +export LANGUAGE=ru +export LC_CTYPE=ru_RU.UTF-8 + +# export TZ=UTC + +CURRENT_DIR=$(cd "`dirname $0`" && pwd) +SCRIPT=${CURRENT_DIR}/$(basename "$0") +FUNCTIONS=${CURRENT_DIR}/common_functions.sh + +# import common functions +source "${FUNCTIONS}" + +fnc_log "Started: $SCRIPT" + +PROJ_DIR=${CURRENT_DIR}/.. +POM_FILE=${PROJ_DIR}/pom.xml + +fnc_clean_temp_files && +fnc_log "temp files removed successfully" && +mvn -f "${POM_FILE}" clean package -DskipTests && +fnc_log "maven done successfully" + +TARGET_DIR=${PROJ_DIR}/target +JAR_FILE_TEMPLATE=vehicle-*.jar +JAR_FILE=${TARGET_DIR}/${JAR_FILE_TEMPLATE} + +# getting the full name of jar-file +for F in "$TARGET_DIR"/*; do + BASE_NAME=$(basename "${F}") + if [[ $BASE_NAME == $JAR_FILE_TEMPLATE ]] ; then + JAR_FILE=${TARGET_DIR}/${BASE_NAME} + break + fi +done + +JAVA_OPTS=" -Xmx1024m" +JAVA_OPTS+=" -Xms256m" +JAVA_OPTS+=" -XX:-OmitStackTraceInFastThrow" + +## doesn't work because it can't handle spaces in the path: +## JAVA_CMD="java ${JAVA_OPTS} -jar ""${JAR_FILE}""" +## +## that's why didn't use one of these variants: +## exec ${JAVA_CMD} && +## ${JAVA_CMD} && + +java ${JAVA_OPTS} -jar "${JAR_FILE}" && +fnc_log "$JAR_FILE started successfully" && +fnc_clean_temp_files && +fnc_log "temp files removed successfully" && +read -p "press enter to exit..." && +exit 0 + +fnc_log "run of $JAR_FILE failed" && +read -p "press enter to exit..." && +exit 1 + diff --git a/tools/test.bat b/tools/test.bat new file mode 100644 index 0000000..4a128fc --- /dev/null +++ b/tools/test.bat @@ -0,0 +1,35 @@ +rem Build and test (removes temporary project files before and after tests) + +cls + +@echo off + +chcp 1251 + +set "LANG=ru_RU.UTF-8" +set "LANGUAGE=ru" +set "LC_CTYPE=ru_RU.UTF-8" + +set "TZ=UTC" + +set "CURRENT_DIR=%~dp0" +set "SCRIPT=%0" +set "FUNCTION=%CURRENT_DIR%common_functions.bat" + +call "%FUNCTION%" fnc_log "Started: %SCRIPT%" + +set "PROJ_DIR=%CURRENT_DIR%.." +set "POM_FILE=%PROJ_DIR%\pom.xml" + +call "%FUNCTION%" fnc_clean_temp_files & +call "%FUNCTION%" fnc_log "temp files removed successfully" & +call mvn -f "%POM_FILE%" clean test & +call "%FUNCTION%" fnc_log "maven done successfully" & +call "%FUNCTION%" fnc_clean_temp_files & +call "%FUNCTION%" fnc_log "temp files removed successfully" & +pause & +exit 0 + +call "%FUNCTION%" fnc_log "maven error" & +pause & +exit 1 diff --git a/tools/test.sh b/tools/test.sh new file mode 100644 index 0000000..6ec0387 --- /dev/null +++ b/tools/test.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Build and test (removes temporary project files before and after tests) + +clear + +export LANG=ru_RU.UTF-8 +export LANGUAGE=ru +export LC_CTYPE=ru_RU.UTF-8 + +# export TZ=UTC + +CURRENT_DIR=$(cd "`dirname $0`" && pwd) +SCRIPT=${CURRENT_DIR}/$(basename "$0") +FUNCTIONS=${CURRENT_DIR}/common_functions.sh + +# import common functions +source "${FUNCTIONS}" + +fnc_log "Started: $SCRIPT" + +PROJ_DIR=${CURRENT_DIR}/.. +POM_FILE=${PROJ_DIR}/pom.xml + +fnc_clean_temp_files && +fnc_log "temp files removed successfully" && +mvn -f "${POM_FILE}" clean test && +fnc_log "maven done successfully" && +fnc_clean_temp_files && +fnc_log "temp files removed successfully" && +read -p "press enter to exit..." && +exit 0 + +fnc_log "maven error" && +read -p "press enter to exit..." && +exit 1 +