Simplify SQLite lists

pull/312/head
Alinson S. Xavier 8 years ago
parent edeba897fb
commit 6d06e06840

@ -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();
}
}

@ -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<HabitRecord> 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);

@ -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<HabitRecord> 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<Habit> habits = habitList.toList();
List<Habit> 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();
}
}

@ -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<RepetitionRecord> 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);
}
}

@ -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

@ -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<Long, Habit> cache;
private static SQLiteHabitList instance;
@NonNull
private final SQLiteUtils<HabitRecord> sqlite;
private final SQLiteRepository<HabitRecord> 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<HabitRecord> 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<Habit> iterator()
{
return Collections.unmodifiableCollection(toList()).iterator();
loadRecords();
return list.iterator();
}
public void rebuildOrder()
private void rebuildOrder()
{
List<Habit> 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<Habit> 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<Habit> habits)
public synchronized void update(List<Habit> 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<Habit> toList()
{
String query = buildSelectQuery();
List<HabitRecord> recordList = sqlite.query(query, null);
List<Habit> 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<Object> 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;
}
}

@ -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<RepetitionRecord> repository;
private final SQLiteUtils<RepetitionRecord> 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<RepetitionRecord> 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.
* <p>
* 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<Repetition> 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<RepetitionRecord> 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<Repetition> toRepetitions(
@NonNull List<RepetitionRecord> records)
public void removeAll()
{
loadRecords();
list.removeAll();
check(habit.getId());
List<Repetition> 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");
}
}

@ -1,84 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.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<T extends SQLiteRecord>
{
private Class klass;
public SQLiteUtils(Class klass)
{
this.klass = klass;
}
@NonNull
public List<T> 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<T> cursorToMultipleRecords(Cursor c)
{
List<T> 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);
}
}
}

@ -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();
}
}

@ -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);

@ -1,27 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.models.sqlite.records;
import android.database.*;
public interface SQLiteRecord
{
void copyFrom(Cursor c);
}

@ -221,4 +221,6 @@ public abstract class RepetitionList
add(new Repetition(timestamp, value));
habit.invalidateNewerThan(timestamp);
}
public abstract void removeAll();
}

@ -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<Checkmark> filtered = new LinkedList<>();
List<Checkmark> filtered = new ArrayList<>((int) days);
for(long time = toTimestamp; time >= fromTimestamp; time -= millisecondsInOneDay)
{
if(time > newestTimestamp || time < oldestTimestamp)

@ -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<Habit> nameComparator =
(h1, h2) -> h1.getName().compareTo(h2.getName());
Comparator<Habit> colorComparator = (h1, h2) -> {
Comparator<Habit> 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<Habit> scoreComparator = (h1, h2) -> {
Comparator<Habit> 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);
}

@ -30,12 +30,12 @@ import java.util.*;
*/
public class MemoryRepetitionList extends RepetitionList
{
LinkedList<Repetition> list;
ArrayList<Repetition> 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<Repetition> getByInterval(long fromTimestamp, long toTimestamp)
{
LinkedList<Repetition> filtered = new LinkedList<>();
ArrayList<Repetition> 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();
}
}

Loading…
Cancel
Save