Convert model memory

pull/716/head
Quentin Hibon 5 years ago
parent a58cbffb81
commit 1cec5a6067

@ -68,7 +68,7 @@ class ModelObservable {
* Interface implemented by objects that want to be notified when the model
* changes.
*/
interface Listener {
fun interface Listener {
/**
* Called whenever the model associated to this observable has been
* modified.

@ -1,297 +0,0 @@
/*
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.models.memory;
import androidx.annotation.*;
import org.isoron.uhabits.core.models.*;
import org.isoron.uhabits.core.utils.*;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import static org.isoron.uhabits.core.models.HabitList.Order.*;
/**
* In-memory implementation of {@link HabitList}.
*/
public class MemoryHabitList extends HabitList
{
@NonNull
private LinkedList<Habit> list = new LinkedList<>();
@NonNull
private Order primaryOrder = Order.BY_POSITION;
@NonNull
private Order secondaryOrder = Order.BY_NAME_ASC;
private Comparator<Habit> comparator = getComposedComparatorByOrder(primaryOrder, secondaryOrder);
@Nullable
private MemoryHabitList parent = null;
public MemoryHabitList()
{
super();
}
protected MemoryHabitList(@NonNull HabitMatcher matcher,
Comparator<Habit> comparator,
@NonNull MemoryHabitList parent)
{
super(matcher);
this.parent = parent;
this.comparator = comparator;
this.primaryOrder = parent.primaryOrder;
this.secondaryOrder = parent.secondaryOrder;
parent.getObservable().addListener(this::loadFromParent);
loadFromParent();
}
@Override
public synchronized void add(@NonNull Habit habit)
throws IllegalArgumentException
{
throwIfHasParent();
if (list.contains(habit))
throw new IllegalArgumentException("habit already added");
Long id = habit.getId();
if (id != null && getById(id) != null)
throw new RuntimeException("duplicate id");
if (id == null) habit.setId((long) list.size());
list.addLast(habit);
resort();
}
@Override
public synchronized Habit getById(long id)
{
for (Habit h : list)
{
if (h.getId() == null) throw new IllegalStateException();
if (h.getId() == id) return h;
}
return null;
}
@Override
public synchronized Habit getByUUID(String uuid)
{
for (Habit h : list) if (Objects.requireNonNull(h.getUuid()).equals(uuid)) return h;
return null;
}
@NonNull
@Override
public synchronized Habit getByPosition(int position)
{
return list.get(position);
}
@NonNull
@Override
public synchronized HabitList getFiltered(HabitMatcher matcher)
{
return new MemoryHabitList(matcher, comparator, this);
}
@NotNull
@Override
public synchronized Order getPrimaryOrder()
{
return primaryOrder;
}
@NotNull
@Override
public synchronized Order getSecondaryOrder()
{
return secondaryOrder;
}
@Override
public synchronized void setPrimaryOrder(@NonNull Order order)
{
this.primaryOrder = order;
this.comparator = getComposedComparatorByOrder(this.primaryOrder, this.secondaryOrder);
resort();
}
@Override
public void setSecondaryOrder(@NonNull Order order)
{
this.secondaryOrder = order;
this.comparator = getComposedComparatorByOrder(this.primaryOrder, this.secondaryOrder);
resort();
}
private Comparator<Habit> getComposedComparatorByOrder(Order firstOrder, Order secondOrder)
{
return (h1, h2) -> {
int firstResult = getComparatorByOrder(firstOrder).compare(h1, h2);
if (firstResult != 0 || secondOrder == null) {
return firstResult;
}
return getComparatorByOrder(secondOrder).compare(h1, h2);
};
}
private Comparator<Habit> getComparatorByOrder(Order order) {
Comparator<Habit> nameComparatorAsc = (h1, h2) ->
h1.getName().compareTo(h2.getName());
Comparator<Habit> nameComparatorDesc = (h1, h2) ->
nameComparatorAsc.compare(h2, h1);
Comparator<Habit> colorComparatorAsc = (h1, h2) ->
h1.getColor().compareTo(h2.getColor());
Comparator<Habit> colorComparatorDesc = (h1, h2) ->
colorComparatorAsc.compare(h2, h1);
Comparator<Habit> scoreComparatorDesc = (h1, h2) ->
{
Timestamp today = DateUtils.getTodayWithOffset();
return Double.compare(
h1.getScores().get(today).getValue(),
h2.getScores().get(today).getValue());
};
Comparator<Habit> scoreComparatorAsc = (h1, h2) ->
scoreComparatorDesc.compare(h2, h1);
Comparator<Habit> positionComparator = (h1, h2) ->
Integer.compare(h1.getPosition(), h2.getPosition());
Comparator<Habit> statusComparatorDesc = (h1, h2) ->
{
if (h1.isCompletedToday() != h2.isCompletedToday()) {
return h1.isCompletedToday() ? -1 : 1;
}
if (h1.isNumerical() != h2.isNumerical()) {
return h1.isNumerical() ? -1 : 1;
}
Timestamp today = DateUtils.getTodayWithOffset();
Integer v1 = h1.getComputedEntries().get(today).getValue();
Integer v2 = h2.getComputedEntries().get(today).getValue();
return v2.compareTo(v1);
};
Comparator<Habit> statusComparatorAsc = (h1, h2) -> statusComparatorDesc.compare(h2, h1);
if (order == BY_POSITION) return positionComparator;
if (order == BY_NAME_ASC) return nameComparatorAsc;
if (order == BY_NAME_DESC) return nameComparatorDesc;
if (order == BY_COLOR_ASC) return colorComparatorAsc;
if (order == BY_COLOR_DESC) return colorComparatorDesc;
if (order == BY_SCORE_DESC) return scoreComparatorDesc;
if (order == BY_SCORE_ASC) return scoreComparatorAsc;
if (order == BY_STATUS_DESC) return statusComparatorDesc;
if (order == BY_STATUS_ASC) return statusComparatorAsc;
throw new IllegalStateException();
}
@Override
public synchronized int indexOf(@NonNull Habit h)
{
return list.indexOf(h);
}
@NonNull
@Override
public synchronized Iterator<Habit> iterator()
{
return new ArrayList<>(list).iterator();
}
@Override
public synchronized void remove(@NonNull Habit habit)
{
throwIfHasParent();
list.remove(habit);
getObservable().notifyListeners();
}
@Override
public synchronized void reorder(@NonNull Habit from, @NonNull Habit to)
{
throwIfHasParent();
if (primaryOrder != BY_POSITION) throw new IllegalStateException(
"cannot reorder automatically sorted list");
if (indexOf(from) < 0) throw new IllegalArgumentException(
"list does not contain (from) habit");
int toPos = indexOf(to);
if (toPos < 0) throw new IllegalArgumentException(
"list does not contain (to) habit");
list.remove(from);
list.add(toPos, from);
int position = 0;
for(Habit h : list)
h.setPosition(position++);
getObservable().notifyListeners();
}
@Override
public synchronized int size()
{
return list.size();
}
@Override
public synchronized void update(List<Habit> habits)
{
resort();
}
private void throwIfHasParent()
{
if (parent != null) throw new IllegalStateException(
"Filtered lists cannot be modified directly. " +
"You should modify the parent list instead.");
}
private synchronized void loadFromParent()
{
if (parent == null) throw new IllegalStateException();
list.clear();
for (Habit h : parent) if (filter.matches(h)) list.add(h);
resort();
}
public synchronized void resort()
{
if (comparator != null) list.sort(comparator);
getObservable().notifyListeners();
}
}

@ -0,0 +1,233 @@
/*
* Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.core.models.memory
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.HabitMatcher
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
import java.util.ArrayList
import java.util.Comparator
import java.util.LinkedList
import java.util.Objects
/**
* In-memory implementation of [HabitList].
*/
class MemoryHabitList : HabitList {
private val list = LinkedList<Habit>()
@get:Synchronized
override var primaryOrder = Order.BY_POSITION
set(value) {
field = value
comparator = getComposedComparatorByOrder(primaryOrder, secondaryOrder)
resort()
}
@get:Synchronized
override var secondaryOrder = Order.BY_NAME_ASC
set(value) {
field = value
comparator = getComposedComparatorByOrder(primaryOrder, secondaryOrder)
resort()
}
private var comparator: Comparator<Habit>? =
getComposedComparatorByOrder(primaryOrder, secondaryOrder)
private var parent: MemoryHabitList? = null
constructor() : super()
constructor(
matcher: HabitMatcher,
comparator: Comparator<Habit>?,
parent: MemoryHabitList
) : super(matcher) {
this.parent = parent
this.comparator = comparator
primaryOrder = parent.primaryOrder
secondaryOrder = parent.secondaryOrder
parent.observable.addListener { loadFromParent() }
loadFromParent()
}
@Synchronized
@Throws(IllegalArgumentException::class)
override fun add(habit: Habit) {
throwIfHasParent()
require(!list.contains(habit)) { "habit already added" }
val id = habit.id
if (id != null && getById(id) != null) throw RuntimeException("duplicate id")
if (id == null) habit.id = list.size.toLong()
list.addLast(habit)
resort()
}
@Synchronized
override fun getById(id: Long): Habit? {
for (h in list) {
checkNotNull(h.id)
if (h.id == id) return h
}
return null
}
@Synchronized
override fun getByUUID(uuid: String?): Habit? {
for (h in list) if (Objects.requireNonNull(h.uuid) == uuid) return h
return null
}
@Synchronized
override fun getByPosition(position: Int): Habit {
return list[position]
}
@Synchronized
override fun getFiltered(matcher: HabitMatcher?): HabitList {
return MemoryHabitList(matcher!!, comparator, this)
}
private fun getComposedComparatorByOrder(
firstOrder: Order,
secondOrder: Order?
): Comparator<Habit> {
return Comparator { h1: Habit, h2: Habit ->
val firstResult = getComparatorByOrder(firstOrder).compare(h1, h2)
if (firstResult != 0 || secondOrder == null) {
return@Comparator firstResult
}
getComparatorByOrder(secondOrder).compare(h1, h2)
}
}
private fun getComparatorByOrder(order: Order): Comparator<Habit> {
val nameComparatorAsc = Comparator<Habit> { habit1, habit2 ->
habit1.name.compareTo(
habit2.name
)
}
val nameComparatorDesc =
Comparator { h1: Habit, h2: Habit -> nameComparatorAsc.compare(h2, h1) }
val colorComparatorAsc = Comparator<Habit> { (color1), (color2) ->
color1.compareTo(
color2
)
}
val colorComparatorDesc =
Comparator { h1: Habit, h2: Habit -> colorComparatorAsc.compare(h2, h1) }
val scoreComparatorDesc =
Comparator<Habit> { habit1, habit2 ->
val today = getTodayWithOffset()
habit1.scores[today].value.compareTo(habit2.scores[today].value)
}
val scoreComparatorAsc =
Comparator { h1: Habit, h2: Habit -> scoreComparatorDesc.compare(h2, h1) }
val positionComparator =
Comparator<Habit> { habit1, habit2 ->
habit1.position.compareTo(habit2.position)
}
val statusComparatorDesc = Comparator { h1: Habit, h2: Habit ->
if (h1.isCompletedToday() != h2.isCompletedToday()) {
return@Comparator if (h1.isCompletedToday()) -1 else 1
}
if (h1.isNumerical != h2.isNumerical) {
return@Comparator if (h1.isNumerical) -1 else 1
}
val today = getTodayWithOffset()
val v1 = h1.computedEntries.get(today).value
val v2 = h2.computedEntries.get(today).value
v2.compareTo(v1)
}
val statusComparatorAsc =
Comparator { h1: Habit, h2: Habit -> statusComparatorDesc.compare(h2, h1) }
if (order === Order.BY_POSITION) return positionComparator
if (order === Order.BY_NAME_ASC) return nameComparatorAsc
if (order === Order.BY_NAME_DESC) return nameComparatorDesc
if (order === Order.BY_COLOR_ASC) return colorComparatorAsc
if (order === Order.BY_COLOR_DESC) return colorComparatorDesc
if (order === Order.BY_SCORE_DESC) return scoreComparatorDesc
if (order === Order.BY_SCORE_ASC) return scoreComparatorAsc
if (order === Order.BY_STATUS_DESC) return statusComparatorDesc
if (order === Order.BY_STATUS_ASC) return statusComparatorAsc
throw IllegalStateException()
}
@Synchronized
override fun indexOf(h: Habit): Int {
return list.indexOf(h)
}
@Synchronized
override fun iterator(): Iterator<Habit> {
return ArrayList(list).iterator()
}
@Synchronized
override fun remove(h: Habit) {
throwIfHasParent()
list.remove(h)
observable.notifyListeners()
}
@Synchronized
override fun reorder(from: Habit, to: Habit) {
throwIfHasParent()
check(!(primaryOrder !== Order.BY_POSITION)) { "cannot reorder automatically sorted list" }
require(indexOf(from) >= 0) { "list does not contain (from) habit" }
val toPos = indexOf(to)
require(toPos >= 0) { "list does not contain (to) habit" }
list.remove(from)
list.add(toPos, from)
var position = 0
for (h in list) h.position = position++
observable.notifyListeners()
}
@Synchronized
override fun size(): Int {
return list.size
}
@Synchronized
override fun update(habits: List<Habit?>?) {
resort()
}
private fun throwIfHasParent() {
check(parent == null) {
"Filtered lists cannot be modified directly. " +
"You should modify the parent list instead."
}
}
@Synchronized
private fun loadFromParent() {
checkNotNull(parent)
list.clear()
for (h in parent!!) if (filter.matches(h)) list.add(h)
resort()
}
@Synchronized
override fun resort() {
if (comparator != null) list.sortWith(comparator!!)
observable.notifyListeners()
}
}
Loading…
Cancel
Save