From a02c7bdc4401daa920a7dcf7954ed87d0d8cfae5 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 29 Dec 2020 20:48:02 -0600 Subject: [PATCH] Convert core.database classes to Kotlin --- .../uhabits/database/AndroidDatabase.kt | 45 ++- .../core/database/{Column.java => Column.kt} | 12 +- .../core/database/{Cursor.java => Cursor.kt} | 26 +- .../uhabits/core/database/Database.java | 64 ---- .../isoron/uhabits/core/database/Database.kt | 62 +++ ...{DatabaseOpener.java => DatabaseOpener.kt} | 14 +- .../uhabits/core/database/JdbcCursor.java | 120 ------ .../uhabits/core/database/JdbcCursor.kt | 77 ++++ .../uhabits/core/database/JdbcDatabase.java | 213 ----------- .../uhabits/core/database/JdbcDatabase.kt | 157 ++++++++ .../core/database/MigrationHelper.java | 71 ---- .../uhabits/core/database/MigrationHelper.kt | 49 +++ .../uhabits/core/database/Repository.java | 355 ------------------ .../uhabits/core/database/Repository.kt | 252 +++++++++++++ .../uhabits/core/database/SQLParser.java | 163 -------- .../isoron/uhabits/core/database/SQLParser.kt | 130 +++++++ .../core/database/{Table.java => Table.kt} | 15 +- ...=> UnsupportedDatabaseVersionException.kt} | 7 +- .../isoron/uhabits/core/sync/SyncManager.kt | 2 +- 19 files changed, 774 insertions(+), 1060 deletions(-) rename android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/{Column.java => Column.kt} (82%) rename android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/{Cursor.java => Cursor.kt} (83%) delete mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Database.java create mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Database.kt rename android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/{DatabaseOpener.java => DatabaseOpener.kt} (82%) delete mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcCursor.java create mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcCursor.kt delete mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcDatabase.java create mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcDatabase.kt delete mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/MigrationHelper.java create mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/MigrationHelper.kt delete mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Repository.java create mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Repository.kt delete mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/SQLParser.java create mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/SQLParser.kt rename android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/{Table.java => Table.kt} (78%) rename android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/{UnsupportedDatabaseVersionException.java => UnsupportedDatabaseVersionException.kt} (86%) diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidDatabase.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidDatabase.kt index 5e167eee3..803eeb423 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidDatabase.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/database/AndroidDatabase.kt @@ -26,41 +26,41 @@ import java.io.* class AndroidDatabase( private val db: SQLiteDatabase, - private val file: File?, + override val file: File?, ) : Database { override fun beginTransaction() = db.beginTransaction() override fun setTransactionSuccessful() = db.setTransactionSuccessful() override fun endTransaction() = db.endTransaction() override fun close() = db.close() - override fun getVersion() = db.version - override fun getFile(): File? { - return file - } + override val version: Int + get() = db.version - override fun query(query: String, vararg params: String) - = AndroidCursor(db.rawQuery(query, params)) + override fun query(q: String, vararg params: String) = AndroidCursor(db.rawQuery(q, params)) - override fun execute(query: String, vararg params: Any) - = db.execSQL(query, params) + override fun execute(query: String, vararg params: Any) = db.execSQL(query, params) - override fun update(tableName: String, - map: Map, - where: String, - vararg params: String): Int { - val values = mapToContentValues(map) - return db.update(tableName, values, where, params) + override fun update( + tableName: String, + values: Map, + where: String, + vararg params: String, + ): Int { + val contValues = mapToContentValues(values) + return db.update(tableName, contValues, where, params) } - override fun insert(tableName: String, map: Map): Long? { - val values = mapToContentValues(map) - return db.insert(tableName, null, values) + override fun insert(tableName: String, values: Map): Long? { + val contValues = mapToContentValues(values) + return db.insert(tableName, null, contValues) } - override fun delete(tableName: String, - where: String, - vararg params: String) { + override fun delete( + tableName: String, + where: String, + vararg params: String, + ) { db.delete(tableName, where, params) } @@ -73,8 +73,7 @@ class AndroidDatabase( is Long -> values.put(key, value) is Double -> values.put(key, value) is String -> values.put(key, value) - else -> throw IllegalStateException( - "unsupported type: " + value) + else -> throw IllegalStateException("unsupported type: $value") } } return values diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Column.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Column.kt similarity index 82% rename from android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Column.java rename to android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Column.kt index be794b796..5e3eac49f 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Column.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Column.kt @@ -16,13 +16,7 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.core.database -package org.isoron.uhabits.core.database; - -import java.lang.annotation.*; - -@Retention(RetentionPolicy.RUNTIME) -public @interface Column -{ - String name() default ""; -} +@Retention(AnnotationRetention.RUNTIME) +annotation class Column(val name: String = "") \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Cursor.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Cursor.kt similarity index 83% rename from android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Cursor.java rename to android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Cursor.kt index af2920878..8185c5877 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Cursor.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Cursor.kt @@ -16,15 +16,13 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.core.database -package org.isoron.uhabits.core.database; +import java.io.* -import androidx.annotation.*; +interface Cursor : Closeable { -public interface Cursor extends AutoCloseable -{ - @Override - void close(); + override fun close() /** * Moves the cursor forward one row from its current position. Returns @@ -32,37 +30,33 @@ public interface Cursor extends AutoCloseable * past the last row. The cursor start at position -1, so this method must * be called first. */ - boolean moveToNext(); + fun moveToNext(): Boolean /** * Retrieves the value of the designated column in the current row of this * Cursor as an Integer. If the value is null, returns null. The first * column has index zero. */ - @Nullable - Integer getInt(int index); + fun getInt(index: Int): Int? /** * Retrieves the value of the designated column in the current row of this * Cursor as a Long. If the value is null, returns null. The first * column has index zero. */ - @Nullable - Long getLong(int index); + fun getLong(index: Int): Long? /** * Retrieves the value of the designated column in the current row of this * Cursor as a Double. If the value is null, returns null. The first * column has index zero. */ - @Nullable - Double getDouble(int index); + fun getDouble(index: Int): Double? /** * Retrieves the value of the designated column in the current row of this * Cursor as a String. If the value is null, returns null. The first * column has index zero. */ - @Nullable - String getString(int index); -} + fun getString(index: Int): String? +} \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Database.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Database.java deleted file mode 100644 index 2b70ecc21..000000000 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Database.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * 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 . - */ - -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 values, - String where, - String... params); - - Long insert(String tableName, Map 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); - } -} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Database.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Database.kt new file mode 100644 index 000000000..568df8d5f --- /dev/null +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Database.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * 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 . + */ +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, + where: String, + vararg params: String, + ): Int + + fun insert(tableName: String, values: Map): 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) + } +} \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/DatabaseOpener.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/DatabaseOpener.kt similarity index 82% rename from android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/DatabaseOpener.java rename to android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/DatabaseOpener.kt index 30910b1eb..bdaf2a055 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/DatabaseOpener.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/DatabaseOpener.kt @@ -16,14 +16,10 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.core.database -package org.isoron.uhabits.core.database; +import java.io.* -import androidx.annotation.*; - -import java.io.*; - -public interface DatabaseOpener -{ - Database open(@NonNull File file); -} +interface DatabaseOpener { + fun open(file: File): Database? +} \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcCursor.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcCursor.java deleted file mode 100644 index 3e3797d9d..000000000 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcCursor.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * 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 . - * - * - */ - -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); - } - } -} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcCursor.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcCursor.kt new file mode 100644 index 000000000..6b7c86cf1 --- /dev/null +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcCursor.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * 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 . + * + * + */ +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) + } + } +} \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcDatabase.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcDatabase.java deleted file mode 100644 index 93591cfd1..000000000 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcDatabase.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * 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 . - */ - -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 map, - String where, - String... params) - { - try - { - ArrayList fields = new ArrayList<>(); - ArrayList values = new ArrayList<>(); - - for (Map.Entry 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 map) - { - try - { - ArrayList fields = new ArrayList<>(); - ArrayList params = new ArrayList<>(); - ArrayList questionMarks = new ArrayList<>(); - - for (Map.Entry 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; - } -} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcDatabase.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcDatabase.kt new file mode 100644 index 000000000..b49c6f645 --- /dev/null +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/JdbcDatabase.kt @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * 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 . + */ +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, + where: String, + vararg params: String, + ): Int { + return try { + val fields = ArrayList() + val valuesStr = ArrayList() + 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): Long? { + return try { + val fields = ArrayList() + val params = ArrayList() + val questionMarks = ArrayList() + 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): 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 +} \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/MigrationHelper.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/MigrationHelper.java deleted file mode 100644 index bad533a2e..000000000 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/MigrationHelper.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * 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 . - */ - -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); - } -} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/MigrationHelper.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/MigrationHelper.kt new file mode 100644 index 000000000..daed0f9ec --- /dev/null +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/MigrationHelper.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * 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 . + */ +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") + } +} \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Repository.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Repository.java deleted file mode 100644 index a61b86ffa..000000000 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Repository.java +++ /dev/null @@ -1,355 +0,0 @@ -/* - * Copyright (C) 2017 Álinson Santos Xavier - * - * 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 . - */ - -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 -{ - @NonNull - private final Class klass; - - @NonNull - private final Database db; - - public Repository(@NonNull Class 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. - *

- * 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 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. - *

- * 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. - *

- * 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. - *

- * 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. - */ - public void save(T record) - { - try - { - Field fields[] = getFields(); - String columns[] = getColumnNames(); - - Map 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 cursorToMultipleRecords(Cursor c) - { - List 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> getFieldColumnPairs() - { - List> 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 fields = new ArrayList<>(); - List> columns = getFieldColumnPairs(); - for (Pair 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 names = new ArrayList<>(); - List> columns = getFieldColumnPairs(); - for (Pair 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; - } -} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Repository.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Repository.kt new file mode 100644 index 000000000..3275591e4 --- /dev/null +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Repository.kt @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2017 Álinson Santos Xavier + * + * 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 . + */ +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( + private val klass: Class, + 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 { + 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 = 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 { + val records: MutableList = 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> + get() { + val fields: MutableList> = 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? = null + + private fun getFields(): Array { + if (cacheFields == null) { + val fields: MutableList = ArrayList() + val columns = fieldColumnPairs + for (pair in columns) fields.add(pair.left) + cacheFields = fields.toTypedArray() + } + return cacheFields!! + } + + private var cacheColumnNames: Array? = null + + private fun getColumnNames(): Array { + if (cacheColumnNames == null) { + val names: MutableList = 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 + } +} \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/SQLParser.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/SQLParser.java deleted file mode 100644 index b2b5f593b..000000000 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/SQLParser.java +++ /dev/null @@ -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 parse(final InputStream stream) throws IOException { - - final BufferedInputStream buffer = new BufferedInputStream(stream); - final List commands = new ArrayList(); - 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 == ' '; - } -} \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/SQLParser.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/SQLParser.kt new file mode 100644 index 000000000..d338e160a --- /dev/null +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/SQLParser.kt @@ -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 { + val buffer = BufferedInputStream(stream) + val commands: MutableList = 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 == ' ' + } +} \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Table.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Table.kt similarity index 78% rename from android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Table.java rename to android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Table.kt index fc8fd0a55..5c41e9d67 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Table.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/Table.kt @@ -16,15 +16,8 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.core.database -package org.isoron.uhabits.core.database; - -import java.lang.annotation.*; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface Table -{ - String name(); - String id() default "id"; -} +@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +annotation class Table(val name: String, val id: String = "id") \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/UnsupportedDatabaseVersionException.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/UnsupportedDatabaseVersionException.kt similarity index 86% rename from android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/UnsupportedDatabaseVersionException.java rename to android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/UnsupportedDatabaseVersionException.kt index f3379fb0c..3bd1ab730 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/UnsupportedDatabaseVersionException.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/database/UnsupportedDatabaseVersionException.kt @@ -16,9 +16,6 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ +package org.isoron.uhabits.core.database -package org.isoron.uhabits.core.database; - -public class UnsupportedDatabaseVersionException extends RuntimeException -{ -} +class UnsupportedDatabaseVersionException : RuntimeException() \ No newline at end of file diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/sync/SyncManager.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/sync/SyncManager.kt index 98a71de12..fcd8e8f65 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/sync/SyncManager.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/sync/SyncManager.kt @@ -89,7 +89,7 @@ class SyncManager @Inject constructor( } logger.info("Encrypting local database...") - val encryptedDB = db.file.encryptToString(encryptionKey) + val encryptedDB = db.file!!.encryptToString(encryptionKey) val size = encryptedDB.length / 1024 try {