Simplify the PassClassifier

This commit is contained in:
ligi 2016-02-17 15:23:09 +01:00
parent 6206bcbb23
commit 62fa6a8941
8 changed files with 74 additions and 91 deletions

View file

@ -1,11 +1,12 @@
package org.ligi.passandroid.injections;
import android.support.annotation.Nullable;
import org.ligi.passandroid.model.FiledPass;
import org.ligi.passandroid.model.Pass;
import org.ligi.passandroid.model.PassClassifier;
import org.ligi.passandroid.model.PassStore;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
@ -40,6 +41,7 @@ public class FixedPassListPassStore implements PassStore {
}
@Override
@Nullable
public Pass getPassbookForId(String id) {
for (Pass pass : passes) {
if (pass.getId().equals(id)) {
@ -62,7 +64,7 @@ public class FixedPassListPassStore implements PassStore {
@Override
public PassClassifier getClassifier() {
if (passClassifier == null) {
passClassifier = new PassClassifier(new HashMap<String, Collection<String>>());
passClassifier = new PassClassifier(new HashMap<String, String>(), this);
}
return passClassifier;
}

View file

@ -27,8 +27,8 @@ public class AndroidFileSystemPassStore implements PassStore {
path = settings.getPassesDir();
refreshPassesList();
final File classificationFile = new File(settings.getStateDir(), "classification_state.json");
passClassifier = new FileBackedPassClassifier(classificationFile);
final File classificationFile = new File(settings.getStateDir(), "classifier_state.json");
passClassifier = new FileBackedPassClassifier(classificationFile, this);
}
@Override

View file

@ -5,7 +5,6 @@ import com.squareup.moshi.Moshi;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
@ -17,8 +16,8 @@ public class FileBackedPassClassifier extends PassClassifier {
private final JsonAdapter<Map> adapter;
private final File backed_file;
public FileBackedPassClassifier(final File backed_file) {
super(getBase(backed_file));
public FileBackedPassClassifier(final File backed_file, final PassStore passStore) {
super(getBase(backed_file), passStore);
this.backed_file = backed_file;
adapter = getAdapter();
@ -30,11 +29,11 @@ public class FileBackedPassClassifier extends PassClassifier {
}
@SuppressWarnings("unchecked")
private static Map<String, Collection<String>> getBase(final File backed_file) {
private static Map<String, String> getBase(final File backed_file) {
if (backed_file.exists()) {
try {
return (Map<String, Collection<String>>) getAdapter().fromJson(Okio.buffer(Okio.source(backed_file)));
return (Map<String, String>) getAdapter().fromJson(Okio.buffer(Okio.source(backed_file)));
} catch (IOException e) {
e.printStackTrace();
}
@ -71,7 +70,7 @@ public class FileBackedPassClassifier extends PassClassifier {
if (buffer != null) {
try {
adapter.toJson(buffer, pass_id_list_by_topic);
adapter.toJson(buffer, topic_by_id);
buffer.close();
} catch (IOException e) {
e.printStackTrace();

View file

@ -1,11 +1,9 @@
package org.ligi.passandroid.model;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArraySet;
public class PassClassifier {
@ -18,65 +16,36 @@ public class PassClassifier {
public final static String DEFAULT_TOPIC = "active";
protected final Map<String, Collection<String>> pass_id_list_by_topic;
private final Map<String, String> topic_by_id = new HashMap<>();
protected final Map<String, String> topic_by_id;
public PassClassifier(Map<String, Collection<String>> pass_id_list_by_topic) {
this.pass_id_list_by_topic = pass_id_list_by_topic;
private final PassStore passStore;
public PassClassifier(Map<String, String> topic_by_id, PassStore passStore) {
this.topic_by_id = topic_by_id;
this.passStore = passStore;
processDataChange();
}
public void processDataChange() {
calculateReverseMapping();
removeEmpty();
makeSureDefaultTopicExists();
}
private void calculateReverseMapping() {
topic_by_id.clear();
for (Map.Entry<String, Collection<String>> stringListEntry : pass_id_list_by_topic.entrySet()) {
final String topic = stringListEntry.getKey();
for (String id : stringListEntry.getValue()) {
topic_by_id.put(id, topic);
final Set<String> keysToRemove = new HashSet<>();
for (String key : topic_by_id.keySet()) {
if (passStore.getPassbookForId(key) == null) {
keysToRemove.add(key);
}
}
}
private void makeSureDefaultTopicExists() {
if (pass_id_list_by_topic.isEmpty()) {
pass_id_list_by_topic.put(DEFAULT_TOPIC, new TreeSet<String>());
}
}
private void removeEmpty() {
final Set<String> toRemove = new HashSet<>();
for (Map.Entry<String, Collection<String>> stringListEntry : pass_id_list_by_topic.entrySet()) {
if (stringListEntry.getValue().isEmpty()) {
toRemove.add(stringListEntry.getKey());
}
for (String key : keysToRemove) {
topic_by_id.remove(key);
}
for (String s : toRemove) {
pass_id_list_by_topic.remove(s);
}
}
public void moveToTopic(final Pass pass, final String newTopic) {
if (topic_by_id.containsKey(pass.getId())) {
final String oldTopic = topic_by_id.get(pass.getId());
final Collection<String> idsForOldTopic = pass_id_list_by_topic.get(oldTopic);
idsForOldTopic.remove(pass.getId());
if (idsForOldTopic.isEmpty()) {
pass_id_list_by_topic.remove(oldTopic);
}
}
upsertPassToTopic(pass, newTopic);
topic_by_id.put(pass.getId(), newTopic);
processDataChange();
@ -89,29 +58,25 @@ public class PassClassifier {
}
}
private void upsertPassToTopic(Pass pass, String newTopic) {
if (!pass_id_list_by_topic.containsKey(newTopic)) {
pass_id_list_by_topic.put(newTopic, new TreeSet<String>());
public Collection<String> getTopics() {
final Collection<String> res = new HashSet<>();
res.addAll(topic_by_id.values());
if (res.isEmpty()) {
res.add(DEFAULT_TOPIC);
}
final Collection<String> strings = pass_id_list_by_topic.get(newTopic);
if (!strings.contains(pass.getId())) {
strings.add(pass.getId());
processDataChange();
}
}
public String[] getTopics() {
final Set<String> strings = pass_id_list_by_topic.keySet();
return pass_id_list_by_topic.keySet().toArray(new String[strings.size()]);
return res;
}
public String getTopic(Pass pass) {
if(topic_by_id.containsKey(pass.getId())) {
if (topic_by_id.containsKey(pass.getId())) {
return topic_by_id.get(pass.getId());
}
upsertPassToTopic(pass,DEFAULT_TOPIC);
topic_by_id.put(pass.getId(),DEFAULT_TOPIC);
return DEFAULT_TOPIC;
}

View file

@ -10,6 +10,7 @@ public interface PassStore {
void refreshPassesList();
@Nullable
Pass getPassbookForId(String id);
boolean deletePassWithId(String id);

View file

@ -41,6 +41,8 @@ import org.ligi.snackengage.snacks.DefaultRateSnack;
import org.ligi.tracedroid.TraceDroid;
import org.ligi.tracedroid.sending.TraceDroidEmailSender;
import java.util.Collection;
import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;
@ -294,7 +296,8 @@ public class PassListActivity extends PassAndroidActivity implements PassClassif
@Override
public void notifyDataSetChanged() {
topic_array = passClassifier.getTopics();
final Collection<String> topics = passClassifier.getTopics();
topic_array = topics.toArray(new String[topics.size()]);
super.notifyDataSetChanged();
}

View file

@ -23,6 +23,8 @@ import org.ligi.passandroid.model.PassStore;
import org.ligi.passandroid.model.PassStoreProjection;
import org.ligi.passandroid.model.Settings;
import java.util.Collection;
import javax.inject.Inject;
import static android.support.v7.widget.helper.ItemTouchHelper.LEFT;
@ -96,7 +98,7 @@ public class PassListFragment extends Fragment implements OnClassificationChange
@Nullable
private String calculateNextTopic(final int swipeDir, final FiledPass pass) {
final String[] topics = passStore.getClassifier().getTopics();
final Collection<String> topics = passStore.getClassifier().getTopics();
switch (swipeDir) {
case LEFT:
@ -109,7 +111,7 @@ public class PassListFragment extends Fragment implements OnClassificationChange
}
@Nullable
private String getNextTopicRight(FiledPass pass, String[] topics) {
private String getNextTopicRight(FiledPass pass, Collection<String> topics) {
boolean nextIsCandidate = false;
@ -126,7 +128,7 @@ public class PassListFragment extends Fragment implements OnClassificationChange
@Nullable
private String getNextTopicLeft(FiledPass pass, String[] topics) {
private String getNextTopicLeft(FiledPass pass, Collection<String> topics) {
String prev = null;
for (String topic : topics) {

View file

@ -2,46 +2,57 @@ package org.ligi.passandroid.unittest;
import org.junit.Test;
import org.ligi.passandroid.model.FiledPass;
import org.ligi.passandroid.model.Pass;
import org.ligi.passandroid.model.PassClassifier;
import org.ligi.passandroid.model.PassImpl;
import org.mockito.internal.util.collections.Sets;
import org.ligi.passandroid.model.PassStore;
import java.util.Collection;
import java.util.HashMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ThePassClassifier {
public static final String ID_1 = "ID1";
public static final String TOPIC_1 = "topic1";
private PassStore getMockedPassStore() {
final PassStore mock = mock(PassStore.class);
when(mock.getPassbookForId(anyString())).thenReturn(mock(Pass.class));
return mock;
}
@Test
public void testThatPassIsInactiveByDefault() {
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>());
final PassClassifier tested = new PassClassifier(new HashMap<String, String>(), getMockedPassStore());
assertThat(tested.getTopic(getPassWithId(ID_1)).equals(PassClassifier.DEFAULT_TOPIC));
}
@Test
public void testThatOnlyNonDefaultTopicInTopicListWhenOnePassWithNonDefaultTopic() {
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>() {
final PassClassifier tested = new PassClassifier(new HashMap<String, String>() {
{
put(TOPIC_1, Sets.newSet(ID_1));
put(ID_1, TOPIC_1);
}
});
}, getMockedPassStore());
assertThat(tested.getTopics()).containsExactly(TOPIC_1);
}
@Test
public void testThatAfterMovingFromOnlyOneTopicToDefaultTopicOnly() {
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>() {
final PassClassifier tested = new PassClassifier(new HashMap<String, String>() {
{
put(TOPIC_1, Sets.newSet(ID_1));
put(ID_1, TOPIC_1);
}
});
}, getMockedPassStore());
tested.moveToTopic(getPassWithId(ID_1), PassClassifier.DEFAULT_TOPIC);
assertThat(tested.getTopics()).containsExactly(PassClassifier.DEFAULT_TOPIC);
@ -50,11 +61,11 @@ public class ThePassClassifier {
@Test
public void testThatTopicIsGoneAfterMove() {
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>() {
final PassClassifier tested = new PassClassifier(new HashMap<String, String>() {
{
put(TOPIC_1, Sets.newSet(ID_1));
put(ID_1, TOPIC_1);
}
});
}, getMockedPassStore());
tested.moveToTopic(getPassWithId(ID_1), PassClassifier.DEFAULT_TOPIC);
assertThat(tested.getTopics()).containsExactly(PassClassifier.DEFAULT_TOPIC);
@ -62,11 +73,11 @@ public class ThePassClassifier {
@Test
public void testThatPassIsInTopicAsExpected() {
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>() {
final PassClassifier tested = new PassClassifier(new HashMap<String, String>() {
{
put(TOPIC_1, Sets.newSet(ID_1));
put(ID_1, TOPIC_1);
}
});
}, getMockedPassStore());
assertThat(tested.getTopic(getPassWithId(ID_1)).equals(PassClassifier.DEFAULT_TOPIC));
}
@ -74,7 +85,7 @@ public class ThePassClassifier {
@Test
public void testHasAtLeastOneTopic() {
final PassClassifier tested = new PassClassifier(new HashMap<String, Collection<String>>());
final PassClassifier tested = new PassClassifier(new HashMap<String, String>(), getMockedPassStore());
assertThat(tested.getTopics()).containsExactly(PassClassifier.DEFAULT_TOPIC);
}