Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IN PROGRESS] Implement SQLite as a replacement for XML-based storage in saving game data #133

Draft
wants to merge 11 commits into
base: sf-auto-merge
Choose a base branch
from
Binary file added FSG_FILE.fsg
Binary file not shown.
Empty file added FSG_FILE.xml
Empty file.
Empty file added XML_FILE.xml
Empty file.
82 changes: 82 additions & 0 deletions src/net/sf/freecol/common/io/FreeColSQLReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright (C) 2002-2022 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/

package net.sf.freecol.common.io;
import java.io.Closeable;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
* This code creates a `FreeColSQLReader` class that connects to an
* SQLite database and is capable of executing queries.
* It maintains a connection to the SQLite file and a statement object used to
* execute the queries. The `ReadScopeType` is also included in the implementation,
* similar to the XML implementation.
*/
public class FreeColSQLReader implements Closeable {

private static final Logger logger = Logger.getLogger(FreeColSQLReader.class.getName());

/**
* Enum representing the scope of the data reading.
*/
public enum ReadScopeType {
CLIENT, // Only the client-visible information
SERVER, // Full server-visible information
LOAD // Absolutely everything needed to load the game state
}

private Connection connection;
private Statement statement;
private final ReadScopeType readScope;

public FreeColSQLReader(String dbPath, ReadScopeType scope) throws SQLException {
this.connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath);
this.statement = connection.createStatement();
this.readScope = (scope == null) ? ReadScopeType.LOAD : scope;
}

/**
* Executes a query and returns the result set.
*
* @param query SQL query to execute.
* @return ResultSet containing the result of the query.
* @throws SQLException If the query execution fails.
*/
public ResultSet executeQuery(String query) throws SQLException {
return statement.executeQuery(query);
}

@Override
public void close() {
try {
if (statement != null) statement.close();
if (connection != null) connection.close();
} catch (SQLException e) {
logger.log(Level.WARNING, "Failed to close resources", e);
}
}
}
133 changes: 133 additions & 0 deletions src/net/sf/freecol/common/io/FreeColSQLWriter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* Copyright (C) 2002-2022 The FreeCol Team
*
* This file is part of FreeCol.
*
* FreeCol is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* FreeCol is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with FreeCol. If not, see <http://www.gnu.org/licenses/>.
*/

package net.sf.freecol.common.io;

import java.io.Closeable;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
* `FreeColSQLWriter` class to save game data into an SQLite database.
* It consists of the `saveGameData` method that saves general game data,
* player data, turn data, and tile data. Additional game components can be saved
* in a similar manner using prepared statements. The `close()`
* method ensures that the database connection is closed when done.
*/
public class FreeColSQLWriter implements Closeable {

private static final Logger logger = Logger.getLogger(FreeColSQLWriter.class.getName());

/**
* Enum representing the scope of the data writing.
*/
public enum WriteScopeType {
CLIENT, // Only the client-visible information
SERVER, // Full server-visible information
SAVE // Absolutely everything needed to save the game state
};

private Connection connection;
private WriteScopeType writeScope;

public FreeColSQLWriter(String dbPath, WriteScopeType scope) throws SQLException {
this.connection = DriverManager.getConnection("jdbc:sqlite:" + dbPath);
this.writeScope = (scope == null) ? WriteScopeType.SAVE : scope;
}

/**
* Executes an SQL update statement.
*
* @param update SQL update statement to execute.
* @throws SQLException If the statement execution fails.
*/
public void executeUpdate(String update) throws SQLException {
try (PreparedStatement pstmt = connection.prepareStatement(update)) {
pstmt.executeUpdate();
}
}

/**
* Save game data to the SQLite database.
*
* @param game The Game object to save.
* @throws SQLException If any error occurs during saving.
*/
public void saveGameData(Game game) throws SQLException {
// Save general game data
String sqlGame = "INSERT OR REPLACE INTO game (id, options) VALUES(?, ?)";
try (PreparedStatement pstmt = connection.prepareStatement(sqlGame)) {
pstmt.setInt(1, game.getId());
pstmt.setString(2, game.getGameOptions().toString());
pstmt.executeUpdate();
}

// Save player data
String sqlPlayer = "INSERT OR REPLACE INTO player (id, name, nation) VALUES(?, ?, ?)";
for (Player player : game.getPlayers()) {
try (PreparedStatement pstmt = connection.prepareStatement(sqlPlayer)) {
pstmt.setInt(1, player.getId());
pstmt.setString(2, player.getName());
pstmt.setString(3, player.getNation().getName());
pstmt.executeUpdate();
}
}

// Save turn data
String sqlTurn = "UPDATE game SET turn = ? WHERE id
// Save turn data
String sqlTurn = "UPDATE game SET turn = ? WHERE id = ?";
try (PreparedStatement pstmt = connection.prepareStatement(sqlTurn)) {
pstmt.setInt(1, game.getTurn());
pstmt.setInt(2, game.getId());
pstmt.executeUpdate();
}

// Save tile data
String sqlTile = "INSERT OR REPLACE INTO tile (x, y, terrain_type, owner, game_id) VALUES(?, ?, ?, ?, ?)";
for (Tile tile : game.getMap().getAllTiles()) {
try (PreparedStatement pstmt = connection.prepareStatement(sqlTile)) {
pstmt.setInt(1, tile.getX());
pstmt.setInt(2, tile.getY());
pstmt.setString(3, tile.getTerrainType().getName());
pstmt.setString(4, tile.getOwner().getName());
pstmt.setInt(5, game.getId());
pstmt.executeUpdate();
}
}

// Additional game components can be saved similarly with prepared statements
}

@Override
public void close() {
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
logger.log(Level.WARNING, "Failed to close resources", e);
}
}
}
1 change: 1 addition & 0 deletions src/net/sf/freecol/common/io/FreeColXMLWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ public void writeAttribute(String attributeName, Enum<?> value)
*/
public void writeAttribute(String attributeName, Object value)
throws XMLStreamException {
logger.log(Level.WARNING, "Writing attribute:" + attributeName + ", with value of: " + String.valueOf(value));
xmlStreamWriter.writeAttribute(attributeName, String.valueOf(value));
}

Expand Down
84 changes: 84 additions & 0 deletions src/net/sf/freecol/common/model/sql/generate_sql_tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
-- Create game table
CREATE TABLE IF NOT EXISTS game (
id INTEGER PRIMARY KEY,
difficulty TEXT,
mapFile TEXT,
gameOptions TEXT,
language TEXT,
turns INTEGER,
graphicalOptions TEXT,
soundOptions TEXT,
turn INTEGER
);

-- Create player table
CREATE TABLE IF NOT EXISTS player (
id INTEGER PRIMARY KEY,
game_id INTEGER,
name TEXT,
color TEXT,
flag TEXT,
playerPreferences TEXT,
FOREIGN KEY (game_id) REFERENCES game (id) ON DELETE CASCADE
);

-- Create native table
CREATE TABLE IF NOT EXISTS natives (
id INTEGER PRIMARY KEY,
game_id INTEGER,
name TEXT,
color TEXT,
type TEXT,
FOREIGN KEY (game_id) REFERENCES game (id) ON DELETE CASCADE
);

-- Create faction table
CREATE TABLE IF NOT EXISTS factions (
id INTEGER PRIMARY KEY,
game_id INTEGER,
name TEXT,
FOREIGN KEY (game_id) REFERENCES game (id) ON DELETE CASCADE
);

-- ... The existing unit, city, and tile table creation statements remain unchanged

-- Create building table
CREATE TABLE IF NOT EXISTS building (
id INTEGER PRIMARY KEY,
game_id INTEGER,
city_id INTEGER,
type TEXT,
x INTEGER,
y INTEGER,
FOREIGN KEY (game_id) REFERENCES game (id) ON DELETE CASCADE,
FOREIGN KEY (city_id) REFERENCES city (id) ON DELETE CASCADE
);

-- Create resource (goods) table
CREATE TABLE IF NOT EXISTS goods (
id INTEGER PRIMARY KEY,
game_id INTEGER,
type TEXT,
available INTEGER,
FOREIGN KEY (game_id) REFERENCES game (id) ON DELETE CASCADE
);

-- Create highscore table
CREATE TABLE IF NOT EXISTS highScores (
id INTEGER PRIMARY KEY,
game_id INTEGER,
player_id INTEGER,
score INTEGER,
FOREIGN KEY (game_id) REFERENCES game (id) ON DELETE CASCADE,
FOREIGN KEY (player_id) REFERENCES player (id) ON DELETE CASCADE
);

-- Create foundingFathers table
CREATE TABLE IF NOT EXISTS foundingFathers (
id INTEGER PRIMARY KEY,
game_id INTEGER,
name TEXT,
bonus TEXT,
improvement TEXT,
FOREIGN KEY (game_id) REFERENCES game (id) ON DELETE CASCADE
);
15 changes: 8 additions & 7 deletions src/net/sf/freecol/tools/FSGConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ private void convertToXML(InputStream ins, OutputStream outs)
return;
}
in.reset();
if (!"<?xml".equals(new String(buf, StandardCharsets.UTF_8))) {
in = new BufferedInputStream(new GZIPInputStream(ins));
}
// if (!"<?xml".equals(new String(buf, StandardCharsets.UTF_8))) {
// in = new BufferedInputStream(new GZIPInputStream(ins));
// }

// Support for XML comments has not been added:
int indent = 0;
Expand Down Expand Up @@ -183,15 +183,16 @@ private static void printUsage() {
* @param args The command-line parameters.
*/
public static void main(String[] args) {
if (args.length >= 2 && args[0].endsWith("output:xml")) {
File in = new File(args[1]);
String[] actual_args = {"FSG_FILE:xml", "5c812fa4_Голландцы_1494.fsg"};
if (actual_args.length >= 2 && actual_args[0].endsWith("output:xml")) {
File in = new File(actual_args[1]);
if (!in.exists()) {
printUsage();
System.exit(1);
}
File out;
if (args.length >= 3) {
out = new File(args[2]);
if (actual_args.length >= 3) {
out = new File(actual_args[2]);
} else {
String name = in.getName();
String filename = name.replaceAll("." + FreeCol.FREECOL_SAVE_EXTENSION, ".xml");
Expand Down
Loading