Passed
Push — master ( 17cb22...b65cac )
by Vincent
03:39
created

executeQuery(String)   A

Complexity

Conditions 1

Size

Total Lines 3
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
/*
1 ignored issue
show
Code Smell introduced by
It is a best practice to supply a copyright/licence header in your code. Your organisation probably has a template for that.
Loading history...
2
 * This file is part of Swf Map Loader.
3
 *
4
 * Swf Map Loader is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU Lesser General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * Swf Map Loader is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public License
15
 * along with Swf Map Loader.  If not, see <https://www.gnu.org/licenses/>.
16
 *
17
 * Copyright (c) 2020-2020 Vincent Quatrevieux
18
 */
19
20
package fr.arakne.swfmaploader.cache;
21
22
import fr.arakne.swfmaploader.swf.SwfMapStructure;
23
24
import java.io.File;
25
import java.sql.Connection;
26
import java.sql.DriverManager;
27
import java.sql.PreparedStatement;
28
import java.sql.ResultSet;
29
import java.sql.SQLException;
30
import java.sql.Statement;
31
import java.util.Optional;
32
33
/**
34
 * SQL implementation for the cache system
35
 */
36
final public class SqlMapStructureCache implements MapStructureCache {
37
    final static public int SCHEMA_VERSION = 1;
38
39
    final private Connection connection;
40
41 1
    public SqlMapStructureCache(Connection connection) throws SQLException {
42 1
        this.connection = connection;
43
44 1
        initDatabase();
45 1
    }
46
47
    @Override
48
    public Optional<SwfMapStructure> retrieve(int id) {
49 1
        try (PreparedStatement stmt = connection.prepareStatement("SELECT * FROM `MAP_CACHE` WHERE `ID` = ?")) {
50 1
            stmt.setInt(1, id);
51
52 1
            try (ResultSet rs = stmt.executeQuery()) {
53 1
                if (!rs.next()) {
54 1
                    return Optional.empty();
55
                }
56
57 1
                SwfMapStructure structure = new SwfMapStructure();
58
59 1
                structure.setId(rs.getInt("ID"));
60 1
                structure.setWidth(rs.getInt("WIDTH"));
61 1
                structure.setHeight(rs.getInt("HEIGHT"));
62 1
                structure.setBackgroundNum(rs.getInt("BACKGROUND_NUM"));
63 1
                structure.setAmbianceId(rs.getInt("AMBIANCE_ID"));
64 1
                structure.setMusicId(rs.getInt("MUSIC_ID"));
65 1
                structure.setOutdoor(rs.getInt("OUTDOOR") == 1);
66 1
                structure.setCapabilities(rs.getInt("CAPABILITIES"));
67 1
                structure.setMapData(rs.getString("MAP_DATA"));
68 1
                structure.setVersion(rs.getString("VERSION"));
69
70 1
                return Optional.of(structure);
71
            }
72
        } catch (SQLException e) {
73
            throw new RuntimeException("Cannot read map " + id + " from cache", e);
0 ignored issues
show
Best Practice introduced by
Dedicated exceptions should be preferred over throwing the generic Exception.
Loading history...
74
        }
75
    }
76
77
    @Override
78
    public void store(SwfMapStructure structure) {
79 1
        try (PreparedStatement stmt = connection.prepareStatement("REPLACE INTO `MAP_CACHE` (`ID`, `WIDTH`, `HEIGHT`, `BACKGROUND_NUM`, `AMBIANCE_ID`, `MUSIC_ID`, `OUTDOOR`, `CAPABILITIES`, `MAP_DATA`, `VERSION`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")) {
80 1
            stmt.setInt(1, structure.id());
81 1
            stmt.setInt(2, structure.width());
0 ignored issues
show
Comprehensibility introduced by
Consider assigning this magic number 2 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
82 1
            stmt.setInt(3, structure.height());
0 ignored issues
show
Comprehensibility introduced by
Consider assigning this magic number 3 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
83 1
            stmt.setInt(4, structure.backgroundNum());
0 ignored issues
show
Comprehensibility introduced by
Consider assigning this magic number 4 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
84 1
            stmt.setInt(5, structure.ambianceId());
0 ignored issues
show
Comprehensibility introduced by
Consider assigning this magic number 5 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
85 1
            stmt.setInt(6, structure.musicId());
0 ignored issues
show
Comprehensibility introduced by
Consider assigning this magic number 6 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
86 1
            stmt.setInt(7, structure.isOutdoor() ? 1 : 0);
0 ignored issues
show
Comprehensibility introduced by
Consider assigning this magic number 7 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
87 1
            stmt.setInt(8, structure.capabilities());
0 ignored issues
show
Comprehensibility introduced by
Consider assigning this magic number 8 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
88 1
            stmt.setString(9, structure.mapData());
0 ignored issues
show
Comprehensibility introduced by
Consider assigning this magic number 9 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
89 1
            stmt.setString(10, structure.version());
0 ignored issues
show
Comprehensibility introduced by
Consider assigning this magic number 10 to a constant.

Using constants for hard-coded numbers is a best practice. A constant’s name can explain the rationale behind this magic number. It is also easier to find if you ever need to change it.

Loading history...
90
91 1
            stmt.executeUpdate();
92
        } catch (SQLException e) {
93
            throw new RuntimeException("Cannot store map " + structure.id() + " in cache", e);
0 ignored issues
show
Best Practice introduced by
Dedicated exceptions should be preferred over throwing the generic Exception.
Loading history...
94 1
        }
95 1
    }
96
97
    @Override
98
    public void clear() {
99
        try {
100 1
            executeQuery("DELETE FROM `MAP_CACHE`");
101
        } catch (SQLException e) {
102
            throw new RuntimeException("Cannot clear map cache", e);
0 ignored issues
show
Best Practice introduced by
Dedicated exceptions should be preferred over throwing the generic Exception.
Loading history...
103 1
        }
104 1
    }
105
106
    /**
107
     * Initialize the SQL database
108
     *
109
     * @throws SQLException
110
     */
111
    private void initDatabase() throws SQLException {
112 1
        boolean hasTable = hasTable("MAP_CACHE");
113 1
        boolean checkVersion = checkSchemaVersion();
114
115 1
        if (hasTable && !checkVersion) {
116 1
            executeQuery("DROP TABLE `MAP_CACHE`");
117 1
            hasTable = false;
118
        }
119
120 1
        if (!hasTable) {
121 1
            createCacheTable();
122
        }
123
124 1
        if (!checkVersion) {
125 1
            updateSchemaVersion();
126
        }
127 1
    }
128
129
    /**
130
     * Check if the schema version match
131
     */
132
    private boolean checkSchemaVersion() throws SQLException {
133 1
        if (!hasTable("SCHEMA_VERSION")) {
134 1
            executeQuery("CREATE TABLE `SCHEMA_VERSION` (`VERSION` INTEGER PRIMARY KEY)");
135
136 1
            return false;
137
        }
138
139 1
        try (PreparedStatement stmt = connection.prepareStatement("SELECT `VERSION` FROM `SCHEMA_VERSION` WHERE `VERSION` = ?")) {
140 1
            stmt.setInt(1, SCHEMA_VERSION);
141
142 1
            try (ResultSet rs = stmt.executeQuery()) {
143 1
                return rs.next();
144
            }
145
        }
146
    }
147
148
    /**
149
     * Check if a table exists
150
     *
151
     * @param tableName The table name
152
     *
153
     * @return true if exists
154
     */
155
    private boolean hasTable(String tableName) throws SQLException {
156 1
        try (PreparedStatement stmt = connection.prepareStatement("SELECT `name` FROM  `sqlite_master` WHERE `type` = 'table' AND `name` = ?")) {
157 1
            stmt.setString(1, tableName);
158
159 1
            try (ResultSet rs = stmt.executeQuery()) {
160 1
                return rs.next();
161
            }
162
        }
163
    }
164
165
    /**
166
     * Set the schema version
167
     */
168
    private void updateSchemaVersion() throws SQLException {
169 1
        try (PreparedStatement stmt = connection.prepareStatement("INSERT INTO `SCHEMA_VERSION` VALUES (?)")) {
170 1
            stmt.setInt(1, SCHEMA_VERSION);
171 1
            stmt.executeUpdate();
172
        }
173 1
    }
174
175
    /**
176
     * Create the table
177
     */
178
    private void createCacheTable() throws SQLException {
179 1
        executeQuery(
180
            "CREATE TABLE `MAP_CACHE` (" +
181
                "`ID` INTEGER PRIMARY KEY, " +
182
                "`VERSION` VARCHAR(32), " +
183
                "`WIDTH` INTEGER, " +
184
                "`HEIGHT` INTEGER, " +
185
                "`BACKGROUND_NUM` INTEGER, " +
186
                "`AMBIANCE_ID` INTEGER, " +
187
                "`MUSIC_ID` INTEGER, " +
188
                "`OUTDOOR` INTEGER, " +
189
                "`CAPABILITIES` INTEGER, " +
190
                "`MAP_DATA` BLOB" +
191
            ")"
192
        );
193 1
    }
194
195
    /**
196
     * Execute a simple SQL query
197
     */
198
    private void executeQuery(String query) throws SQLException {
199 1
        try (Statement stmt = connection.createStatement()) {
200 1
            stmt.execute(query);
201
        }
202 1
    }
203
204
    /**
205
     * Create a new cache instance for a SQLite file database
206
     *
207
     * @param filename The sqlite filename
208
     *
209
     * @return The cache instance
210
     */
211
    static public SqlMapStructureCache createBySqliteFile(String filename) throws SQLException {
212 1
        File file = new File(filename);
213
214 1
        if (!file.exists() && !file.getParentFile().exists()) {
215 1
            file.getParentFile().mkdirs();
216
        }
217
218 1
        return new SqlMapStructureCache(DriverManager.getConnection("jdbc:sqlite:" + filename));
219
    }
220
}
221