mirror of https://github.com/iSoron/uhabits.git
parent
feb3c98459
commit
a02c7bdc44
@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.core.database;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
|
||||
public interface Database
|
||||
{
|
||||
Cursor query(String query, String... params);
|
||||
|
||||
default void query(String query, ProcessCallback callback)
|
||||
{
|
||||
try (Cursor c = query(query)) {
|
||||
c.moveToNext();
|
||||
callback.process(c);
|
||||
}
|
||||
}
|
||||
|
||||
int update(String tableName,
|
||||
Map<String, Object> values,
|
||||
String where,
|
||||
String... params);
|
||||
|
||||
Long insert(String tableName, Map<String, Object> values);
|
||||
|
||||
void delete(String tableName, String where, String... params);
|
||||
|
||||
void execute(String query, Object... params);
|
||||
|
||||
void beginTransaction();
|
||||
|
||||
void setTransactionSuccessful();
|
||||
|
||||
void endTransaction();
|
||||
|
||||
void close();
|
||||
|
||||
int getVersion();
|
||||
|
||||
File getFile();
|
||||
|
||||
interface ProcessCallback
|
||||
{
|
||||
void process(Cursor cursor);
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.core.database
|
||||
|
||||
import java.io.*
|
||||
|
||||
interface Database {
|
||||
|
||||
fun query(q: String, vararg params: String): Cursor
|
||||
|
||||
fun query(q: String, callback: ProcessCallback) {
|
||||
query(q).use { c ->
|
||||
c.moveToNext()
|
||||
callback.process(c)
|
||||
}
|
||||
}
|
||||
|
||||
fun update(
|
||||
tableName: String,
|
||||
values: Map<String, Any?>,
|
||||
where: String,
|
||||
vararg params: String,
|
||||
): Int
|
||||
|
||||
fun insert(tableName: String, values: Map<String, Any?>): Long?
|
||||
|
||||
fun delete(tableName: String, where: String, vararg params: String)
|
||||
|
||||
fun execute(query: String, vararg params: Any)
|
||||
|
||||
fun beginTransaction()
|
||||
|
||||
fun setTransactionSuccessful()
|
||||
|
||||
fun endTransaction()
|
||||
|
||||
fun close()
|
||||
|
||||
val version: Int
|
||||
|
||||
val file: File?
|
||||
|
||||
interface ProcessCallback {
|
||||
fun process(cursor: Cursor)
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.core.database;
|
||||
|
||||
import java.sql.*;
|
||||
|
||||
public class JdbcCursor implements Cursor
|
||||
{
|
||||
private ResultSet resultSet;
|
||||
|
||||
public JdbcCursor(ResultSet resultSet)
|
||||
{
|
||||
this.resultSet = resultSet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
resultSet.close();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean moveToNext()
|
||||
{
|
||||
try
|
||||
{
|
||||
return resultSet.next();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getInt(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
Integer value = resultSet.getInt(index + 1);
|
||||
if(resultSet.wasNull()) return null;
|
||||
else return value;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getLong(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
Long value = resultSet.getLong(index + 1);
|
||||
if(resultSet.wasNull()) return null;
|
||||
else return value;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double getDouble(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
Double value = resultSet.getDouble(index + 1);
|
||||
if(resultSet.wasNull()) return null;
|
||||
else return value;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getString(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
String value = resultSet.getString(index + 1);
|
||||
if(resultSet.wasNull()) return null;
|
||||
else return value;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*/
|
||||
package org.isoron.uhabits.core.database
|
||||
|
||||
import java.sql.*
|
||||
|
||||
class JdbcCursor(private val resultSet: ResultSet) : Cursor {
|
||||
override fun close() {
|
||||
try {
|
||||
resultSet.close()
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun moveToNext(): Boolean {
|
||||
return try {
|
||||
resultSet.next()
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getInt(index: Int): Int? {
|
||||
return try {
|
||||
val value = resultSet.getInt(index + 1)
|
||||
if (resultSet.wasNull()) null else value
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLong(index: Int): Long? {
|
||||
return try {
|
||||
val value = resultSet.getLong(index + 1)
|
||||
if (resultSet.wasNull()) null else value
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDouble(index: Int): Double? {
|
||||
return try {
|
||||
val value = resultSet.getDouble(index + 1)
|
||||
if (resultSet.wasNull()) null else value
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getString(index: Int): String? {
|
||||
return try {
|
||||
val value = resultSet.getString(index + 1)
|
||||
if (resultSet.wasNull()) null else value
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,213 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.core.database;
|
||||
|
||||
import org.apache.commons.lang3.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.sql.*;
|
||||
import java.util.*;
|
||||
|
||||
public class JdbcDatabase implements Database
|
||||
{
|
||||
private Connection connection;
|
||||
|
||||
private boolean transactionSuccessful;
|
||||
|
||||
public JdbcDatabase(Connection connection)
|
||||
{
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(String query, String... params)
|
||||
{
|
||||
try
|
||||
{
|
||||
PreparedStatement st = buildStatement(query, params);
|
||||
return new JdbcCursor(st.executeQuery());
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(String tableName,
|
||||
Map<String, Object> map,
|
||||
String where,
|
||||
String... params)
|
||||
{
|
||||
try
|
||||
{
|
||||
ArrayList<String> fields = new ArrayList<>();
|
||||
ArrayList<String> values = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : map.entrySet())
|
||||
{
|
||||
if (entry.getValue() == null) continue;
|
||||
fields.add(entry.getKey() + "=?");
|
||||
values.add(entry.getValue().toString());
|
||||
}
|
||||
values.addAll(Arrays.asList(params));
|
||||
|
||||
String query = String.format("update %s set %s where %s", tableName,
|
||||
StringUtils.join(fields, ", "), where);
|
||||
|
||||
PreparedStatement st = buildStatement(query, values.toArray());
|
||||
return st.executeUpdate();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long insert(String tableName, Map<String, Object> map)
|
||||
{
|
||||
try
|
||||
{
|
||||
ArrayList<String> fields = new ArrayList<>();
|
||||
ArrayList<Object> params = new ArrayList<>();
|
||||
ArrayList<String> questionMarks = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Object> entry : map.entrySet())
|
||||
{
|
||||
if (entry.getValue() == null) continue;
|
||||
fields.add(entry.getKey());
|
||||
params.add(entry.getValue());
|
||||
questionMarks.add("?");
|
||||
}
|
||||
|
||||
String query =
|
||||
String.format("insert into %s(%s) values(%s)", tableName,
|
||||
StringUtils.join(fields, ", "),
|
||||
StringUtils.join(questionMarks, ", "));
|
||||
|
||||
PreparedStatement st = buildStatement(query, params.toArray());
|
||||
st.execute();
|
||||
|
||||
Long id = null;
|
||||
ResultSet keys = st.getGeneratedKeys();
|
||||
if (keys.next()) id = keys.getLong(1);
|
||||
return id;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String tableName, String where, String... params)
|
||||
{
|
||||
String query =
|
||||
String.format("delete from %s where %s", tableName, where);
|
||||
execute(query, (Object[]) params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(String query, Object... params)
|
||||
{
|
||||
try
|
||||
{
|
||||
buildStatement(query, params).execute();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private PreparedStatement buildStatement(String query, Object[] params)
|
||||
throws SQLException
|
||||
{
|
||||
PreparedStatement st = connection.prepareStatement(query);
|
||||
int index = 1;
|
||||
for (Object param : params) st.setString(index++, param.toString());
|
||||
return st;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void beginTransaction()
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.setAutoCommit(false);
|
||||
transactionSuccessful = false;
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setTransactionSuccessful()
|
||||
{
|
||||
transactionSuccessful = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void endTransaction()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (transactionSuccessful) connection.commit();
|
||||
else connection.rollback();
|
||||
connection.setAutoCommit(true);
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close()
|
||||
{
|
||||
try
|
||||
{
|
||||
connection.close();
|
||||
}
|
||||
catch (SQLException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getVersion()
|
||||
{
|
||||
try (Cursor c = query("PRAGMA user_version"))
|
||||
{
|
||||
c.moveToNext();
|
||||
return c.getInt(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getFile()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.core.database
|
||||
|
||||
import org.apache.commons.lang3.*
|
||||
import java.io.*
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.sql.*
|
||||
import java.util.*
|
||||
|
||||
class JdbcDatabase(private val connection: Connection) : Database {
|
||||
private var transactionSuccessful = false
|
||||
override fun query(q: String, vararg params: String): Cursor {
|
||||
return try {
|
||||
val st = buildStatement(q, params)
|
||||
JdbcCursor(st.executeQuery())
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun update(
|
||||
tableName: String,
|
||||
values: Map<String, Any?>,
|
||||
where: String,
|
||||
vararg params: String,
|
||||
): Int {
|
||||
return try {
|
||||
val fields = ArrayList<String?>()
|
||||
val valuesStr = ArrayList<String>()
|
||||
for ((key, value) in values) {
|
||||
fields.add("$key=?")
|
||||
valuesStr.add(value.toString())
|
||||
}
|
||||
valuesStr.addAll(listOf(*params))
|
||||
val query = String.format("update %s set %s where %s", tableName,
|
||||
StringUtils.join(fields, ", "), where)
|
||||
val st = buildStatement(query, valuesStr.toTypedArray())
|
||||
st.executeUpdate()
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun insert(tableName: String, values: Map<String, Any?>): Long? {
|
||||
return try {
|
||||
val fields = ArrayList<String?>()
|
||||
val params = ArrayList<Any?>()
|
||||
val questionMarks = ArrayList<String?>()
|
||||
for ((key, value) in values) {
|
||||
fields.add(key)
|
||||
params.add(value)
|
||||
questionMarks.add("?")
|
||||
}
|
||||
val query = String.format("insert into %s(%s) values(%s)", tableName,
|
||||
StringUtils.join(fields, ", "),
|
||||
StringUtils.join(questionMarks, ", "))
|
||||
val st = buildStatement(query, params.toTypedArray())
|
||||
st.execute()
|
||||
var id: Long? = null
|
||||
val keys = st.generatedKeys
|
||||
if (keys.next()) id = keys.getLong(1)
|
||||
id
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun delete(tableName: String, where: String, vararg params: String) {
|
||||
val query = String.format("delete from %s where %s", tableName, where)
|
||||
execute(query, *params)
|
||||
}
|
||||
|
||||
override fun execute(query: String, vararg params: Any) {
|
||||
try {
|
||||
buildStatement(query, params).execute()
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildStatement(query: String, params: Array<out Any?>): PreparedStatement {
|
||||
val st = connection.prepareStatement(query)
|
||||
var index = 1
|
||||
for (param in params) {
|
||||
when (param) {
|
||||
null -> st.setNull(index++, Types.INTEGER)
|
||||
is Int -> st.setInt(index++, param)
|
||||
is Double -> st.setDouble(index++, param)
|
||||
is String -> st.setString(index++, param)
|
||||
is Long -> st.setLong(index++, param)
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
return st
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun beginTransaction() {
|
||||
try {
|
||||
connection.autoCommit = false
|
||||
transactionSuccessful = false
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun setTransactionSuccessful() {
|
||||
transactionSuccessful = true
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun endTransaction() {
|
||||
try {
|
||||
if (transactionSuccessful) connection.commit() else connection.rollback()
|
||||
connection.autoCommit = true
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
try {
|
||||
connection.close()
|
||||
} catch (e: SQLException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
override val version: Int
|
||||
get() {
|
||||
query("PRAGMA user_version").use { c ->
|
||||
c.moveToNext()
|
||||
return c.getInt(0)!!
|
||||
}
|
||||
}
|
||||
|
||||
override val file: File?
|
||||
get() = null
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.core.database;
|
||||
|
||||
import androidx.annotation.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.*;
|
||||
import java.util.logging.*;
|
||||
|
||||
public class MigrationHelper
|
||||
{
|
||||
private static final Logger LOGGER =
|
||||
Logger.getLogger(MigrationHelper.class.getName());
|
||||
|
||||
private final Database db;
|
||||
|
||||
public MigrationHelper(@NonNull Database db)
|
||||
{
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public void migrateTo(int newVersion)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int v = db.getVersion() + 1; v <= newVersion; v++)
|
||||
{
|
||||
String fname = String.format(Locale.US, "/migrations/%02d.sql", v);
|
||||
for (String command : SQLParser.parse(open(fname)))
|
||||
db.execute(command);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private InputStream open(String fname) throws IOException
|
||||
{
|
||||
InputStream resource = getClass().getResourceAsStream(fname);
|
||||
if(resource != null) return resource;
|
||||
|
||||
// Workaround for bug in Android Studio / IntelliJ. Removing this
|
||||
// causes unit tests to fail when run from within the IDE, although
|
||||
// everything works fine from the command line.
|
||||
File file = new File("uhabits-core/src/main/resources/" + fname);
|
||||
if(file.exists()) return new FileInputStream(file);
|
||||
|
||||
throw new RuntimeException("resource not found: " + fname);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.core.database
|
||||
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
|
||||
class MigrationHelper(
|
||||
private val db: Database,
|
||||
) {
|
||||
fun migrateTo(newVersion: Int) {
|
||||
try {
|
||||
for (v in db.version + 1..newVersion) {
|
||||
val fname = String.format(Locale.US, "/migrations/%02d.sql", v)
|
||||
for (command in SQLParser.parse(open(fname))) db.execute(command)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun open(fname: String): InputStream {
|
||||
val resource = javaClass.getResourceAsStream(fname)
|
||||
if (resource != null) return resource
|
||||
|
||||
// Workaround for bug in Android Studio / IntelliJ. Removing this
|
||||
// causes unit tests to fail when run from within the IDE, although
|
||||
// everything works fine from the command line.
|
||||
val file = File("uhabits-core/src/main/resources/$fname")
|
||||
if (file.exists()) return FileInputStream(file)
|
||||
throw RuntimeException("resource not found: $fname")
|
||||
}
|
||||
}
|
@ -1,355 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.isoron.uhabits.core.database;
|
||||
|
||||
import androidx.annotation.*;
|
||||
|
||||
import org.apache.commons.lang3.*;
|
||||
import org.apache.commons.lang3.tuple.*;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
|
||||
public class Repository<T>
|
||||
{
|
||||
@NonNull
|
||||
private final Class klass;
|
||||
|
||||
@NonNull
|
||||
private final Database db;
|
||||
|
||||
public Repository(@NonNull Class<T> klass, @NonNull Database db)
|
||||
{
|
||||
this.klass = klass;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the record that has the id provided.
|
||||
* If no record is found, returns null.
|
||||
*/
|
||||
@Nullable
|
||||
public T find(@NonNull Long id)
|
||||
{
|
||||
return findFirst(String.format("where %s=?", getIdName()),
|
||||
id.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all records matching the given SQL query.
|
||||
* <p>
|
||||
* The query should only contain the "where" part of the SQL query, and
|
||||
* optionally the "order by" part. "Group by" is not allowed. If no matching
|
||||
* records are found, returns an empty list.
|
||||
*/
|
||||
@NonNull
|
||||
public List<T> findAll(@NonNull String query, @NonNull String... params)
|
||||
{
|
||||
try (Cursor c = db.query(buildSelectQuery() + query, params))
|
||||
{
|
||||
return cursorToMultipleRecords(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first record matching the given SQL query.
|
||||
* See findAll for more details about the parameters.
|
||||
*/
|
||||
@Nullable
|
||||
public T findFirst(String query, String... params)
|
||||
{
|
||||
try (Cursor c = db.query(buildSelectQuery() + query, params))
|
||||
{
|
||||
if (!c.moveToNext()) return null;
|
||||
return cursorToSingleRecord(c);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given SQL query on the repository.
|
||||
* <p>
|
||||
* The query can be of any kind. For example, complex deletes and updates
|
||||
* are allowed. The repository does not perform any checks to guarantee
|
||||
* that the query is valid, however the underlying database might.
|
||||
*/
|
||||
public void execSQL(String query, Object... params)
|
||||
{
|
||||
db.execute(query, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given callback inside a database transaction.
|
||||
* <p>
|
||||
* If the callback terminates without throwing any exceptions, the
|
||||
* transaction is considered successful. If any exceptions are thrown,
|
||||
* the transaction is aborted. Nesting transactions is not allowed.
|
||||
*/
|
||||
public void executeAsTransaction(Runnable callback)
|
||||
{
|
||||
db.beginTransaction();
|
||||
try
|
||||
{
|
||||
callback.run();
|
||||
db.setTransactionSuccessful();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
db.endTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the record on the database.
|
||||
* <p>
|
||||
* If the id of the given record is null, it is assumed that the record has
|
||||
* not been inserted in the repository yet. The record will be inserted, a
|
||||
* new id will be automatically generated, and the id of the given record
|
||||
* will be updated.
|
||||
* <p>
|
||||
* If the given record has a non-null id, then an update will be performed
|
||||
* instead. That is, the previous record will be overwritten by the one
|
||||
* provided.
|
||||
*/
|
||||
public void save(T record)
|
||||
{
|
||||
try
|
||||
{
|
||||
Field fields[] = getFields();
|
||||
String columns[] = getColumnNames();
|
||||
|
||||
Map<String, Object> values = new HashMap<>();
|
||||
for (int i = 0; i < fields.length; i++)
|
||||
values.put(columns[i], fields[i].get(record));
|
||||
|
||||
Long id = (Long) getIdField().get(record);
|
||||
int affectedRows = 0;
|
||||
|
||||
if (id != null) affectedRows =
|
||||
db.update(getTableName(), values, getIdName() + "=?",
|
||||
id.toString());
|
||||
|
||||
if (id == null || affectedRows == 0)
|
||||
{
|
||||
id = db.insert(getTableName(), values);
|
||||
getIdField().set(record, id);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given record from the repository.
|
||||
* The id of the given record is also set to null.
|
||||
*/
|
||||
public void remove(T record)
|
||||
{
|
||||
try
|
||||
{
|
||||
Long id = (Long) getIdField().get(record);
|
||||
if (id == null) return;
|
||||
|
||||
db.delete(getTableName(), getIdName() + "=?", id.toString());
|
||||
getIdField().set(record, null);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<T> cursorToMultipleRecords(Cursor c)
|
||||
{
|
||||
List<T> records = new LinkedList<>();
|
||||
while (c.moveToNext()) records.add(cursorToSingleRecord(c));
|
||||
return records;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private T cursorToSingleRecord(Cursor cursor)
|
||||
{
|
||||
try
|
||||
{
|
||||
Constructor constructor = klass.getDeclaredConstructors()[0];
|
||||
constructor.setAccessible(true);
|
||||
T record = (T) constructor.newInstance();
|
||||
|
||||
int index = 0;
|
||||
for (Field field : getFields())
|
||||
copyFieldFromCursor(record, field, cursor, index++);
|
||||
return record;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyFieldFromCursor(T record, Field field, Cursor c, int index)
|
||||
throws IllegalAccessException
|
||||
{
|
||||
if (field.getType().isAssignableFrom(Integer.class))
|
||||
field.set(record, c.getInt(index));
|
||||
else if (field.getType().isAssignableFrom(Long.class))
|
||||
field.set(record, c.getLong(index));
|
||||
else if (field.getType().isAssignableFrom(Double.class))
|
||||
field.set(record, c.getDouble(index));
|
||||
else if (field.getType().isAssignableFrom(String.class))
|
||||
field.set(record, c.getString(index));
|
||||
else throw new RuntimeException(
|
||||
"Type not supported: " + field.getType().getName() + " " +
|
||||
field.getName());
|
||||
}
|
||||
|
||||
private String buildSelectQuery()
|
||||
{
|
||||
return String.format("select %s from %s ",
|
||||
StringUtils.join(getColumnNames(), ", "), getTableName());
|
||||
}
|
||||
|
||||
private List<Pair<Field, Column>> getFieldColumnPairs()
|
||||
{
|
||||
List<Pair<Field, Column>> fields = new ArrayList<>();
|
||||
for (Field field : klass.getDeclaredFields())
|
||||
for (Annotation annotation : field.getAnnotations())
|
||||
{
|
||||
if (!(annotation instanceof Column)) continue;
|
||||
Column column = (Column) annotation;
|
||||
fields.add(new ImmutablePair<>(field, column));
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
private Field[] cacheFields = null;
|
||||
|
||||
@NonNull
|
||||
private Field[] getFields()
|
||||
{
|
||||
if (cacheFields == null)
|
||||
{
|
||||
List<Field> fields = new ArrayList<>();
|
||||
List<Pair<Field, Column>> columns = getFieldColumnPairs();
|
||||
for (Pair<Field, Column> pair : columns) fields.add(pair.getLeft());
|
||||
cacheFields = fields.toArray(new Field[]{});
|
||||
}
|
||||
|
||||
return cacheFields;
|
||||
}
|
||||
|
||||
private String[] cacheColumnNames = null;
|
||||
|
||||
@NonNull
|
||||
private String[] getColumnNames()
|
||||
{
|
||||
if (cacheColumnNames == null)
|
||||
{
|
||||
List<String> names = new ArrayList<>();
|
||||
List<Pair<Field, Column>> columns = getFieldColumnPairs();
|
||||
for (Pair<Field, Column> pair : columns)
|
||||
{
|
||||
String cname = pair.getRight().name();
|
||||
if (cname.isEmpty()) cname = pair.getLeft().getName();
|
||||
if (names.contains(cname))
|
||||
throw new RuntimeException("duplicated column : " + cname);
|
||||
names.add(cname);
|
||||
}
|
||||
|
||||
cacheColumnNames = names.toArray(new String[]{});
|
||||
}
|
||||
|
||||
return cacheColumnNames;
|
||||
}
|
||||
|
||||
private String cacheTableName = null;
|
||||
|
||||
@NonNull
|
||||
private String getTableName()
|
||||
{
|
||||
if (cacheTableName == null)
|
||||
{
|
||||
String name = getTableAnnotation().name();
|
||||
if (name.isEmpty()) throw new RuntimeException("Table name is empty");
|
||||
cacheTableName = name;
|
||||
}
|
||||
return cacheTableName;
|
||||
}
|
||||
|
||||
private String cacheIdName = null;
|
||||
|
||||
@NonNull
|
||||
private String getIdName()
|
||||
{
|
||||
if (cacheIdName == null)
|
||||
{
|
||||
String id = getTableAnnotation().id();
|
||||
if (id.isEmpty()) throw new RuntimeException("Table id is empty");
|
||||
cacheIdName = id;
|
||||
}
|
||||
|
||||
return cacheIdName;
|
||||
}
|
||||
|
||||
private Field cacheIdField = null;
|
||||
|
||||
@NonNull
|
||||
private Field getIdField()
|
||||
{
|
||||
if (cacheIdField == null)
|
||||
{
|
||||
Field fields[] = getFields();
|
||||
String idName = getIdName();
|
||||
for (Field f : fields)
|
||||
if (f.getName().equals(idName))
|
||||
{
|
||||
cacheIdField = f;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cacheIdField == null)
|
||||
throw new RuntimeException("Field not found: " + idName);
|
||||
}
|
||||
|
||||
return cacheIdField;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private Table getTableAnnotation()
|
||||
{
|
||||
Table t = null;
|
||||
for (Annotation annotation : klass.getAnnotations())
|
||||
{
|
||||
if (!(annotation instanceof Table)) continue;
|
||||
t = (Table) annotation;
|
||||
break;
|
||||
}
|
||||
|
||||
if (t == null) throw new RuntimeException("Table annotation not found");
|
||||
return t;
|
||||
}
|
||||
}
|
@ -0,0 +1,252 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Álinson Santos Xavier <isoron@gmail.com>
|
||||
*
|
||||
* This file is part of Loop Habit Tracker.
|
||||
*
|
||||
* Loop Habit Tracker 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 3 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
* Loop Habit Tracker 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.isoron.uhabits.core.database
|
||||
|
||||
import org.apache.commons.lang3.*
|
||||
import org.apache.commons.lang3.tuple.*
|
||||
import java.lang.reflect.*
|
||||
import java.util.*
|
||||
|
||||
class Repository<T>(
|
||||
private val klass: Class<T>,
|
||||
private val db: Database,
|
||||
) {
|
||||
/**
|
||||
* Returns the record that has the id provided. If no record is found, returns null.
|
||||
*/
|
||||
fun find(id: Long): T? {
|
||||
return findFirst(String.format("where %s=?", getIdName()), id.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all records matching the given SQL query.
|
||||
*
|
||||
* The query should only contain the "where" part of the SQL query, and optionally the "order
|
||||
* by" part. "Group by" is not allowed. If no matching records are found, returns an empty list.
|
||||
*/
|
||||
fun findAll(query: String, vararg params: String): List<T> {
|
||||
db.query(buildSelectQuery() + query, *params).use { c -> return cursorToMultipleRecords(c) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the first record matching the given SQL query. See findAll for more details about
|
||||
* the parameters.
|
||||
*/
|
||||
fun findFirst(query: String, vararg params: String): T? {
|
||||
db.query(buildSelectQuery() + query, *params).use { c ->
|
||||
return if (!c.moveToNext()) null else cursorToSingleRecord(c)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given SQL query on the repository.
|
||||
*
|
||||
* The query can be of any kind. For example, complex deletes and updates are allowed. The
|
||||
* repository does not perform any checks to guarantee that the query is valid, however the
|
||||
* underlying database might.
|
||||
*/
|
||||
fun execSQL(query: String, vararg params: Any) {
|
||||
db.execute(query, *params)
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the given callback inside a database transaction.
|
||||
*
|
||||
* If the callback terminates without throwing any exceptions, the transaction is considered
|
||||
* successful. If any exceptions are thrown, the transaction is aborted. Nesting transactions
|
||||
* is not allowed.
|
||||
*/
|
||||
fun executeAsTransaction(callback: Runnable) {
|
||||
db.beginTransaction()
|
||||
try {
|
||||
callback.run()
|
||||
db.setTransactionSuccessful()
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
} finally {
|
||||
db.endTransaction()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the record on the database.
|
||||
*
|
||||
* If the id of the given record is null, it is assumed that the record has not been inserted
|
||||
* in the repository yet. The record will be inserted, a new id will be automatically generated,
|
||||
* and the id of the given record will be updated.
|
||||
*
|
||||
* If the given record has a non-null id, then an update will be performed instead. That is,
|
||||
* the previous record will be overwritten by the one provided.
|
||||
*/
|
||||
fun save(record: T) {
|
||||
try {
|
||||
val fields = getFields()
|
||||
val columns = getColumnNames()
|
||||
val values: MutableMap<String, Any?> = HashMap()
|
||||
for (i in fields.indices) values[columns[i]] = fields[i][record]
|
||||
var id = getIdField()[record] as Long?
|
||||
var affectedRows = 0
|
||||
if (id != null) {
|
||||
affectedRows = db.update(getTableName(), values, "${getIdName()}=?", id.toString())
|
||||
}
|
||||
if (id == null || affectedRows == 0) {
|
||||
id = db.insert(getTableName(), values)
|
||||
getIdField()[record] = id
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given record from the repository. The id of the given record is also set to null.
|
||||
*/
|
||||
fun remove(record: T) {
|
||||
try {
|
||||
val id = getIdField()[record] as Long?
|
||||
db.delete(getTableName(), "${getIdName()}=?", id.toString())
|
||||
getIdField()[record] = null
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cursorToMultipleRecords(c: Cursor): List<T> {
|
||||
val records: MutableList<T> = LinkedList()
|
||||
while (c.moveToNext()) records.add(cursorToSingleRecord(c))
|
||||
return records
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun cursorToSingleRecord(cursor: Cursor): T {
|
||||
return try {
|
||||
val constructor = klass.declaredConstructors[0]
|
||||
constructor.isAccessible = true
|
||||
val record = constructor.newInstance() as T
|
||||
var index = 0
|
||||
for (field in getFields()) copyFieldFromCursor(record, field, cursor, index++)
|
||||
record
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyFieldFromCursor(record: T, field: Field, c: Cursor, index: Int) {
|
||||
when {
|
||||
field.type.isAssignableFrom(java.lang.Integer::class.java) -> field[record] = c.getInt(index)
|
||||
field.type.isAssignableFrom(java.lang.Long::class.java) -> field[record] = c.getLong(index)
|
||||
field.type.isAssignableFrom(java.lang.Double::class.java) -> field[record] = c.getDouble(index)
|
||||
field.type.isAssignableFrom(java.lang.String::class.java) -> field[record] = c.getString(index)
|
||||
else -> throw RuntimeException("Type not supported: ${field.type.name} ${field.name}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSelectQuery(): String {
|
||||
return String.format("select %s from %s ", StringUtils.join(getColumnNames(), ", "), getTableName())
|
||||
}
|
||||
|
||||
private val fieldColumnPairs: List<Pair<Field, Column>>
|
||||
get() {
|
||||
val fields: MutableList<Pair<Field, Column>> = ArrayList()
|
||||
for (f in klass.declaredFields) {
|
||||
for (annotation in f.annotations) {
|
||||
if (annotation !is Column) continue
|
||||
fields.add(ImmutablePair(f, annotation))
|
||||
}
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
private var cacheFields: Array<Field>? = null
|
||||
|
||||
private fun getFields(): Array<Field> {
|
||||
if (cacheFields == null) {
|
||||
val fields: MutableList<Field> = ArrayList()
|
||||
val columns = fieldColumnPairs
|
||||
for (pair in columns) fields.add(pair.left)
|
||||
cacheFields = fields.toTypedArray()
|
||||
}
|
||||
return cacheFields!!
|
||||
}
|
||||
|
||||
private var cacheColumnNames: Array<String>? = null
|
||||
|
||||
private fun getColumnNames(): Array<String> {
|
||||
if (cacheColumnNames == null) {
|
||||
val names: MutableList<String> = ArrayList()
|
||||
val columns = fieldColumnPairs
|
||||
for (pair in columns) {
|
||||
var cname = pair.right.name
|
||||
if (cname.isEmpty()) cname = pair.left.name
|
||||
if (names.contains(cname)) throw RuntimeException("duplicated column : $cname")
|
||||
names.add(cname)
|
||||
}
|
||||
cacheColumnNames = names.toTypedArray()
|
||||
}
|
||||
return cacheColumnNames!!
|
||||
}
|
||||
|
||||
private var cacheTableName: String? = null
|
||||
|
||||
private fun getTableName(): String {
|
||||
if (cacheTableName == null) {
|
||||
val name = getTableAnnotation().name
|
||||
if (name.isEmpty()) throw RuntimeException("Table name is empty")
|
||||
cacheTableName = name
|
||||
}
|
||||
return cacheTableName!!
|
||||
}
|
||||
|
||||
private var cacheIdName: String? = null
|
||||
|
||||
private fun getIdName(): String {
|
||||
if (cacheIdName == null) {
|
||||
val id = getTableAnnotation().id
|
||||
if (id.isEmpty()) throw RuntimeException("Table id is empty")
|
||||
cacheIdName = id
|
||||
}
|
||||
return cacheIdName!!
|
||||
}
|
||||
|
||||
private var cacheIdField: Field? = null
|
||||
|
||||
private fun getIdField(): Field {
|
||||
if (cacheIdField == null) {
|
||||
val fields = getFields()
|
||||
val idName = getIdName()
|
||||
for (f in fields) if (f.name == idName) {
|
||||
cacheIdField = f
|
||||
break
|
||||
}
|
||||
if (cacheIdField == null) throw RuntimeException("Field not found: $idName")
|
||||
}
|
||||
return cacheIdField!!
|
||||
}
|
||||
|
||||
private fun getTableAnnotation(): Table {
|
||||
var t: Table? = null
|
||||
for (annotation in klass.annotations) {
|
||||
if (annotation !is Table) continue
|
||||
t = annotation
|
||||
break
|
||||
}
|
||||
if (t == null) throw RuntimeException("Table annotation not found")
|
||||
return t
|
||||
}
|
||||
}
|
@ -1,163 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Markus Pfeiffer
|
||||
*
|
||||
* 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 org.isoron.uhabits.core.database;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class Tokenizer {
|
||||
|
||||
private final InputStream mStream;
|
||||
|
||||
private boolean mIsNext;
|
||||
private int mCurrent;
|
||||
|
||||
public Tokenizer(final InputStream in) {
|
||||
this.mStream = in;
|
||||
}
|
||||
|
||||
public boolean hasNext() throws IOException {
|
||||
|
||||
if (!this.mIsNext) {
|
||||
this.mIsNext = true;
|
||||
this.mCurrent = this.mStream.read();
|
||||
}
|
||||
return this.mCurrent != -1;
|
||||
}
|
||||
|
||||
public int next() throws IOException {
|
||||
|
||||
if (!this.mIsNext) {
|
||||
this.mCurrent = this.mStream.read();
|
||||
}
|
||||
this.mIsNext = false;
|
||||
return this.mCurrent;
|
||||
}
|
||||
|
||||
public boolean skip(final String s) throws IOException {
|
||||
|
||||
if (s == null || s.length() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (s.charAt(0) != this.mCurrent) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final int len = s.length();
|
||||
this.mStream.mark(len - 1);
|
||||
|
||||
for (int n = 1; n < len; n++) {
|
||||
final int value = this.mStream.read();
|
||||
|
||||
if (value != s.charAt(n)) {
|
||||
this.mStream.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class SQLParser {
|
||||
|
||||
public final static int STATE_NONE = 0;
|
||||
public final static int STATE_STRING = 1;
|
||||
public final static int STATE_COMMENT = 2;
|
||||
public final static int STATE_COMMENT_BLOCK = 3;
|
||||
|
||||
public static List<String> parse(final InputStream stream) throws IOException {
|
||||
|
||||
final BufferedInputStream buffer = new BufferedInputStream(stream);
|
||||
final List<String> commands = new ArrayList<String>();
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
|
||||
try {
|
||||
final Tokenizer tokenizer = new Tokenizer(buffer);
|
||||
int state = STATE_NONE;
|
||||
|
||||
while (tokenizer.hasNext()) {
|
||||
final char c = (char) tokenizer.next();
|
||||
|
||||
if (state == STATE_COMMENT_BLOCK) {
|
||||
if (tokenizer.skip("*/")) {
|
||||
state = STATE_NONE;
|
||||
}
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_COMMENT) {
|
||||
if (isNewLine(c)) {
|
||||
state = STATE_NONE;
|
||||
}
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && tokenizer.skip("/*")) {
|
||||
state = STATE_COMMENT_BLOCK;
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && tokenizer.skip("--")) {
|
||||
state = STATE_COMMENT;
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && c == ';') {
|
||||
final String command = sb.toString().trim();
|
||||
commands.add(command);
|
||||
sb.setLength(0);
|
||||
continue;
|
||||
|
||||
} else if (state == STATE_NONE && c == '\'') {
|
||||
state = STATE_STRING;
|
||||
|
||||
} else if (state == STATE_STRING && c == '\'') {
|
||||
state = STATE_NONE;
|
||||
|
||||
}
|
||||
|
||||
if (state == STATE_NONE || state == STATE_STRING) {
|
||||
if (state == STATE_NONE && isWhitespace(c)) {
|
||||
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
|
||||
sb.append(' ');
|
||||
}
|
||||
} else {
|
||||
sb.append(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} finally {
|
||||
buffer.close();
|
||||
}
|
||||
|
||||
if (sb.length() > 0) {
|
||||
commands.add(sb.toString().trim());
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
private static boolean isNewLine(final char c) {
|
||||
return c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
private static boolean isWhitespace(final char c) {
|
||||
return c == '\r' || c == '\n' || c == '\t' || c == ' ';
|
||||
}
|
||||
}
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright (C) 2014 Markus Pfeiffer
|
||||
*
|
||||
* 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 org.isoron.uhabits.core.database
|
||||
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
|
||||
internal class Tokenizer(
|
||||
private val mStream: InputStream,
|
||||
) {
|
||||
private var mIsNext = false
|
||||
private var mCurrent = 0
|
||||
|
||||
operator fun hasNext(): Boolean {
|
||||
if (!mIsNext) {
|
||||
mIsNext = true
|
||||
mCurrent = mStream.read()
|
||||
}
|
||||
return mCurrent != -1
|
||||
}
|
||||
|
||||
operator fun next(): Int {
|
||||
if (!mIsNext) {
|
||||
mCurrent = mStream.read()
|
||||
}
|
||||
mIsNext = false
|
||||
return mCurrent
|
||||
}
|
||||
|
||||
fun skip(s: String?): Boolean {
|
||||
if (s == null || s.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
if (s[0].toInt() != mCurrent) {
|
||||
return false
|
||||
}
|
||||
val len = s.length
|
||||
mStream.mark(len - 1)
|
||||
for (n in 1 until len) {
|
||||
val value = mStream.read()
|
||||
if (value != s[n].toInt()) {
|
||||
mStream.reset()
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
object SQLParser {
|
||||
private const val STATE_NONE = 0
|
||||
private const val STATE_STRING = 1
|
||||
private const val STATE_COMMENT = 2
|
||||
private const val STATE_COMMENT_BLOCK = 3
|
||||
|
||||
fun parse(stream: InputStream): List<String> {
|
||||
val buffer = BufferedInputStream(stream)
|
||||
val commands: MutableList<String> = ArrayList()
|
||||
val sb = StringBuffer()
|
||||
try {
|
||||
val tokenizer = Tokenizer(buffer)
|
||||
var state = STATE_NONE
|
||||
while (tokenizer.hasNext()) {
|
||||
val c = tokenizer.next().toChar()
|
||||
if (state == STATE_COMMENT_BLOCK) {
|
||||
if (tokenizer.skip("*/")) {
|
||||
state = STATE_NONE
|
||||
}
|
||||
continue
|
||||
} else if (state == STATE_COMMENT) {
|
||||
if (isNewLine(c)) {
|
||||
state = STATE_NONE
|
||||
}
|
||||
continue
|
||||
} else if (state == STATE_NONE && tokenizer.skip("/*")) {
|
||||
state = STATE_COMMENT_BLOCK
|
||||
continue
|
||||
} else if (state == STATE_NONE && tokenizer.skip("--")) {
|
||||
state = STATE_COMMENT
|
||||
continue
|
||||
} else if (state == STATE_NONE && c == ';') {
|
||||
val command = sb.toString().trim { it <= ' ' }
|
||||
commands.add(command)
|
||||
sb.setLength(0)
|
||||
continue
|
||||
} else if (state == STATE_NONE && c == '\'') {
|
||||
state = STATE_STRING
|
||||
} else if (state == STATE_STRING && c == '\'') {
|
||||
state = STATE_NONE
|
||||
}
|
||||
if (state == STATE_NONE || state == STATE_STRING) {
|
||||
if (state == STATE_NONE && isWhitespace(c)) {
|
||||
if (sb.length > 0 && sb[sb.length - 1] != ' ') {
|
||||
sb.append(' ')
|
||||
}
|
||||
} else {
|
||||
sb.append(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
buffer.close()
|
||||
}
|
||||
if (sb.isNotEmpty()) {
|
||||
commands.add(sb.toString().trim { it <= ' ' })
|
||||
}
|
||||
return commands
|
||||
}
|
||||
|
||||
private fun isNewLine(c: Char): Boolean {
|
||||
return c == '\r' || c == '\n'
|
||||
}
|
||||
|
||||
private fun isWhitespace(c: Char): Boolean {
|
||||
return c == '\r' || c == '\n' || c == '\t' || c == ' '
|
||||
}
|
||||
}
|
Loading…
Reference in new issue