/* * 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.support.annotation.*; import com.activeandroid.query.*; import com.activeandroid.util.*; import org.apache.commons.lang3.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.models.sqlite.records.*; import java.util.*; /** * 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; @NonNull private final ModelFactory modelFactory; @NonNull private Order order; public SQLiteHabitList(@NonNull ModelFactory modelFactory) { super(); this.modelFactory = modelFactory; if (cache == null) cache = new HashMap<>(); sqlite = new SQLiteUtils<>(HabitRecord.class); order = Order.BY_POSITION; } protected SQLiteHabitList(@NonNull ModelFactory modelFactory, @NonNull HabitMatcher filter, @NonNull Order order) { super(filter); this.modelFactory = modelFactory; if (cache == null) cache = new HashMap<>(); sqlite = new SQLiteUtils<>(HabitRecord.class); this.order = order; } public static SQLiteHabitList getInstance( @NonNull ModelFactory modelFactory) { if (instance == null) instance = new SQLiteHabitList(modelFactory); return instance; } @Override public void add(@NonNull Habit habit) { if (cache.containsValue(habit)) throw new IllegalArgumentException("habit already added"); 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); } @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); } @Override @NonNull public Habit getByPosition(int position) { return toList().get(position); } @NonNull @Override public HabitList getFiltered(HabitMatcher filter) { return new SQLiteHabitList(modelFactory, filter, order); } @Override @NonNull public Order getOrder() { return order; } @Override public void setOrder(@NonNull Order order) { this.order = order; } @Override public int indexOf(@NonNull Habit h) { return toList().indexOf(h); } @Override public Iterator<Habit> iterator() { return Collections.unmodifiableCollection(toList()).iterator(); } public 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); } @Override public void remove(@NonNull Habit habit) { if (!cache.containsKey(habit.getId())) throw new RuntimeException("habit not in cache"); cache.remove(habit.getId()); HabitRecord record = HabitRecord.get(habit.getId()); if (record == null) throw new RuntimeException("habit not in database"); record.cascadeDelete(); rebuildOrder(); } @Override public void removeAll() { sqlite.query("delete from checkmarks", null); sqlite.query("delete from score", null); sqlite.query("delete from streak", null); sqlite.query("delete from repetitions", null); sqlite.query("delete from habits", null); } @Override public synchronized void reorder(Habit from, Habit to) { if (from == to) return; HabitRecord fromRecord = HabitRecord.get(from.getId()); HabitRecord toRecord = HabitRecord.get(to.getId()); if (fromRecord == null) throw new RuntimeException("habit not in database"); if (toRecord == null) throw new RuntimeException("habit not in database"); Integer fromPos = fromRecord.position; Integer toPos = toRecord.position; Log.d("SQLiteHabitList", String.format("reorder: %d %d", fromPos, toPos)); if (toPos < fromPos) { new Update(HabitRecord.class) .set("position = position + 1") .where("position >= ? and position < ?", toPos, fromPos) .execute(); } else { new Update(HabitRecord.class) .set("position = position - 1") .where("position > ? and position <= ?", fromPos, toPos) .execute(); } fromRecord.position = toPos; fromRecord.save(); update(from); getObservable().notifyListeners(); } @Override public void repair() { super.repair(); rebuildOrder(); } @Override public int size() { return toList().size(); } @Override public void update(List<Habit> habits) { for (Habit h : habits) { HabitRecord record = HabitRecord.get(h.getId()); if (record == null) throw new RuntimeException("habit not in database"); record.copyFrom(h); record.save(); } } protected 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) throw new RuntimeException("habit not in database"); if (!filter.matches(habit)) continue; habits.add(habit); } if(order == Order.BY_SCORE) { Collections.sort(habits, (lhs, rhs) -> { int s1 = lhs.getScores().getTodayValue(); int s2 = rhs.getScores().getTodayValue(); return Integer.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() { StringBuilder query = new StringBuilder(); appendSelect(query); appendWhere(query); appendOrderBy(query); return query.toString(); } }