From 46d50583a4d175e63fc716ae8ef748f5fc036e3a Mon Sep 17 00:00:00 2001 From: M_Viper Date: Fri, 15 Aug 2025 19:19:53 +0000 Subject: [PATCH] Dateien nach "src/main/java/com/tylersuehr/sql" hochladen --- .../com/tylersuehr/sql/ContentValues.java | 88 ++++++ .../java/com/tylersuehr/sql/SQLBuilder.java | 121 +++++++++ .../com/tylersuehr/sql/SQLiteCloseable.java | 81 ++++++ .../com/tylersuehr/sql/SQLiteDatabase.java | 257 ++++++++++++++++++ .../com/tylersuehr/sql/SQLiteOpenHelper.java | 107 ++++++++ 5 files changed, 654 insertions(+) create mode 100644 src/main/java/com/tylersuehr/sql/ContentValues.java create mode 100644 src/main/java/com/tylersuehr/sql/SQLBuilder.java create mode 100644 src/main/java/com/tylersuehr/sql/SQLiteCloseable.java create mode 100644 src/main/java/com/tylersuehr/sql/SQLiteDatabase.java create mode 100644 src/main/java/com/tylersuehr/sql/SQLiteOpenHelper.java diff --git a/src/main/java/com/tylersuehr/sql/ContentValues.java b/src/main/java/com/tylersuehr/sql/ContentValues.java new file mode 100644 index 0000000..dfbc861 --- /dev/null +++ b/src/main/java/com/tylersuehr/sql/ContentValues.java @@ -0,0 +1,88 @@ +package com.tylersuehr.sql; +import java.io.Serializable; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +/** + * Basic wrapping of the Java {@link Map} object. + * + * The purpose of doing this is to normalize what types of objects can be inserted into + * the SQLite database, as it only allows for specific data types. + * + * @author Tyler Suehr + */ +public final class ContentValues { + private final Map data; + + + public ContentValues() { + this.data = new LinkedHashMap<>(); + } + + public ContentValues(final int initialCapacity) { + this.data = new LinkedHashMap<>(initialCapacity); + } + + public ContentValues put(final String key, final String value) { + this.data.put(key, value); + return this; + } + + public ContentValues put(final String key, final int value) { + this.data.put(key, value); + return this; + } + + public ContentValues put(final String key, final short value) { + this.data.put(key, value); + return this; + } + + public ContentValues put(final String key, final long value) { + this.data.put(key, value); + return this; + } + + public ContentValues put(final String key, final float value) { + this.data.put(key, value); + return this; + } + + public ContentValues put(final String key, final double value) { + this.data.put(key, value); + return this; + } + + public ContentValues put(final String key, final boolean value) { + this.data.put(key, value); + return this; + } + + public ContentValues put(final String key, final Serializable value) { + this.data.put(key, value); + return this; + } + + public int size() { + return data.size(); + } + + Object get(final String key) { + return data.get(key); + } + + @SuppressWarnings("unchecked") + T getSerializable(final String key) { + return (T)data.get(key); + } + + Collection getData() { + return data.values(); + } + + Set getKeys() { + return data.keySet(); + } +} \ No newline at end of file diff --git a/src/main/java/com/tylersuehr/sql/SQLBuilder.java b/src/main/java/com/tylersuehr/sql/SQLBuilder.java new file mode 100644 index 0000000..395b999 --- /dev/null +++ b/src/main/java/com/tylersuehr/sql/SQLBuilder.java @@ -0,0 +1,121 @@ +package com.tylersuehr.sql; + +/** + * Utility to help construct SQL queries and commands. + * @author Tyler Suehr + */ +final class SQLBuilder { + // SELECT * FROM [table] WHERE [col1] = value ORDER BY [col] LIMIT 0; + static String createQuery(String table, String selection, String order, String limit) { + final StringBuilder sb = new StringBuilder(); + sb.append("SELECT * FROM ").append("[").append(table).append("]"); + sb.append(selection != null ? " WHERE " + selection : ""); + sb.append(order != null ? " ORDER BY " + order : ""); + sb.append(limit != null ? " LIMIT " + limit : ""); + sb.append(";"); + return sb.toString(); + } + + // SELECT [col1],[col2],[col3] FROM [table] WHERE [col] = value ORDER BY [col] LIMIT 0; + static String createQuery(String table, String[] cols, String selection, String order, String limit) { + final StringBuilder sb = new StringBuilder(); + sb.append("SELECT "); + + if (cols != null) { + int i = 0; + for (String c : cols) { + sb.append((i > 0) ? "," : ""); + sb.append("[").append(c).append("]"); + i++; + } + sb.append(" FROM "); + } else { + sb.append("* FROM "); + } + sb.append("[").append(table).append("]"); + + sb.append(selection != null ? " WHERE " + selection : ""); + sb.append(order != null ? " ORDER BY " + order : ""); + sb.append(limit != null ? " LIMIT " + limit : ""); + sb.append(";"); + return sb.toString(); + } + + // INSERT INTO [table] ([col1],[col2],[col3],[col4]) VALUES ('test', 'test2', 123, 12.123); + static String createInsert(String table, ContentValues values) { + final StringBuilder sb = new StringBuilder(); + sb.append("INSERT INTO "); + sb.append("[").append(table).append("] ("); + + int i = 0; + for (String col : values.getKeys()) { + sb.append((i > 0) ? "," : ""); + sb.append("[").append(col).append("]"); + i++; + } + sb.append(") VALUES ("); + + i = 0; + for (Object o : values.getData()) { + sb.append((i > 0) ? "," : ""); + if (o instanceof String) { + sb.append("'").append(o).append("'"); + } else if (o instanceof Boolean) { + Boolean b = (Boolean) o; + if (b) { + sb.append("1"); + } else { + sb.append("0"); + } + } else { + sb.append(o); + } + i++; + } + sb.append(");"); + return sb.toString(); + } + + // UPDATE [table] SET [col1] = 'test' WHERE [col2] = 3; + static String createUpdate(String table, ContentValues values, String selection) { + final StringBuilder sb = new StringBuilder(120); + sb.append("UPDATE "); + sb.append("[").append(table).append("]"); + sb.append(" SET "); + + int i = 0; + for (String col : values.getKeys()) { + sb.append((i > 0) ? "," : ""); + sb.append("[").append(col).append("]"); + sb.append("="); + + if (values.get(col) instanceof String) { + sb.append("'").append(values.get(col)).append("'"); + } else if (values.get(col) instanceof Boolean) { + Boolean b = (Boolean) values.get(col); + if (b) { + sb.append("1"); + } else { + sb.append("0"); + } + } else { + sb.append(values.get(col)); + } + i++; + } + + sb.append(selection != null ? " WHERE " + selection : ""); + sb.append(";"); + return sb.toString(); + } + + // DELETE FROM [table] WHERE [col1] = 23; + static String createDelete(String table, String selection) { + final StringBuilder sb = new StringBuilder(); + sb.append("DELETE FROM "); + sb.append("[").append(table).append("]"); + sb.append(selection != null ? " WHERE " + selection : ""); + sb.append(";"); + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/com/tylersuehr/sql/SQLiteCloseable.java b/src/main/java/com/tylersuehr/sql/SQLiteCloseable.java new file mode 100644 index 0000000..03c0be4 --- /dev/null +++ b/src/main/java/com/tylersuehr/sql/SQLiteCloseable.java @@ -0,0 +1,81 @@ +/* + * MIT License + * + * Copyright (c) Tyler Suehr 2019. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.tylersuehr.sql; +import java.io.Closeable; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Stores a count of references to this object. + * + * This prevents a thread from actually closing the database while another thread may + * still be using it. This will close itself after the last reference has been released. + * + * {@link #onAllReferencesReleased()} is called when the last reference is released. + * + * @author Tyler Suehr + */ +abstract class SQLiteCloseable implements Closeable { + private final AtomicInteger refs = new AtomicInteger(0); + + + @Override + public final void close() { + releaseReference(); + } + + /** + * Called when the last reference to this object is released. + * This method is to be used for actual database cleanup. + */ + protected abstract void onAllReferencesReleased(); + + /** + * Acquires a reference to this object. + */ + protected final void acquireReference() { + this.refs.getAndIncrement(); + } + + /** + * Releases a single reference from this object. + * Determines if this object should invoke {@link #onAllReferencesReleased()}. + */ + protected final void releaseReference() { + final int count = refs.decrementAndGet(); + if (count < 0) { + throw new IllegalStateException("Cannot have less than 0 references!"); + } else if (count == 0) { + onAllReferencesReleased(); + } + } + + /** + * Determines if there is at least 1 reference to this object. + * @return true if at least 1 reference exists, otherwise false + */ + protected final boolean hasReference() { + return refs.get() > 0; + } +} \ No newline at end of file diff --git a/src/main/java/com/tylersuehr/sql/SQLiteDatabase.java b/src/main/java/com/tylersuehr/sql/SQLiteDatabase.java new file mode 100644 index 0000000..8a83591 --- /dev/null +++ b/src/main/java/com/tylersuehr/sql/SQLiteDatabase.java @@ -0,0 +1,257 @@ + +package com.tylersuehr.sql; +import net.licks92.wirelessredstone.ConfigManager; +import net.licks92.wirelessredstone.WirelessRedstone; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +/** + * The SQLite database itself. + * This object allows ease of querying or executing commands. + * + * Will handle establishing the connection, managing statements, and closing the connection + * to the database by utilizing the SQLite JDBC drivers. + * + * The following operations are supported: + * (1) Insert data into the database. {@link #insert(String, ContentValues)} + * (2) Update data in the database. {@link #update(String, ContentValues, String)} + * (3) Delete data in the database. {@link #delete(String, String)} + * (4) Query data in the database. {@link #query(String, String, String, String)} + * (5) Raw query data in the database. {@link #rawQuery(String)} + * (6) Raw command on the database. {@link #execSql(String)} + * + * @author Tyler Suehr + */ +public final class SQLiteDatabase extends SQLiteCloseable { + private static final String DRIVER = "org.sqlite.JDBC"; + private static final String PATH = "jdbc:sqlite:"; + private Connection connection; + private Statement statement; + + + SQLiteDatabase(String dbName) { + openConnection(dbName); + } + + @Override + protected void onAllReferencesReleased() { + try { + if (statement != null) { + this.statement.close(); + } + if (connection != null) { + this.connection.close(); + } + WirelessRedstone.getWRLogger().debug("All references released!"); + } catch (SQLException ex) { + logException(ex); + } + } + + /** + * Queries data from the SQLite database. + * + * @param table the name of the table to query + * @param selection the WHERE clause (i.e. "[id]=12") + * @param order the ORDER BY clause (i.e. "[timestamp ASC]") + * @param limit the LIMIT clause (i.e. "4") + * @return the results + */ + public ResultSet query(String table, String selection, String order, String limit) { + acquireReference(); + try { + final String SQL = SQLBuilder.createQuery(table, selection, order, limit); + return statement.executeQuery(SQL); + } catch (SQLException ex) { + logException(ex); + return null; + } finally { + releaseReference(); + } + } + + /** + * Queries data from the SQLite database. + * + * @param table the name of the table to query + * @param columns string array of columns to select + * @param selection the WHERE clause (i.e. "[id]=12") + * @param order the ORDER BY clause (i.e. "[timestamp ASC]") + * @param limit the LIMIT clause (i.e. "4") + * @return the results + */ + public ResultSet query(String table, String[] columns, String selection, String order, String limit) { + acquireReference(); + try { + final String SQL = SQLBuilder.createQuery(table, columns, selection, order, limit); + return statement.executeQuery(SQL); + } catch (SQLException ex) { + logException(ex); + return null; + } finally { + releaseReference(); + } + } + + /** + * Convenience method for inserting data into the SQLite database. + * + * @param table the name of the table + * @param values the content to be inserted + */ + public void insert(String table, ContentValues values) { + acquireReference(); + try { + final String SQL = SQLBuilder.createInsert(table, values); + this.statement.executeUpdate(SQL); + this.connection.commit(); + } catch (SQLException ex) { + if (WirelessRedstone.getInstance() != null) { + if (ConfigManager.getConfig().getDebugMode()) { + logException(ex); + } + } else { + logException(ex); + } + } finally { + releaseReference(); + } + } + + /** + * Convenience method for updating data in the SQLite database. + * + * @param table the name of the table + * @param values the content to be updated + * @param selection the WHERE clause + */ + public void update(String table, ContentValues values, String selection) { + acquireReference(); + try { + final String SQL = SQLBuilder.createUpdate(table, values, selection); + this.statement.executeUpdate(SQL); + this.connection.commit(); + } catch (SQLException ex) { + logException(ex); + } finally { + releaseReference(); + } + } + + /** + * Convenience method for deleting data in the SQLite database. + * + * @param table the name of the table + * @param selection the WHERE clause + */ + public void delete(String table, String selection) { + acquireReference(); + try { + final String SQL = SQLBuilder.createDelete(table, selection); + this.statement.executeUpdate(SQL); + this.connection.commit(); + } catch (SQLException ex) { + logException(ex); + } finally { + releaseReference(); + } + } + + /** + * Queries data from the SQLite database using a raw SQL query. + * + * @param sql the SQL query to run + * @return the results + */ + public ResultSet rawQuery(String sql) { + acquireReference(); + try { + return statement.executeQuery(sql); + } catch (SQLException ex) { + logException(ex); + return null; + } finally { + releaseReference(); + } + } + + /** + * Executes a command on the SQLite database using a raw SQL query. + * @param sql the SQL query to run + */ + public void execSql(String sql) { + acquireReference(); + try { + this.statement.executeUpdate(sql); + this.connection.commit(); + } catch (SQLException ex) { + logException(ex); + } finally { + releaseReference(); + } + } + + /** + * Sets the user version of the SQLite database. + * @param version the user version to be set + */ + void setVersion(int version) { + acquireReference(); + try { + final String SQL = "PRAGMA user_version=" + version; + this.statement.executeUpdate(SQL); + this.connection.commit(); + } catch (SQLException ex) { + logException(ex); + } finally { + releaseReference(); + } + } + + /** + * Gets the user version of the SQLite database. + * @return the user version of the database + */ + int getVersion() { + acquireReference(); + try { + final String SQL = "PRAGMA user_version"; + ResultSet c = statement.executeQuery(SQL); + return c.getInt("user_version"); + } catch (SQLException ex) { + logException(ex); + return -1; + } finally { + releaseReference(); + } + } + + /** + * Opens a connection to the SQLite database. + * @param dbName the name of the database file (don't include file extension) + */ + private void openConnection(String dbName) { + try { + Class.forName(DRIVER); + this.connection = DriverManager.getConnection(PATH + dbName); + this.connection.setAutoCommit(false); + this.statement = connection.createStatement(); + acquireReference(); + } catch (ClassNotFoundException|SQLException ex) { + logException(ex); + } + } + + /** + * Convenience method to log an exception and print its stacktrace. + * @param ex the exception + */ + private void logException(final Exception ex) { + System.err.println("SQLite > " + ex.getMessage()); + ex.printStackTrace(); + } +} \ No newline at end of file diff --git a/src/main/java/com/tylersuehr/sql/SQLiteOpenHelper.java b/src/main/java/com/tylersuehr/sql/SQLiteOpenHelper.java new file mode 100644 index 0000000..39b698c --- /dev/null +++ b/src/main/java/com/tylersuehr/sql/SQLiteOpenHelper.java @@ -0,0 +1,107 @@ +package com.tylersuehr.sql; +import net.licks92.wirelessredstone.WirelessRedstone; + +import java.io.Closeable; +import java.io.File; + +/** + * Manages the SQLite database file, allowing it to be versioned. + * + * This helper affords the following database techniques: + * (1) Code-First: Create a database during runtime. + * (2) Database-First: Use an existing SQLite database file during runtime. + * + * Code-First Approach + * Create an object and inherit {@link SQLiteOpenHelper}. Implement the abstract methods. + * + * When {@link SQLiteOpenHelper} is instantiated, if the SQLite database file doesn't exist, + * it will be created and then will call, {@link #onCreate(SQLiteDatabase)}. This can be used + * to create all your tables, views, and insert statements if needed. + * + * If the SQLite database file already exists, it will compare the given version to the + * user_version of the database and call, {@link #onUpdate(SQLiteDatabase, int, int)}, if your + * given version was higher than the user_version. This can be used to drop all the tables and + * re-create them if you've updated the table structure. + * + * Database-First Approach + * Create an object and inherit {@link SQLiteOpenHelper}. Implement the abstract methods. + * + * You will not need to worry about using {@link #onCreate(SQLiteDatabase)} because the database + * will already have been created because it already exists. + * + * When {@link SQLiteOpenHelper} is instantiated, if the SQLite database file already exists, it + * will compare the given version to the user_version of the database and call, + * {@link #onUpdate(SQLiteDatabase, int, int)}, if your given version was higher than the + * user_version. This can be used to drop all the tables and re-create them if you've updated + * the table structure. + * + * @author Tyler Suehr + */ +public abstract class SQLiteOpenHelper implements Closeable { + /* Stores reference to the SQLite database instance */ + private SQLiteDatabase database; + /* Stores version of the SQLite database */ + private final int version; + /* Stores name of the SQLite database */ + private final String name; + + + public SQLiteOpenHelper(final String dbName, final int version) { + this.name = (dbName.contains(".db") ? dbName : dbName.concat(".db")); + this.version = version; + } + + @Override + public final void close() { + if (database != null) { + this.database.close(); + } + } + + /** + * Called when a new database is created. + * @param db the created database + */ + protected abstract void onCreate(SQLiteDatabase db); + + /** + * Called when a database is updated. + * + * @param db the updated database + * @param oldV the old user version number + * @param newV the new user version number + */ + protected abstract void onUpdate(SQLiteDatabase db, int oldV, int newV); + + /** + * Lazily loads the SQLite database, ensuring only one instance is available. + * @return the SQLite database + */ + public final SQLiteDatabase getWritableInstance() { + if (database == null) { + final File file = new File(name); + final boolean alreadyExists = file.exists(); + + // This creates our SQLite database file for us, so check if it + // already exists before this call. + this.database = new SQLiteDatabase(name); + + // Check if the database file already exists + if (alreadyExists) { + // Check if the database should be updated + final int curVersion = database.getVersion(); + if (version > curVersion) { + onUpdate(database, curVersion, version); + this.database.setVersion(version); + WirelessRedstone.getWRLogger().debug("SQLite database updated!"); + } + } else { + // Create our database, since it doesn't exist + onCreate(database); + this.database.setVersion(version); + WirelessRedstone.getWRLogger().debug("SQLite database created!"); + } + } + return database; + } +} \ No newline at end of file