mirror of https://github.com/iSoron/uhabits.git
parent
a58cbffb81
commit
1cec5a6067
@ -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…
Reference in new issue