package org.commcare.adapters; import android.app.Activity; import net.sqlcipher.database.SQLiteDatabase; import org.commcare.CommCareApplication; import org.commcare.cases.entity.Entity; import org.commcare.cases.entity.NodeEntityFactory; import org.commcare.cases.util.StringUtils; import org.commcare.modern.util.Pair; import org.commcare.utils.SessionUnavailableException; import org.javarosa.core.model.instance.TreeReference; import org.javarosa.core.services.Logger; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; /** * Filter entity list via all string-representable entity fields */ public class EntityStringFilterer extends EntityFiltererBase { private final boolean isFilterEmpty; private final String[] searchTerms; private final ArrayList<Pair<Integer, Integer>> matchScores = new ArrayList<>(); private final boolean isAsyncMode; private final boolean isFuzzySearchEnabled; public EntityStringFilterer(EntityListAdapter adapter, String[] searchTerms, boolean isAsyncMode, boolean isFuzzySearchEnabled, NodeEntityFactory nodeFactory, List<Entity<TreeReference>> fullEntityList, Activity context) { super(context, nodeFactory, adapter, fullEntityList); this.isAsyncMode = isAsyncMode; this.isFuzzySearchEnabled = isFuzzySearchEnabled; this.isFilterEmpty = searchTerms == null || searchTerms.length == 0; this.searchTerms = searchTerms; if (isFilterEmpty) { matchList.addAll(fullEntityList); } } @Override protected void filter() { long startTime = System.currentTimeMillis(); if (!isFilterEmpty) { buildMatchList(); } if (isCancelled()) { return; } long time = System.currentTimeMillis() - startTime; if (time > 1000) { Logger.log("cache", "Presumably finished caching new entities, time taken: " + time + "ms"); } } private void buildMatchList() { Locale currentLocale = Locale.getDefault(); //It's a bit sketchy here, because this DB lock will prevent //anything else from processing SQLiteDatabase db; try { db = CommCareApplication.instance().getUserDbHandle(); } catch (SessionUnavailableException e) { this.cancelSearch(); return; } db.beginTransaction(); for (int index = 0; index < fullEntityList.size(); ++index) { //Every once and a while we should make sure we're not blocking anything with the database if (index % 500 == 0) { db.yieldIfContendedSafely(); } Entity<TreeReference> e = fullEntityList.get(index); if (isCancelled()) { db.setTransactionSuccessful(); db.endTransaction(); return; } boolean add = false; int score = 0; filter: for (String filter : searchTerms) { add = false; for (int i = 0; i < e.getNumFields(); ++i) { String field = e.getNormalizedField(i); if (!"".equals(field) && field.toLowerCase(currentLocale).contains(filter)) { add = true; continue filter; } else if (isFuzzySearchEnabled) { // We possibly now want to test for edit distance for // fuzzy matching for (String fieldChunk : e.getSortFieldPieces(i)) { Pair<Boolean, Integer> match = StringUtils.fuzzyMatch(filter, fieldChunk); if (match.first) { add = true; score += match.second; continue filter; } } } } if (!add) { break; } } if (add) { matchScores.add(Pair.create(index, score)); } } if (isAsyncMode) { Collections.sort(matchScores, new Comparator<Pair<Integer, Integer>>() { @Override public int compare(Pair<Integer, Integer> lhs, Pair<Integer, Integer> rhs) { return lhs.second - rhs.second; } }); } for (Pair<Integer, Integer> match : matchScores) { matchList.add(fullEntityList.get(match.first)); } db.setTransactionSuccessful(); db.endTransaction(); } }