diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java index 5b0a62dc9..4eeb5f6b9 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/HabitFixtures.java @@ -115,9 +115,8 @@ public class HabitFixtures return habit; } - public void purgeHabits(HabitList habitList) + public synchronized void purgeHabits(HabitList habitList) { - for (Habit h : habitList) - habitList.remove(h); + habitList.removeAll(); } } diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/HabitRecordTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/HabitRecordTest.java index 872b3382c..18c6045f7 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/HabitRecordTest.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/HabitRecordTest.java @@ -22,6 +22,9 @@ package org.isoron.uhabits.models.sqlite; import android.support.test.runner.*; import android.test.suitebuilder.annotation.*; +import com.activeandroid.*; + +import org.isoron.androidbase.storage.*; import org.isoron.uhabits.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.models.sqlite.records.*; @@ -35,25 +38,18 @@ import static org.hamcrest.core.IsEqual.*; @MediumTest public class HabitRecordTest extends BaseAndroidTest { + private Habit habit; + + private SQLiteRepository sqlite = + new SQLiteRepository<>(HabitRecord.class, Cache.openDatabase()); + + @Before @Override public void setUp() { super.setUp(); - Habit h = component.getModelFactory().buildHabit(); - h.setName("Hello world"); - h.setId(1000L); - - HabitRecord record = new HabitRecord(); - record.copyFrom(h); - record.position = 0; - record.save(1000L); - } - - @Test - public void testCopyFrom() - { - Habit habit = component.getModelFactory().buildHabit(); + habit = component.getModelFactory().buildHabit(); habit.setName("Hello world"); habit.setDescription("Did you greet the world today?"); habit.setColor(1); @@ -61,7 +57,11 @@ public class HabitRecordTest extends BaseAndroidTest habit.setFrequency(Frequency.THREE_TIMES_PER_WEEK); habit.setReminder(new Reminder(8, 30, WeekdayList.EVERY_DAY)); habit.setId(1000L); + } + @Test + public void testCopyFrom() + { HabitRecord rec = new HabitRecord(); rec.copyFrom(habit); diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java index a7b308c73..1b7c02a56 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteHabitListTest.java @@ -22,11 +22,12 @@ package org.isoron.uhabits.models.sqlite; import android.support.test.runner.*; import android.test.suitebuilder.annotation.*; -import com.activeandroid.query.*; +import com.activeandroid.*; +import com.google.common.collect.*; +import org.isoron.androidbase.storage.*; import org.isoron.uhabits.*; import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.models.sqlite.*; import org.isoron.uhabits.models.sqlite.records.*; import org.junit.*; import org.junit.rules.*; @@ -49,6 +50,8 @@ public class SQLiteHabitListTest extends BaseAndroidTest private ModelFactory modelFactory; + private SQLiteRepository repository; + @Override public void setUp() { @@ -57,6 +60,8 @@ public class SQLiteHabitListTest extends BaseAndroidTest fixtures.purgeHabits(habitList); modelFactory = component.getModelFactory(); + repository = + new SQLiteRepository<>(HabitRecord.class, Cache.openDatabase()); for (int i = 0; i < 10; i++) { @@ -68,8 +73,10 @@ public class SQLiteHabitListTest extends BaseAndroidTest HabitRecord record = new HabitRecord(); record.copyFrom(h); record.position = i; - record.save(i); + repository.save(record); } + + habitList.reload(); } @Test @@ -91,7 +98,7 @@ public class SQLiteHabitListTest extends BaseAndroidTest habitList.add(habit); assertThat(habit.getId(), equalTo(12300L)); - HabitRecord record = getRecord(12300L); + HabitRecord record = repository.find(12300L); assertNotNull(record); assertThat(record.name, equalTo(habit.getName())); } @@ -106,7 +113,7 @@ public class SQLiteHabitListTest extends BaseAndroidTest habitList.add(habit); assertNotNull(habit.getId()); - HabitRecord record = getRecord(habit.getId()); + HabitRecord record = repository.find(habit.getId()); assertNotNull(record); assertThat(record.name, equalTo(habit.getName())); } @@ -120,7 +127,7 @@ public class SQLiteHabitListTest extends BaseAndroidTest @Test public void testGetAll_withArchived() { - List habits = habitList.toList(); + List habits = Lists.newArrayList(habitList.iterator()); assertThat(habits.size(), equalTo(10)); assertThat(habits.get(3).getName(), equalTo("habit 3")); } @@ -166,12 +173,4 @@ public class SQLiteHabitListTest extends BaseAndroidTest h2.setId(1000L); assertThat(habitList.indexOf(h2), equalTo(-1)); } - - private HabitRecord getRecord(long id) - { - return new Select() - .from(HabitRecord.class) - .where("id = ?", id) - .executeSingle(); - } } \ No newline at end of file diff --git a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java b/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java index dd3657a62..7bd1abfcc 100644 --- a/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java +++ b/uhabits-android/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java @@ -23,8 +23,9 @@ import android.support.annotation.*; import android.support.test.runner.*; import android.test.suitebuilder.annotation.*; -import com.activeandroid.query.*; +import com.activeandroid.*; +import org.isoron.androidbase.storage.*; import org.isoron.uhabits.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.core.utils.*; @@ -35,7 +36,7 @@ import org.junit.runner.*; import java.util.*; import static android.support.test.espresso.matcher.ViewMatchers.assertThat; -import static org.hamcrest.core.IsEqual.equalTo; +import static org.hamcrest.core.IsEqual.*; import static org.isoron.uhabits.core.models.Checkmark.CHECKED_EXPLICITLY; @RunWith(AndroidJUnit4.class) @@ -50,6 +51,8 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest private long day; + private SQLiteRepository sqlite; + @Override public void setUp() { @@ -59,6 +62,8 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest repetitions = habit.getRepetitions(); today = DateUtils.getStartOfToday(); day = DateUtils.millisecondsInOneDay; + sqlite = new SQLiteRepository<>(RepetitionRecord.class, + Cache.openDatabase()); } @Test @@ -130,15 +135,13 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest @Nullable private RepetitionRecord getByTimestamp(long timestamp) { - return selectByTimestamp(timestamp).executeSingle(); - } + String query = "where habit = ? and timestamp = ?"; - @NonNull - private From selectByTimestamp(long timestamp) - { - return new Select() - .from(RepetitionRecord.class) - .where("habit = ?", habit.getId()) - .and("timestamp = ?", timestamp); + String params[] = { + Long.toString(habit.getId()), + Long.toString(timestamp) + }; + + return sqlite.findFirst(query, params); } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java index 4025e824c..795c60ae4 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLModelFactory.java @@ -39,9 +39,9 @@ public class SQLModelFactory implements ModelFactory @Provides @AppScope - public static HabitList provideHabitList() + public static HabitList provideHabitList(ModelFactory modelFactory) { - return SQLiteHabitList.getInstance(provideModelFactory()); + return new SQLiteHabitList(modelFactory); } @Override diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java index 43eed3ce4..74149d309 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteHabitList.java @@ -19,55 +19,61 @@ package org.isoron.uhabits.models.sqlite; +import android.database.sqlite.*; import android.support.annotation.*; -import com.activeandroid.query.*; -import com.activeandroid.util.*; +import com.activeandroid.*; -import org.apache.commons.lang3.*; +import org.isoron.androidbase.storage.*; import org.isoron.uhabits.core.models.*; +import org.isoron.uhabits.core.models.memory.*; import org.isoron.uhabits.models.sqlite.records.*; import java.util.*; +import static org.isoron.uhabits.utils.DatabaseUtils.executeAsTransaction; + /** * Implementation of a {@link HabitList} that is backed by SQLite. */ public class SQLiteHabitList extends HabitList { - private static HashMap cache; - private static SQLiteHabitList instance; @NonNull - private final SQLiteUtils sqlite; + private final SQLiteRepository repository; @NonNull private final ModelFactory modelFactory; @NonNull - private Order order; + private final MemoryHabitList list; + + private boolean loaded = false; public SQLiteHabitList(@NonNull ModelFactory modelFactory) { super(); this.modelFactory = modelFactory; + this.list = new MemoryHabitList(); - if (cache == null) cache = new HashMap<>(); - sqlite = new SQLiteUtils<>(HabitRecord.class); - order = Order.BY_POSITION; + repository = + new SQLiteRepository<>(HabitRecord.class, Cache.openDatabase()); } - protected SQLiteHabitList(@NonNull ModelFactory modelFactory, - @NonNull HabitMatcher filter, - @NonNull Order order) + private void loadRecords() { - super(filter); - this.modelFactory = modelFactory; + if(loaded) return; + loaded = true; - if (cache == null) cache = new HashMap<>(); - sqlite = new SQLiteUtils<>(HabitRecord.class); - this.order = order; + list.removeAll(); + List records = repository.findAll("order by position"); + for (HabitRecord rec : records) + { + Habit h = modelFactory.buildHabit(); + rec.copyTo(h); + list.add(h); + } } public static SQLiteHabitList getInstance( @@ -78,127 +84,120 @@ public class SQLiteHabitList extends HabitList } @Override - public void add(@NonNull Habit habit) + public synchronized void add(@NonNull Habit habit) { - if (cache.containsValue(habit)) - throw new IllegalArgumentException("habit already added"); + loadRecords(); + list.add(habit); HabitRecord record = new HabitRecord(); record.copyFrom(habit); - record.position = size(); - - Long id = habit.getId(); - if (id == null) id = record.save(); - else record.save(id); - - if (id < 0) - throw new IllegalArgumentException("habit could not be saved"); - - habit.setId(id); - cache.put(id, habit); + record.position = list.indexOf(habit); + repository.save(record); } @Override @Nullable public Habit getById(long id) { - if (!cache.containsKey(id)) - { - HabitRecord record = HabitRecord.get(id); - if (record == null) return null; - - Habit habit = modelFactory.buildHabit(); - record.copyTo(habit); - cache.put(id, habit); - } - - return cache.get(id); + loadRecords(); + return list.getById(id); } @Override @NonNull public Habit getByPosition(int position) { - return toList().get(position); + loadRecords(); + return list.getByPosition(position); } @NonNull @Override public HabitList getFiltered(HabitMatcher filter) { - return new SQLiteHabitList(modelFactory, filter, order); + loadRecords(); + return list.getFiltered(filter); } @Override @NonNull public Order getOrder() { - return order; + return list.getOrder(); } @Override public void setOrder(@NonNull Order order) { - this.order = order; + list.setOrder(order); } @Override public int indexOf(@NonNull Habit h) { - return toList().indexOf(h); + loadRecords(); + return list.indexOf(h); } @Override public Iterator iterator() { - return Collections.unmodifiableCollection(toList()).iterator(); + loadRecords(); + return list.iterator(); } - public void rebuildOrder() + private void rebuildOrder() { - List habits = toList(); - - int i = 0; - for (Habit h : habits) - { - HabitRecord record = HabitRecord.get(h.getId()); - if (record == null) - throw new RuntimeException("habit not in database"); - - record.position = i++; - record.save(); - } - - update(habits); +// List habits = toList(); +// +// int i = 0; +// for (Habit h : habits) +// { +// HabitRecord record = repository.find(h.getId()); +// if (record == null) +// throw new RuntimeException("habit not in database"); +// +// record.position = i++; +// repository.save(record); +// } +// +// update(habits); } @Override - public void remove(@NonNull Habit habit) + public synchronized void remove(@NonNull Habit habit) { - if (!cache.containsKey(habit.getId())) - throw new RuntimeException("habit not in cache"); + loadRecords(); + list.remove(habit); - cache.remove(habit.getId()); - HabitRecord record = HabitRecord.get(habit.getId()); + HabitRecord record = repository.find(habit.getId()); if (record == null) throw new RuntimeException("habit not in database"); - record.cascadeDelete(); + executeAsTransaction(() -> + { + ((SQLiteRepetitionList) habit.getRepetitions()).removeAll(); + repository.remove(record); + }); rebuildOrder(); } @Override - public void removeAll() + public synchronized void removeAll() { - sqlite.query("delete from repetitions", null); - sqlite.query("delete from habits", null); + loadRecords(); + list.removeAll(); + SQLiteDatabase db = Cache.openDatabase(); + db.execSQL("delete from habits"); + db.execSQL("delete from repetitions"); } @Override public synchronized void reorder(Habit from, Habit to) { - if (from == to) return; + loadRecords(); + list.reorder(from, to); - HabitRecord fromRecord = HabitRecord.get(from.getId()); - HabitRecord toRecord = HabitRecord.get(to.getId()); + HabitRecord fromRecord = repository.find(from.getId()); + HabitRecord toRecord = repository.find(to.getId()); if (fromRecord == null) throw new RuntimeException("habit not in database"); @@ -207,27 +206,22 @@ public class SQLiteHabitList extends HabitList Integer fromPos = fromRecord.position; Integer toPos = toRecord.position; - - Log.d("SQLiteHabitList", - String.format("reorder: %d %d", fromPos, toPos)); - + SQLiteDatabase db = Cache.openDatabase(); if (toPos < fromPos) { - new Update(HabitRecord.class) - .set("position = position + 1") - .where("position >= ? and position < ?", toPos, fromPos) - .execute(); + db.execSQL("update habits set position = position + 1 " + + "where position >= ? and position < ?", + new String[]{ toPos.toString(), fromPos.toString() }); } else { - new Update(HabitRecord.class) - .set("position = position - 1") - .where("position > ? and position <= ?", fromPos, toPos) - .execute(); + db.execSQL("update habits set position = position - 1 " + + "where position > ? and position <= ?", + new String[]{ fromPos.toString(), toPos.toString() }); } fromRecord.position = toPos; - fromRecord.save(); + repository.save(fromRecord); update(from); getObservable().notifyListeners(); } @@ -235,100 +229,33 @@ public class SQLiteHabitList extends HabitList @Override public void repair() { - super.repair(); + loadRecords(); rebuildOrder(); } @Override public int size() { - return toList().size(); + loadRecords(); + return list.size(); } @Override - public void update(List habits) + public synchronized void update(List habits) { + loadRecords(); for (Habit h : habits) { - HabitRecord record = HabitRecord.get(h.getId()); + HabitRecord record = repository.find(h.getId()); if (record == null) throw new RuntimeException("habit not in database"); record.copyFrom(h); - record.save(); + repository.save(record); } } - public synchronized List toList() - { - String query = buildSelectQuery(); - List recordList = sqlite.query(query, null); - - List habits = new LinkedList<>(); - for (HabitRecord record : recordList) - { - Habit habit = getById(record.getId()); - if (habit == null) continue; - if (!filter.matches(habit)) continue; - habits.add(habit); - } - - if(order == Order.BY_SCORE) - { - Collections.sort(habits, (lhs, rhs) -> { - double s1 = lhs.getScores().getTodayValue(); - double s2 = rhs.getScores().getTodayValue(); - return Double.compare(s2, s1); - }); - } - - return habits; - } - - private void appendOrderBy(StringBuilder query) - { - switch (order) - { - case BY_POSITION: - query.append("order by position "); - break; - - case BY_NAME: - case BY_SCORE: - query.append("order by name "); - break; - - case BY_COLOR: - query.append("order by color, name "); - break; - - default: - throw new IllegalStateException(); - } - } - - private void appendSelect(StringBuilder query) - { - query.append(HabitRecord.SELECT); - } - - private void appendWhere(StringBuilder query) - { - ArrayList where = new ArrayList<>(); - if (filter.isReminderRequired()) where.add("reminder_hour is not null"); - if (!filter.isArchivedAllowed()) where.add("archived = 0"); - - if (where.isEmpty()) return; - query.append("where "); - query.append(StringUtils.join(where, " and ")); - query.append(" "); - } - - private String buildSelectQuery() + public void reload() { - StringBuilder query = new StringBuilder(); - appendSelect(query); - appendWhere(query); - appendOrderBy(query); - return query.toString(); + loaded = false; } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java index 1ebac67f7..71ed98077 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java @@ -19,15 +19,14 @@ package org.isoron.uhabits.models.sqlite; -import android.database.*; -import android.database.sqlite.*; import android.support.annotation.*; import android.support.annotation.Nullable; import com.activeandroid.*; -import com.activeandroid.query.*; +import org.isoron.androidbase.storage.*; import org.isoron.uhabits.core.models.*; +import org.isoron.uhabits.core.models.memory.*; import org.isoron.uhabits.models.sqlite.records.*; import org.jetbrains.annotations.*; @@ -38,165 +37,112 @@ import java.util.*; */ public class SQLiteRepetitionList extends RepetitionList { + private final SQLiteRepository repository; - private final SQLiteUtils sqlite; + private final MemoryRepetitionList list; - @Nullable - private HabitRecord habitRecord; - - private SQLiteStatement addStatement; - - public static final String ADD_QUERY = - "insert into repetitions(habit, timestamp, value) " + - "values (?,?,?)"; + private boolean loaded = false; public SQLiteRepetitionList(@NonNull Habit habit) { super(habit); - sqlite = new SQLiteUtils<>(RepetitionRecord.class); + repository = new SQLiteRepository<>(RepetitionRecord.class, + Cache.openDatabase()); + list = new MemoryRepetitionList(habit); + } + + private void loadRecords() + { + if (loaded) return; + loaded = true; + + check(habit.getId()); + List records = + repository.findAll("where habit = ? order by timestamp", + habit.getId().toString()); - SQLiteDatabase db = Cache.openDatabase(); - addStatement = db.compileStatement(ADD_QUERY); + for (RepetitionRecord rec : records) + list.add(rec.toRepetition()); } - /** - * Adds a repetition to the global SQLite database. - *

- * Given a repetition, this creates and saves the corresponding - * RepetitionRecord to the database. - * - * @param rep the repetition to be added - */ @Override public void add(Repetition rep) { + loadRecords(); + list.add(rep); check(habit.getId()); - addStatement.bindLong(1, habit.getId()); - addStatement.bindLong(2, rep.getTimestamp()); - addStatement.bindLong(3, rep.getValue()); - addStatement.execute(); + RepetitionRecord record = new RepetitionRecord(); + record.habit_id = habit.getId(); + record.copyFrom(rep); + repository.save(record); observable.notifyListeners(); } @Override public List getByInterval(long timeFrom, long timeTo) { - check(habit.getId()); - String query = "select habit, timestamp, value " + - "from Repetitions " + - "where habit = ? and timestamp >= ? and timestamp <= ? " + - "order by timestamp"; - - String params[] = { - Long.toString(habit.getId()), - Long.toString(timeFrom), - Long.toString(timeTo) - }; - - List records = sqlite.query(query, params); - return toRepetitions(records); + loadRecords(); + return list.getByInterval(timeFrom, timeTo); } @Override @Nullable public Repetition getByTimestamp(long timestamp) { - check(habit.getId()); - String query = "select habit, timestamp, value " + - "from Repetitions " + - "where habit = ? and timestamp = ? " + - "limit 1"; - - String params[] = - { Long.toString(habit.getId()), Long.toString(timestamp) }; - - RepetitionRecord record = sqlite.querySingle(query, params); - if (record == null) return null; - record.habit = habitRecord; - return record.toRepetition(); + loadRecords(); + return list.getByTimestamp(timestamp); } @Override public Repetition getOldest() { - check(habit.getId()); - String query = "select habit, timestamp, value " + - "from Repetitions " + - "where habit = ? " + - "order by timestamp asc " + - "limit 1"; - - String params[] = { Long.toString(habit.getId()) }; - - RepetitionRecord record = sqlite.querySingle(query, params); - if (record == null) return null; - record.habit = habitRecord; - return record.toRepetition(); + loadRecords(); + return list.getOldest(); } @Override public Repetition getNewest() { - check(habit.getId()); - String query = "select habit, timestamp, value " + - "from Repetitions " + - "where habit = ? " + - "order by timestamp desc " + - "limit 1"; - - String params[] = { Long.toString(habit.getId()) }; - - RepetitionRecord record = sqlite.querySingle(query, params); - if (record == null) return null; - record.habit = habitRecord; - return record.toRepetition(); + loadRecords(); + return list.getNewest(); } @Override public void remove(@NonNull Repetition repetition) { - new Delete() - .from(RepetitionRecord.class) - .where("habit = ?", habit.getId()) - .and("timestamp = ?", repetition.getTimestamp()) - .execute(); - + loadRecords(); + list.remove(repetition); + check(habit.getId()); + repository.execSQL( + "delete from repetitions where habit = ? and timestamp = ?", + habit.getId()); observable.notifyListeners(); } - @Contract("null -> fail") - private void check(Long id) - { - if (id == null) throw new RuntimeException("habit is not saved"); - - if (habitRecord != null) return; - - habitRecord = HabitRecord.get(id); - if (habitRecord == null) throw new RuntimeException("habit not found"); - } - - @NonNull - private List toRepetitions( - @NonNull List records) + public void removeAll() { + loadRecords(); + list.removeAll(); check(habit.getId()); - - List reps = new LinkedList<>(); - for (RepetitionRecord record : records) - { - record.habit = habitRecord; - reps.add(record.toRepetition()); - } - - return reps; + repository.execSQL("delete from repetitions where habit = ?", + habit.getId()); } @Override public long getTotalCount() { - SQLiteDatabase db = Cache.openDatabase(); + loadRecords(); + return list.getTotalCount(); + } - return DatabaseUtils.queryNumEntries(db, "Repetitions", - "habit=?", new String[] { Long.toString(habit.getId()) }); + public void reload() + { + loaded = false; + } + + @Contract("null -> fail") + private void check(Long value) + { + if (value == null) throw new RuntimeException("null check failed"); } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteUtils.java b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteUtils.java deleted file mode 100644 index 616b0d12f..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteUtils.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2016 Á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.models.sqlite; - -import android.database.*; -import android.database.sqlite.*; -import android.support.annotation.*; - -import com.activeandroid.*; - -import org.isoron.uhabits.models.sqlite.records.*; - -import java.util.*; - -public class SQLiteUtils -{ - private Class klass; - - public SQLiteUtils(Class klass) - { - this.klass = klass; - } - - @NonNull - public List query(String query, String params[]) - { - SQLiteDatabase db = Cache.openDatabase(); - try (Cursor c = db.rawQuery(query, params)) - { - return cursorToMultipleRecords(c); - } - } - - @Nullable - public T querySingle(String query, String params[]) - { - SQLiteDatabase db = Cache.openDatabase(); - try(Cursor c = db.rawQuery(query, params)) - { - if (!c.moveToNext()) return null; - return cursorToSingleRecord(c); - } - } - - @NonNull - private List cursorToMultipleRecords(Cursor c) - { - List records = new LinkedList<>(); - while (c.moveToNext()) records.add(cursorToSingleRecord(c)); - return records; - } - - @NonNull - private T cursorToSingleRecord(Cursor c) - { - try - { - T record = (T) klass.newInstance(); - record.copyFrom(c); - return record; - } - catch (Exception e) - { - throw new RuntimeException(e); - } - } -} diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java index 5731da245..e0bbe26d9 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java @@ -19,122 +19,85 @@ package org.isoron.uhabits.models.sqlite.records; -import android.annotation.*; -import android.database.*; -import android.support.annotation.*; - import com.activeandroid.*; -import com.activeandroid.annotation.*; -import com.activeandroid.query.*; -import com.activeandroid.util.*; +import org.apache.commons.lang3.builder.*; +import org.isoron.androidbase.storage.*; import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.utils.DatabaseUtils; - -import java.lang.reflect.*; /** * The SQLite database record corresponding to a {@link Habit}. */ -@Table(name = "Habits") -public class HabitRecord extends Model implements SQLiteRecord +@Table(name = "habits") +@com.activeandroid.annotation.Table(name = "Habits") +public class HabitRecord extends Model { - public static String SELECT = - "select id, color, description, freq_den, freq_num, " + - "name, position, reminder_hour, reminder_min, " + - "highlight, archived, reminder_days, type, target_type, " + - "target_value, unit from habits "; + @Column + @com.activeandroid.annotation.Column + public String description; - @Column(name = "name") + @Column + @com.activeandroid.annotation.Column public String name; - @Column(name = "description") - public String description; - @Column(name = "freq_num") - public int freqNum; + @com.activeandroid.annotation.Column(name = "freq_num") + public Integer freqNum; @Column(name = "freq_den") - public int freqDen; + @com.activeandroid.annotation.Column(name = "freq_den") + public Integer freqDen; - @Column(name = "color") - public int color; + @Column + @com.activeandroid.annotation.Column + public Integer color; - @Column(name = "position") - public int position; + @Column + @com.activeandroid.annotation.Column + public Integer position; - @Nullable @Column(name = "reminder_hour") + @com.activeandroid.annotation.Column(name = "reminder_hour") public Integer reminderHour; - @Nullable @Column(name = "reminder_min") + @com.activeandroid.annotation.Column(name = "reminder_min") public Integer reminderMin; @Column(name = "reminder_days") - public int reminderDays; + @com.activeandroid.annotation.Column(name = "reminder_days") + public Integer reminderDays; - @Column(name = "highlight") - public int highlight; + @Column + @com.activeandroid.annotation.Column + public Integer highlight; - @Column(name = "archived") - public int archived; + @Column + @com.activeandroid.annotation.Column + public Integer archived; - @Column(name = "type") - public int type; + @Column + @com.activeandroid.annotation.Column + public Integer type; @Column(name = "target_value") - public double targetValue; + @com.activeandroid.annotation.Column(name = "target_value") + public Double targetValue; @Column(name = "target_type") - public int targetType; + @com.activeandroid.annotation.Column(name = "target_type") + public Integer targetType; - @Column(name = "unit") + @Column + @com.activeandroid.annotation.Column public String unit; - public HabitRecord() - { - } - - @Nullable - public static HabitRecord get(long id) - { - return HabitRecord.load(HabitRecord.class, id); - } - - /** - * Changes the id of a habit on the database. - * - * @param oldId the original id - * @param newId the new id - */ - @SuppressLint("DefaultLocale") - public static void updateId(long oldId, long newId) - { - SQLiteUtils.execSql( - String.format("update Habits set Id = %d where Id = %d", newId, - oldId)); - } - - /** - * Deletes the habit and all data associated to it, including checkmarks, - * repetitions and scores. - */ - public void cascadeDelete() - { - Long id = getId(); - - DatabaseUtils.executeAsTransaction(() -> { - new Delete() - .from(RepetitionRecord.class) - .where("habit = ?", id) - .execute(); - delete(); - }); - } + @Column + public Long id; public void copyFrom(Habit model) { + this.id = model.getId(); this.name = model.getName(); this.description = model.getDescription(); this.highlight = 0; @@ -161,35 +124,14 @@ public class HabitRecord extends Model implements SQLiteRecord } } - @Override - public void copyFrom(Cursor c) - { - setId(c.getLong(0)); - color = c.getInt(1); - description = c.getString(2); - freqDen = c.getInt(3); - freqNum = c.getInt(4); - name = c.getString(5); - position = c.getInt(6); - reminderHour = c.getInt(7); - reminderMin = c.getInt(8); - highlight = c.getInt(9); - archived = c.getInt(10); - reminderDays = c.getInt(11); - type = c.getInt(12); - targetType = c.getInt(13); - targetValue = c.getDouble(14); - unit = c.getString(15); - } - public void copyTo(Habit habit) { + habit.setId(this.id); habit.setName(this.name); habit.setDescription(this.description); habit.setFrequency(new Frequency(this.freqNum, this.freqDen)); habit.setColor(this.color); habit.setArchived(this.archived != 0); - habit.setId(this.getId()); habit.setType(this.type); habit.setTargetType(this.targetType); habit.setTargetValue(this.targetValue); @@ -202,28 +144,77 @@ public class HabitRecord extends Model implements SQLiteRecord } } - /** - * Saves the habit on the database, and assigns the specified id to it. - * - * @param id the id that the habit should receive - */ - public void save(long id) + @Override + public boolean equals(Object o) { - save(); - updateId(getId(), id); + if (this == o) return true; + + if (o == null || getClass() != o.getClass()) return false; + + HabitRecord that = (HabitRecord) o; + + return new EqualsBuilder() + .appendSuper(super.equals(o)) + .append(freqNum, that.freqNum) + .append(freqDen, that.freqDen) + .append(color, that.color) + .append(position, that.position) + .append(reminderDays, that.reminderDays) + .append(highlight, that.highlight) + .append(archived, that.archived) + .append(type, that.type) + .append(targetValue, that.targetValue) + .append(targetType, that.targetType) + .append(name, that.name) + .append(description, that.description) + .append(reminderHour, that.reminderHour) + .append(reminderMin, that.reminderMin) + .append(unit, that.unit) + .isEquals(); } - private void setId(Long id) + @Override + public int hashCode() { - try - { - Field f = (Model.class).getDeclaredField("mId"); - f.setAccessible(true); - f.set(this, id); - } - catch (Exception e) - { - throw new RuntimeException(e); - } + return new HashCodeBuilder(17, 37) + .appendSuper(super.hashCode()) + .append(name) + .append(description) + .append(freqNum) + .append(freqDen) + .append(color) + .append(position) + .append(reminderHour) + .append(reminderMin) + .append(reminderDays) + .append(highlight) + .append(archived) + .append(type) + .append(targetValue) + .append(targetType) + .append(unit) + .toHashCode(); + } + + @Override + public String toString() + { + return new ToStringBuilder(this) + .append("name", name) + .append("description", description) + .append("freqNum", freqNum) + .append("freqDen", freqDen) + .append("color", color) + .append("position", position) + .append("reminderHour", reminderHour) + .append("reminderMin", reminderMin) + .append("reminderDays", reminderDays) + .append("highlight", highlight) + .append("archived", archived) + .append("type", type) + .append("targetValue", targetValue) + .append("targetType", targetType) + .append("unit", unit) + .toString(); } } diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java index b10b7c73e..5014a2657 100644 --- a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java +++ b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java @@ -19,32 +19,34 @@ package org.isoron.uhabits.models.sqlite.records; -import android.database.*; - import com.activeandroid.*; -import com.activeandroid.annotation.*; +import org.isoron.androidbase.storage.*; import org.isoron.uhabits.core.models.*; /** * The SQLite database record corresponding to a {@link Repetition}. */ @Table(name = "Repetitions") -public class RepetitionRecord extends Model implements SQLiteRecord +@com.activeandroid.annotation.Table(name = "Repetitions") +public class RepetitionRecord extends Model { - @Column(name = "habit") + @com.activeandroid.annotation.Column(name = "habit") public HabitRecord habit; - @Column(name = "timestamp") + @Column(name = "habit") + public Long habit_id; + + @Column + @com.activeandroid.annotation.Column(name = "timestamp") public Long timestamp; - @Column(name = "value") - public int value; + @Column + @com.activeandroid.annotation.Column(name = "value") + public Integer value; - public static RepetitionRecord get(Long id) - { - return RepetitionRecord.load(RepetitionRecord.class, id); - } + @Column + public Long id; public void copyFrom(Repetition repetition) { @@ -52,13 +54,6 @@ public class RepetitionRecord extends Model implements SQLiteRecord value = repetition.getValue(); } - @Override - public void copyFrom(Cursor c) - { - timestamp = c.getLong(1); - value = c.getInt(2); - } - public Repetition toRepetition() { return new Repetition(timestamp, value); diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/SQLiteRecord.java b/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/SQLiteRecord.java deleted file mode 100644 index 1991b1276..000000000 --- a/uhabits-android/src/main/java/org/isoron/uhabits/models/sqlite/records/SQLiteRecord.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (C) 2016 Á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.models.sqlite.records; - -import android.database.*; - -public interface SQLiteRecord -{ - void copyFrom(Cursor c); -} diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java index 790e9d945..094783ede 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/RepetitionList.java @@ -221,4 +221,6 @@ public abstract class RepetitionList add(new Repetition(timestamp, value)); habit.invalidateNewerThan(timestamp); } + + public abstract void removeAll(); } diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java index 728ba3548..b6d3ee718 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryCheckmarkList.java @@ -22,6 +22,7 @@ package org.isoron.uhabits.core.models.memory; import android.support.annotation.*; import org.isoron.uhabits.core.models.*; +import org.isoron.uhabits.core.utils.*; import java.util.*; @@ -60,8 +61,10 @@ public class MemoryCheckmarkList extends CheckmarkList Checkmark oldest = getOldestComputed(); if(newest != null) newestTimestamp = newest.getTimestamp(); if(oldest != null) oldestTimestamp = oldest.getTimestamp(); + long days = (newestTimestamp - oldestTimestamp) / + DateUtils.millisecondsInOneDay; - List filtered = new LinkedList<>(); + List filtered = new ArrayList<>((int) days); for(long time = toTimestamp; time >= fromTimestamp; time -= millisecondsInOneDay) { if(time > newestTimestamp || time < oldestTimestamp) diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java index 58ec38fe0..fbc82117f 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryHabitList.java @@ -25,7 +25,10 @@ import org.isoron.uhabits.core.models.*; import java.util.*; -import static org.isoron.uhabits.core.models.HabitList.Order.*; +import static org.isoron.uhabits.core.models.HabitList.Order.BY_COLOR; +import static org.isoron.uhabits.core.models.HabitList.Order.BY_NAME; +import static org.isoron.uhabits.core.models.HabitList.Order.BY_POSITION; +import static org.isoron.uhabits.core.models.HabitList.Order.BY_SCORE; /** * In-memory implementation of {@link HabitList}. @@ -55,7 +58,8 @@ public class MemoryHabitList extends HabitList } @Override - public void add(@NonNull Habit habit) throws IllegalArgumentException + public synchronized void add(@NonNull Habit habit) + throws IllegalArgumentException { if (list.contains(habit)) throw new IllegalArgumentException("habit already added"); @@ -70,7 +74,7 @@ public class MemoryHabitList extends HabitList } @Override - public Habit getById(long id) + public synchronized Habit getById(long id) { for (Habit h : list) { @@ -82,14 +86,14 @@ public class MemoryHabitList extends HabitList @NonNull @Override - public Habit getByPosition(int position) + public synchronized Habit getByPosition(int position) { return list.get(position); } @NonNull @Override - public HabitList getFiltered(HabitMatcher matcher) + public synchronized HabitList getFiltered(HabitMatcher matcher) { MemoryHabitList habits = new MemoryHabitList(matcher); habits.comparator = comparator; @@ -98,11 +102,19 @@ public class MemoryHabitList extends HabitList } @Override - public Order getOrder() + public synchronized Order getOrder() { return order; } + @Override + public synchronized void setOrder(@NonNull Order order) + { + this.order = order; + this.comparator = getComparatorByOrder(order); + resort(); + } + @Override public int indexOf(@NonNull Habit h) { @@ -116,27 +128,19 @@ public class MemoryHabitList extends HabitList } @Override - public void remove(@NonNull Habit habit) + public synchronized void remove(@NonNull Habit habit) { list.remove(habit); } @Override - public void reorder(Habit from, Habit to) + public synchronized void reorder(Habit from, Habit to) { int toPos = indexOf(to); list.remove(from); list.add(toPos, from); } - @Override - public void setOrder(@NonNull Order order) - { - this.order = order; - this.comparator = getComparatorByOrder(order); - resort(); - } - @Override public int size() { @@ -154,14 +158,16 @@ public class MemoryHabitList extends HabitList Comparator nameComparator = (h1, h2) -> h1.getName().compareTo(h2.getName()); - Comparator colorComparator = (h1, h2) -> { + Comparator colorComparator = (h1, h2) -> + { Integer c1 = h1.getColor(); Integer c2 = h2.getColor(); if (c1.equals(c2)) return nameComparator.compare(h1, h2); else return c1.compareTo(c2); }; - Comparator scoreComparator = (h1, h2) -> { + Comparator scoreComparator = (h1, h2) -> + { double s1 = h1.getScores().getTodayValue(); double s2 = h2.getScores().getTodayValue(); return Double.compare(s2, s1); @@ -174,7 +180,7 @@ public class MemoryHabitList extends HabitList throw new IllegalStateException(); } - private void resort() + private synchronized void resort() { if (comparator != null) Collections.sort(list, comparator); } diff --git a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryRepetitionList.java b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryRepetitionList.java index 2d7c81fea..0d63b0529 100644 --- a/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryRepetitionList.java +++ b/uhabits-core/src/main/java/org/isoron/uhabits/core/models/memory/MemoryRepetitionList.java @@ -30,12 +30,12 @@ import java.util.*; */ public class MemoryRepetitionList extends RepetitionList { - LinkedList list; + ArrayList list; public MemoryRepetitionList(Habit habit) { super(habit); - list = new LinkedList<>(); + list = new ArrayList<>(); } @Override @@ -48,7 +48,7 @@ public class MemoryRepetitionList extends RepetitionList @Override public List getByInterval(long fromTimestamp, long toTimestamp) { - LinkedList filtered = new LinkedList<>(); + ArrayList filtered = new ArrayList<>(); for (Repetition r : list) { @@ -57,7 +57,7 @@ public class MemoryRepetitionList extends RepetitionList } Collections.sort(filtered, - (r1, r2) -> (int) (r1.getTimestamp() - r2.getTimestamp())); + (r1, r2) -> Long.compare(r1.getTimestamp(), r2.getTimestamp())); return filtered; } @@ -122,4 +122,10 @@ public class MemoryRepetitionList extends RepetitionList { return list.size(); } + + @Override + public void removeAll() + { + list.clear(); + } }