/* GNU General Public License CacheWolf is a software for PocketPC, Win and Linux that enables paperless caching. It supports the sites geocaching.com and opencaching.de Copyright (C) 2006 CacheWolf development team See http://www.cachewolf.de/ for more information. This program 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; version 2 of the License. This program 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, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package CacheWolf; import CacheWolf.database.CacheDB; import CacheWolf.database.CacheHolder; import CacheWolf.database.CacheSize; import CacheWolf.database.CacheType; import CacheWolf.utils.Common; import com.stevesoft.ewe_pat.Regex; import ewe.util.Hashtable; /** * Class that actually filters the cache database.<br> * The class that uses this filter must set the different public variables. * * @author BilboWolf (optimiert von salzkammergut) */ public class Filter { public static final int FILTER_INACTIVE = 0; public static final int FILTER_ACTIVE = 1; public static final int FILTER_CACHELIST = 2; public static final int FILTER_MARKED_ONLY = 3; /** Indicator whether a filter is inverted */ // public static boolean filterInverted=false; /** Indicator whether a filter is active. Used in status bar to indicate filter status */ // public static int filterActive=FILTER_INACTIVE; private static final int SMALLER = -1; private static final int EQUAL = 0; private static final int GREATER = 1; private static final int N = 1; private static final int NNE = 2; private static final int NE = 4; private static final int ENE = 8; private static final int E = 16; private static final int ESE = 32; private static final int SE = 64; private static final int SSE = 128; private static final int SSW = 256; private static final int SW = 512; private static final int WSW = 1024; private static final int W = 2048; private static final int WNW = 4096; private static final int NW = 8192; private static final int NNW = 16384; private static final int S = 32768; private static final int ROSE_ALL = N | NNE | NE | ENE | E | ESE | SE | SSE | SSW | SW | WSW | W | WNW | NW | NNW | S; private int distdirec = 0; private int diffdirec = 0; private int terrdirec = 0; String[] byVec; private int roseMatchPattern; private boolean hasRoseMatchPattern; private int typeMatchPattern; private boolean hasTypeMatchPattern; private int sizeMatchPattern; private boolean hasSizeMatchPattern; private boolean foundByMe; private boolean notFoundByMe; private String cacheStatus; private boolean useRegexp; private boolean filterNoCoord; private boolean ownedByMe; private boolean notOwnedByMe; double fscDist; double fscTerr; double fscDiff; private boolean archived = false; private boolean notArchived = false; private boolean premium = false; private boolean noPremium = false; private boolean solved = false; private boolean notSolved = false; private boolean available = false; private boolean notAvailable = false; double pi180 = java.lang.Math.PI / 180.0; private long[] attributesPattern = { 0l, 0l, 0l, 0l }; private int attributesChoice = 0; private String syncDate = ""; private String namePattern = ""; private int nameCompare = 0; private boolean nameCaseSensitive = true; /** * Set the filter from the filter data stored in the MainForm.profile (the filterscreen also updates the MainForm.profile) */ public void setFilter() { archived = MainForm.profile.getFilterVar().charAt(0) == '1'; available = MainForm.profile.getFilterVar().charAt(1) == '1'; foundByMe = MainForm.profile.getFilterVar().charAt(2) == '1'; ownedByMe = MainForm.profile.getFilterVar().charAt(3) == '1'; notArchived = MainForm.profile.getFilterVar().charAt(4) == '1'; notAvailable = MainForm.profile.getFilterVar().charAt(5) == '1'; notFoundByMe = MainForm.profile.getFilterVar().charAt(6) == '1'; notOwnedByMe = MainForm.profile.getFilterVar().charAt(7) == '1'; premium = MainForm.profile.getFilterVar().charAt(8) == '1'; noPremium = MainForm.profile.getFilterVar().charAt(9) == '1'; solved = MainForm.profile.getFilterVar().charAt(10) == '1'; notSolved = MainForm.profile.getFilterVar().charAt(11) == '1'; cacheStatus = MainForm.profile.getFilterStatus(); useRegexp = MainForm.profile.getFilterUseRegexp(); filterNoCoord = MainForm.profile.getFilterNoCoord(); typeMatchPattern = CacheType.Type_FilterString2Type_FilterPattern(MainForm.profile.getFilterType()); hasTypeMatchPattern = CacheType.hasTypeMatchPattern(typeMatchPattern); roseMatchPattern = 0; String filterRose = MainForm.profile.getFilterRose(); if (filterRose.charAt(0) == '1') roseMatchPattern |= NW; if (filterRose.charAt(1) == '1') roseMatchPattern |= NNW; if (filterRose.charAt(2) == '1') roseMatchPattern |= N; if (filterRose.charAt(3) == '1') roseMatchPattern |= NNE; if (filterRose.charAt(4) == '1') roseMatchPattern |= NE; if (filterRose.charAt(5) == '1') roseMatchPattern |= ENE; if (filterRose.charAt(6) == '1') roseMatchPattern |= E; if (filterRose.charAt(7) == '1') roseMatchPattern |= ESE; if (filterRose.charAt(8) == '1') roseMatchPattern |= SE; if (filterRose.charAt(9) == '1') roseMatchPattern |= SSE; if (filterRose.charAt(10) == '1') roseMatchPattern |= S; if (filterRose.charAt(11) == '1') roseMatchPattern |= SSW; if (filterRose.charAt(12) == '1') roseMatchPattern |= SW; if (filterRose.charAt(13) == '1') roseMatchPattern |= WSW; if (filterRose.charAt(14) == '1') roseMatchPattern |= W; if (filterRose.charAt(15) == '1') roseMatchPattern |= WNW; hasRoseMatchPattern = roseMatchPattern != ROSE_ALL; sizeMatchPattern = 0; String filterSize = MainForm.profile.getFilterSize(); if (filterSize.charAt(0) == '1') sizeMatchPattern |= CacheSize.CW_FILTER_MICRO; if (filterSize.charAt(1) == '1') sizeMatchPattern |= CacheSize.CW_FILTER_SMALL; if (filterSize.charAt(2) == '1') sizeMatchPattern |= CacheSize.CW_FILTER_NORMAL; if (filterSize.charAt(3) == '1') sizeMatchPattern |= CacheSize.CW_FILTER_LARGE; if (filterSize.charAt(4) == '1') sizeMatchPattern |= CacheSize.CW_FILTER_VERYLARGE; if (filterSize.charAt(5) == '1') sizeMatchPattern |= CacheSize.CW_FILTER_OTHER; if (filterSize.charAt(6) == '1') sizeMatchPattern |= CacheSize.CW_FILTER_NOTCHOSEN; if (filterSize.charAt(7) == '1') sizeMatchPattern |= CacheSize.CW_FILTER_VIRTUAL; hasSizeMatchPattern = sizeMatchPattern != CacheSize.CW_FILTER_ALL; distdirec = MainForm.profile.getFilterDist().charAt(0) == 'L' ? SMALLER : GREATER; fscDist = Common.parseDouble(MainForm.profile.getFilterDist().substring(1)); // Distance diffdirec = MainForm.profile.getFilterDiff().charAt(0) == 'L' ? SMALLER : (MainForm.profile.getFilterDiff().charAt(0) == '=' ? EQUAL : GREATER); fscDiff = Common.parseDouble(MainForm.profile.getFilterDiff().substring(1)); // Difficulty terrdirec = MainForm.profile.getFilterTerr().charAt(0) == 'L' ? SMALLER : (MainForm.profile.getFilterTerr().charAt(0) == '=' ? EQUAL : GREATER); fscTerr = Common.parseDouble(MainForm.profile.getFilterTerr().substring(1)); // Terrain attributesPattern = MainForm.profile.getFilterAttr(); attributesChoice = MainForm.profile.getFilterAttrChoice(); // items from search panel syncDate = MainForm.profile.getFilterSyncDate(); nameCaseSensitive = MainForm.profile.getFilterNameCaseSensitive(); if (nameCaseSensitive) namePattern = MainForm.profile.getFilterNamePattern(); else namePattern = MainForm.profile.getFilterNamePattern().toLowerCase(); nameCompare = MainForm.profile.getFilterNameCompare(); } /** * Apply the filter. Caches that match a criteria are flagged is_filtered = true. The table model is responsible for displaying or not displaying a cache that is filtered. */ public void doFilter() { CacheDB cacheDB = MainForm.profile.cacheDB; Hashtable examinedCaches; if (cacheDB.size() == 0) return; if (!hasFilter()) { // If the filter was completely reset, we can just // clear it clearFilter(); return; } MainForm.profile.selectionChanged = true; CacheHolder ch; examinedCaches = new Hashtable(cacheDB.size()); for (int i = cacheDB.size() - 1; i >= 0; i--) { ch = cacheDB.get(i); if (examinedCaches.containsKey(ch)) continue; boolean filterCache = excludedByFilter(ch); if (!filterCache && ch.mainCache != null && CacheType.hasMainTypeMatchPattern(typeMatchPattern)) { if (examinedCaches.containsKey(ch.mainCache)) { filterCache = ch.mainCache.isFiltered(); } else { ch.mainCache.setFiltered(excludedByFilter(ch.mainCache)); filterCache = ch.mainCache.isFiltered(); examinedCaches.put(ch.mainCache, null); } } ch.setFiltered(filterCache); } MainForm.profile.setFilterActive(FILTER_ACTIVE); examinedCaches = null; // MainForm.profile.hasUnsavedChanges=true; } public boolean excludedByFilter(CacheHolder ch) { // Match once against type pattern and once against rose pattern // Default is_filtered = false, means will be displayed! // If cache does not match type or rose pattern then is_filtered is set to true // and we proceed to next cache (no further tests needed) // Then we check the other filter criteria one by one: As soon as one is found that // eliminates the cache (i.e. sets is_filtered to true), we can skip the other tests // A cache is only displayed (i.e. is_filtered = false) if it meets all 9 filter criteria int cacheTypePattern; int cacheRosePattern; int cacheSizePattern; double dummyd1; boolean cacheFiltered = false; do { // ///////////////////////////// // Filter criterium 1: Cache type // ///////////////////////////// if (hasTypeMatchPattern) { // Only do the checks if we have a // filter cacheTypePattern = CacheType.getCacheTypePattern(ch.getType()); if ((cacheTypePattern & typeMatchPattern) == 0) { cacheFiltered = true; break; } } // ///////////////////////////// // Filter criterium 2: Bearing from centre // ///////////////////////////// // The optimal number of comparisons to identify one of 16 objects is 4 (=log2(16)) // By using else if we can reduce the number of comparisons from 16 to just over 8 // By first checking the first letter, we can reduce the average number further to // just under 5 if (hasRoseMatchPattern) { if (ch.getBearing().startsWith("N")) { if (ch.getBearing().equals("NW")) cacheRosePattern = NW; else if (ch.getBearing().equals("NNW")) cacheRosePattern = NNW; else if (ch.getBearing().equals("N")) cacheRosePattern = N; else if (ch.getBearing().equals("NNE")) cacheRosePattern = NNE; else cacheRosePattern = NE; } else if (ch.getBearing().startsWith("E")) { if (ch.getBearing().equals("ENE")) cacheRosePattern = ENE; else if (ch.getBearing().equals("E")) cacheRosePattern = E; else cacheRosePattern = ESE; } else if (ch.getBearing().startsWith("S")) { if (ch.getBearing().equals("SW")) cacheRosePattern = SW; else if (ch.getBearing().equals("SSW")) cacheRosePattern = SSW; else if (ch.getBearing().equals("S")) cacheRosePattern = S; else if (ch.getBearing().equals("SSE")) cacheRosePattern = SSE; else cacheRosePattern = SE; } else { if (ch.getBearing().equals("WNW")) cacheRosePattern = WNW; else if (ch.getBearing().equals("W")) cacheRosePattern = W; else if (ch.getBearing().equals("WSW")) cacheRosePattern = WSW; else cacheRosePattern = 0; } if ((cacheRosePattern != 0) && ((cacheRosePattern & roseMatchPattern) == 0)) { cacheFiltered = true; break; } } // ///////////////////////////// // Filter criterium 3: Distance // ///////////////////////////// if (fscDist > 0.0) { dummyd1 = ch.kilom; if (distdirec == SMALLER && dummyd1 > fscDist) { cacheFiltered = true; break; } if (distdirec == GREATER && dummyd1 < fscDist) { cacheFiltered = true; break; } } // ///////////////////////////// // Filter criterium 4: Difficulty // ///////////////////////////// if (fscDiff > 0.0) { dummyd1 = ch.getDifficulty() / 10D; if (diffdirec == SMALLER && dummyd1 > fscDiff) { cacheFiltered = true; break; } if (diffdirec == EQUAL && dummyd1 != fscDiff) { cacheFiltered = true; break; } if (diffdirec == GREATER && dummyd1 < fscDiff) { cacheFiltered = true; break; } } // ///////////////////////////// // Filter criterium 5: Terrain // ///////////////////////////// if (fscTerr > 0.0) { dummyd1 = ch.getTerrain() / 10D; if (terrdirec == SMALLER && dummyd1 > fscTerr) { cacheFiltered = true; break; } if (terrdirec == EQUAL && dummyd1 != fscTerr) { cacheFiltered = true; break; } if (terrdirec == GREATER && dummyd1 < fscTerr) { cacheFiltered = true; break; } } // ///////////////////////////// // Filter criterium 6: Found by me // ///////////////////////////// if ((ch.isFound() && !foundByMe) || (!ch.isFound() && !notFoundByMe)) { cacheFiltered = true; break; } // ///////////////////////////// // Filter criterium 7: Owned by me // ///////////////////////////// if ((ch.isOwned() && !ownedByMe) || (!ch.isOwned() && !notOwnedByMe)) { cacheFiltered = true; break; } // ///////////////////////////// // Filter criterium 8: Archived // ///////////////////////////// if ((ch.isArchived() && !archived) || (!ch.isArchived() && !notArchived)) { cacheFiltered = true; break; } // ///////////////////////////// // Filter criterium 9: Unavailable // ///////////////////////////// if ((ch.isAvailable() && !available) || (!ch.isAvailable() && !notAvailable)) { cacheFiltered = true; break; } if ((ch.isPremiumCache() && !premium) || (!ch.isPremiumCache() && !noPremium)) { cacheFiltered = true; break; } if ((ch.isSolved() && !solved) || (!ch.isSolved() && !notSolved)) { cacheFiltered = true; break; } // ///////////////////////////// // Filter criterium 10: Size // ///////////////////////////// if (hasSizeMatchPattern) { cacheSizePattern = CacheSize.getFilterPattern(ch.getSize()); if ((cacheSizePattern & sizeMatchPattern) == 0) { cacheFiltered = true; break; } } // ///////////////////////////// // Filter criterium 11: Attributes // ///////////////////////////// if ((attributesPattern[0] != 0 || attributesPattern[1] != 0 || attributesPattern[2] != 0 || attributesPattern[3] != 0) && ch.mainCache == null) { long[] chAtts = ch.getAttributesBits(); if (attributesChoice == 0) { // AND-condition: if ((chAtts[0] & attributesPattern[0]) != attributesPattern[0] || // (chAtts[1] & attributesPattern[1]) != attributesPattern[1] || // (chAtts[2] & attributesPattern[2]) != attributesPattern[2] || // (chAtts[3] & attributesPattern[3]) != attributesPattern[3] // ) { cacheFiltered = true; break; } } else if (attributesChoice == 1) { // OR-condition: if ((chAtts[0] & attributesPattern[0]) == 0 && // (chAtts[1] & attributesPattern[1]) == 0 && // (chAtts[2] & attributesPattern[2]) == 0 && // (chAtts[3] & attributesPattern[3]) == 0 // ) { cacheFiltered = true; break; } } else { // NOT-condition: if ((chAtts[0] & attributesPattern[0]) != 0 || // (chAtts[1] & attributesPattern[1]) != 0 || // (chAtts[2] & attributesPattern[2]) != 0 || // (chAtts[3] & attributesPattern[3]) != 0 // ) { cacheFiltered = true; break; } } } // ///////////////////////////// // Filter criterium 12: Status // ///////////////////////////// if (!cacheStatus.equals("")) { if (!useRegexp) { if (ch.getStatusText().toLowerCase().indexOf(cacheStatus.toLowerCase()) < 0) { cacheFiltered = true; break; } } else { Regex rex = new Regex(cacheStatus.toLowerCase()); rex.search(ch.getStatusText().toLowerCase()); if (rex.stringMatched() == null) { cacheFiltered = true; break; } } } // ///////////////////////////// // Filter criterium 13: NoCoord // ///////////////////////////// if (!filterNoCoord && !ch.getWpt().isValid()) { cacheFiltered = true; break; } // ///////////////////////////// // Filter criterium 14: Search // ///////////////////////////// if (!syncDate.equals("")) { if (syncDate.length() >= 10) { // First sign is <, =, >, followed by '-' and then yyyymmdd String theOperator = syncDate.substring(0, 1); String theDate = syncDate.substring(2, 10); String cacheSyncDate = ch.getLastSync(); if (cacheSyncDate.length() >= 8) { // time will not be taken into account cacheSyncDate = cacheSyncDate.substring(0, 8); int diff = theDate.compareTo(cacheSyncDate); if (theOperator.equals("<")) { if (diff <= 0) { cacheFiltered = true; break; } } else if (theOperator.equals("=")) { if (diff != 0) { cacheFiltered = true; break; } } else { if (diff >= 0) { cacheFiltered = true; break; } } } } } if (namePattern.length() > 0) { String cacheName; if (nameCaseSensitive) cacheName = ch.getName(); else cacheName = ch.getName().toLowerCase(); if (nameCompare == 0) { if (!cacheName.startsWith(namePattern)) { cacheFiltered = true; break; } } else if (nameCompare == 1) { if (cacheName.indexOf(namePattern) < 0) { cacheFiltered = true; break; } } else if (nameCompare == 2) { if (!cacheName.endsWith(namePattern)) { cacheFiltered = true; break; } } else if (nameCompare == 3) { if (cacheName.indexOf(namePattern) >= 0) { cacheFiltered = true; break; } } } break; } while (true); return cacheFiltered; } /** * Switches flag to invert filter property. */ public void invertFilter() { MainForm.profile.setFilterInverted(!MainForm.profile.isFilterInverted()); } /** * Clear the is_filtered flag from the cache database. */ public void clearFilter() { MainForm.profile.selectionChanged = true; CacheDB cacheDB = MainForm.profile.cacheDB; for (int i = cacheDB.size() - 1; i >= 0; i--) { CacheHolder ch = cacheDB.get(i); ch.setFiltered(false); } MainForm.profile.setFilterActive(FILTER_INACTIVE); } public boolean hasFilter() { long[] attribs = MainForm.profile.getFilterAttr(); return !(MainForm.profile.getFilterType().equals(FilterData.FILTERTYPE) && MainForm.profile.getFilterRose().equals(FilterData.FILTERROSE) && MainForm.profile.getFilterVar().equals(FilterData.FILTERVAR) && MainForm.profile.getFilterSize().equals(FilterData.FILTERSIZE) && MainForm.profile.getFilterDist().equals("L") && MainForm.profile.getFilterDiff().equals("L") && MainForm.profile.getFilterTerr().equals("L") && attribs[0] == 0l && attribs[1] == 0l && attribs[2] == 0l && attribs[3] == 0l && MainForm.profile.getFilterStatus().equals("") && MainForm.profile.getFilterSyncDate().equals("") && MainForm.profile.getFilterNamePattern().equals("") && MainForm.profile .getFilterNoCoord()); } }