source: trunk/gli/src/org/greenstone/gatherer/gui/metaaudit/Autofilter.java@ 7154

Last change on this file since 7154 was 6221, checked in by jmt12, 21 years ago

Cancelling the autofilter dialog no longer alternatively sets or clears a random filter

  • Property svn:keywords set to Author Date Id Revision
File size: 15.6 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * Author: John Thompson, Greenstone Digital Library, University of Waikato
9 *
10 * Copyright (C) 1999 New Zealand Digital Library Project
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *########################################################################
26 */
27package org.greenstone.gatherer.gui.metaaudit;
28
29/**************************************************************************************
30 * Title: Gatherer
31 * Description: The Gatherer: a tool for gathering and enriching a digital collection.
32 * Copyright: Copyright (c) 2001
33 * Company: The University of Waikato
34 * Written: /05/02
35 * Revised: 22/08/02 Revamped, Optimized and Commented.
36 * 13-08-03 Extended matching functionality to correctly handle the wildcard character
37 **************************************************************************************/
38
39import java.util.ArrayList;
40import org.greenstone.gatherer.util.StaticStrings;
41
42/** An Autofilter object stores the filters set on a single column, and provides a method for determining if a certain value passes the filter.
43 * @author John Thompson
44 * @version 2.3
45 */
46public class Autofilter {
47
48 static public void main(String[] args) {
49 if(args.length != 2) {
50 System.err.println("Usage: java Autofilter <string> <pattern>");
51 }
52 else {
53 switch(compareToPattern(args[0], args[1])) {
54 case LESS_THAN:
55 System.err.print(args[0] + " is less than");
56 break;
57 case GREATER_THAN:
58 System.err.print(args[0] + " is greater than");
59 break;
60 case EQUAL:
61 System.err.print(args[0] + " is equal to");
62 break;
63 default:
64 System.err.print(args[0] + " is something to");
65 }
66 System.err.println(" " + args[1]);
67 }
68 }
69
70 /** A String comparison which also recognises the WildCard character. The basic idea is that '*' matches zero or more of any characters, and if we limited patterns to be like "a*" then as soon as we hit the star we could return a match. The problem is that we want patterns like "b*y" which would match "boy" but not "bee" nor "bus" (but should return less than or greater than for each of these respectively). Thus our testing has to become non-deterministic so the matches are conducted like so:
71 * Given p = [ b * y ]
72 * Test s = [ b e e ] Result: p0=s0,{(p2>s1=>ERROR),(p1=s1,{(p1>s2=>ERROR),(p2>s2=>LESS_THAN)})} => LESS_THAN
73 * Test s = [ b o y ] Result: p0=s0,{(p2>s1=>ERROR),(p1=s1,{(p1>s2=>ERROR),(p2=s2=>EQUAL)})} => EQUAL
74 * Test s = [ b u s ] Result: p0=s0,{(p2>s1=>ERROR),(p1=s1,{(p1>s2=>ERROR),(p2<s2=>GREATER_THAN)})} => GREATER_THAN
75 * where the two () phrases within a {} represent two different 'threads' of processing. Errors are generated when one string runs out of characters before the other. Only non-error results are propogated. I've decided to implement this non-deterministic matching as a recursive function call.
76 * If you analyse it you can see there are three possible actions when a '*' is detected.
77 * 1. * is an empty string, ignore it in pattern and try to compare the next character in p with the current character in s. This case only occurs if there are more characters in p (otherwise apple would be greater than a*)
78 * 2. * matches exactly the current character in s, try to match the next character in p with the next character in s
79 * 3. * matches some string of characters including the current character in s, try to match '*' against the next character in s
80 * @param s the String being matched
81 * @param p the Pattern to match it against
82 * @return -1 if s is less than p, 0 if they are equal, 1 if s is greater than p
83 */
84 static public int compareToPattern(String s, String p) {
85 if(p.indexOf(StaticStrings.STAR_CHAR) == -1) {
86 return compareToPattern(s, p, 0, 0, false);
87 }
88 else {
89 return compareToPattern(s, p, 0, 0, true);
90 }
91 }
92
93 static final private int LESS_THAN = -1;
94 static final private int EQUAL = 0;
95 static final private int GREATER_THAN = 1;
96 static final private int ERROR = 42;
97
98 static private int compareToPattern(String s, String p, int s_index, int p_index, boolean wildcard_matching) {
99 // Lets do the simple cases first.
100 // Both out of characters at the same time
101 if(s_index >= s.length() && p_index >= p.length()) {
102 ///ystem.err.println("Both out of characters. Equal.");
103 return EQUAL;
104 }
105 // s out of characters first
106 if(s_index >= s.length()) {
107 // Not allowed to run out if matching wildcard
108 if(wildcard_matching) {
109 return ERROR;
110 }
111 // Remember that wildcard matches the empty string too, so as long as there are only '*' characters left in the pattern we can say they are equal as well.
112 else {
113 while(p_index < p.length()) {
114 if(p.charAt(p_index) == StaticStrings.STAR_CHAR) {
115 p_index++;
116 }
117 else {
118 ///ystem.err.println("S out of characters. Less Than.");
119 return LESS_THAN;
120 }
121 }
122 // We've run out of pattern and it only contained '*' so we're still equal
123 ///ystem.err.println("Both out of characters. Equal.");
124 return EQUAL;
125 }
126 }
127 // p out of characters first
128 if(p_index >= p.length()) {
129 // Not allowed to run out if matching wildcard
130 if(wildcard_matching) {
131 return ERROR;
132 }
133 // so it is greater than
134 else {
135 ///ystem.err.println("P out of characters. Greater Than.");
136 return GREATER_THAN;
137 }
138 }
139 char s_char = s.charAt(s_index);
140 char p_char = p.charAt(p_index);
141 ///ystem.err.println("Comparing " + s_char + " against pattern character " + p_char);
142 // Equivelent characters
143 // Now the tricky-dicky bit - WildCard matching.
144 if(p_char == StaticStrings.STAR_CHAR) {
145 int result;
146 // Case 1
147 ///ystem.err.println("WildCard Case 1");
148 if(p_index + 1 < p.length()) {
149 if((result = compareToPattern(s, p, s_index, p_index + 1, wildcard_matching)) != ERROR) {
150 return result;
151 }
152 }
153 // Case 2
154 ///ystem.err.println("WildCard Case 2");
155 if((result = compareToPattern(s, p, s_index + 1, p_index + 1, wildcard_matching)) != ERROR) {
156 return result;
157 }
158 // Case 3
159 ///ystem.err.println("WildCard Case 3");
160 if((result = compareToPattern(s, p, s_index + 1, p_index, wildcard_matching)) != ERROR) {
161 return result;
162 }
163 }
164 if(s_char == p_char) {
165 return compareToPattern(s, p, s_index + 1, p_index + 1, wildcard_matching);
166 }
167 // A preceeding character
168 if(s_char < p_char) {
169 ///ystem.err.println("s char is less than p char");
170 if(wildcard_matching) {
171 ///ystem.err.println("Because we are wildcard matching we still have to match the rest of s incase of errors.");
172 if(compareToPattern(s, p, s_index + 1, p_index + 1, wildcard_matching) != ERROR) {
173 ///ystem.err.println("No error. Less than.");
174 return LESS_THAN;
175 }
176 else {
177 ///ystem.err.println("Error detected.");
178 return ERROR;
179 }
180 }
181 else {
182 ///ystem.err.println("Less than.");
183 return LESS_THAN;
184 }
185 }
186 // A succeeding character
187 if(s_char > p_char) {
188 ///ystem.err.println("s char is greater than p char");
189 if(wildcard_matching) {
190 ///ystem.err.println("Because we are wildcard matching we still have to match the rest of s incase of errors.");
191 if(compareToPattern(s, p, s_index + 1, p_index + 1, wildcard_matching) != ERROR) {
192 ///ystem.err.println("No error. Greater than.");
193 return GREATER_THAN;
194 }
195 else {
196 ///ystem.err.println("Error detected.");
197 return ERROR;
198 }
199 }
200 else {
201 ///ystem.err.println("Greater than.");
202 return GREATER_THAN;
203 }
204 }
205 // Oh-No. Well, we'll just assume that string is less than pattern
206 return LESS_THAN;
207 }
208
209 /** <i>true</i> if the filter should be applied, <i>false</i> to indicate the filter is turned off. */
210 public boolean active;
211 /** <i>true</i> if the matching for the first expression should be case sensitive, <i>false</i> otherwise. */
212 public boolean casesense_one;
213 /** <i>true</i> if the matching for the second expression should be case sensitive, <i>false</i> otherwise. */
214 public boolean casesense_two;
215 /** Used to determine the operation intended when applying two filters, and set using the values of the OPERATION_TYPE enumeration. */
216 public boolean operation;
217 /** Used to determine how the column this filter is applied to should be sorted, and set using the values of the SORT_TYPE enumeration. */
218 public boolean sort;
219 /** The method to be used for the first filter expression, set from the values of the METHOD_LIST enumeration. */
220 public int method_one;
221 /** The method to be used for the second filter expression, set from the values of the METHOD_LIST enumeration. */
222 public int method_two;
223 /** The value to be matched against for the first expression. */
224 public String value_one;
225 /** The value to be matched against for the second expression. */
226 public String value_two;
227 /** An element of the SORT_TYPE enumeration, indicates lowest to highest value column ordering. */
228 public static final boolean ASCENDING = true;
229 /** An element of the OPERATION_TYPE enumeration, indicates that both filter expressions must be met (conjunction). */
230 public static final boolean AND = true;
231 /** An element of the SORT_TYPE enumeration, indicates highest to lowest value column ordering. */
232 public static final boolean DESCENDING = false;
233 /** An element of the OPERATION_TYPE enumeration, indicates that either (or both) filter expressions must be met (disjunction). */
234 public static final boolean OR = false;
235 /** An enumeration of symbolic names of various matching methods. */
236 public static final String METHOD_LIST[] = {"eqeq", "!eq", "<", "<eq", ">", ">eq", "^", "!^", "$", "!$", "?", "!?"};
237 /** Default Constructor. */
238 public Autofilter() {
239 operation = OR;
240 sort = ASCENDING;
241 }
242 /** Determine if this filter is currently active.
243 * @return <i>true</i> if it is active, <i>false</i> otherwise.
244 */
245 public boolean active() {
246 return active;
247 }
248 /** Determine if this list of values (for a certain cell) passes the filter.
249 * @param values An <strong>ArrayList</strong> of values sourced from a single cell in the associated column.
250 * @return <i>true</i> if the values match and should be displayed, <i>false</i> otherwise.
251 */
252 public boolean filter(ArrayList values) {
253 boolean result = false;
254 if(value_one != null) {
255 result = filter(values, method_one, value_one, casesense_one);
256 if(result) {
257 if(operation == AND && value_two != null) {
258 result = filter(values, method_two, value_two, casesense_two);
259 }
260 }
261 else if(operation == OR && value_two != null) {
262 result = filter(values, method_two, value_two, casesense_two);
263 }
264 }
265 return result;
266 }
267 /** Set the current activity state of this filter.
268 * @param active The new state of this filter, <i>true</i> to activate, <i>false</i> otherwise.
269 */
270 public void setActive(boolean active) {
271 this.active = active;
272 }
273 /** Set one of the filter expressions using the given information.
274 * @param number The number of the filter you wish to set as an <i>int</i>. Either 1 or 2.
275 * @param method An <i>int</i> indicating the method to be used when matching.
276 * @param value The <strong>String</strong> to be matched against.
277 * @param casesense <i>true</i> if this expression should be case sensitive, <i>false</i> otherwise.
278 */
279 public void setFilter(int number, int method, String value, boolean casesense) {
280 if(!casesense && value != null) {
281 value = value.toLowerCase();
282 }
283 if(number == 1) {
284 casesense_one = casesense;
285 method_one = method;
286 value_one = value;
287 }
288 else {
289 casesense_two = casesense;
290 method_two = method;
291 value_two = value;
292 }
293 }
294 /** Set the operation to be used to join the two filters (if a second filter is set).
295 * @param operation <i>true</i> for conjunct filters, <i>false</i> for disjunct.
296 */
297 public void setOperation(boolean operation) {
298 this.operation = operation;
299 }
300 /** Set the sort order of this column.
301 * @param sort <i>true</i> for ascending sort, <i>false</i> for descending.
302 */
303 public void setSort(boolean sort) {
304 this.sort = sort;
305 }
306 /** Decide whether a row should be displayed or filtered. The result depends on the selector set with setFilterType.
307 * @param values An <strong>ArrayList</strong> of values to be checked against the filter.
308 * @param method The method of matching to be used, as an <i>int</i>.
309 * @param target The <strong>String</Strong> to match against.
310 * @param casesense <i>true</i> if the match is to be case sensitive, <i>false</i> otherwise.
311 * @return <i>true</i> to display the row, <i>false</i> to hide it.
312 */
313 public boolean filter(ArrayList values, int method, String target, boolean casesense) {
314 boolean result = false;
315 // There are several special cases when the filter always returns turn, such as when the target is the wildcard character.
316 if(target == null || target.length() == 0 || target.equals("*")) {
317 result = true;
318 }
319 else {
320 // For each value in the list...
321 for(int i = 0; i < values.size(); i++) {
322 boolean pass;
323 String source;
324 // Account for case sensitivity.
325 if(casesense) {
326 source = values.get(i).toString();
327 }
328 else {
329 source = values.get(i).toString().toLowerCase();
330 }
331 ///ystem.err.println("Testing " + source + " against pattern " + target);
332 // Perform the match, based on the selected method.
333 switch(method) {
334 case 1: // !EQ
335 pass = (compareToPattern(source, target) != 0);
336 break;
337 case 2: // <
338 pass = (compareToPattern(source, target) < 0);
339 break;
340 case 3: // <eq
341 pass = (compareToPattern(source, target) <= 0);
342 break;
343 case 4: // >
344 pass = (compareToPattern(source, target) > 0);
345 break;
346 case 5: // >eq
347 pass = (compareToPattern(source, target) >= 0);
348 break;
349 case 6: // ^
350 pass = source.startsWith(target);
351 break;
352 case 7: // !^
353 pass = !(source.startsWith(target));
354 break;
355 case 8: // $
356 pass = source.endsWith(target);
357 break;
358 case 9: // !$
359 pass = !(source.endsWith(target));
360 break;
361 case 10: // ?
362 pass = (source.indexOf(target) != -1);
363 break;
364 case 11: // !?
365 pass = (source.indexOf(target) == -1);
366 break;
367 default: // EQEQ
368 pass = (compareToPattern(source, target) == 0);
369 break;
370 }
371 result = result || pass;
372 }
373 }
374 return result;
375 }
376 /** Produce a textual representation of this autofilter.
377 * @return A <strong>String</strong> displaying details of this autofilter.
378 */
379 public String toString() {
380 String result = "One: " + method_one + " - " + value_one + " - " + casesense_one;
381 if(value_two != null) {
382 result = result + "\n" + "Operation: " + (operation?"AND":"OR") + "\n";
383 result = result + "Two: " + method_two + " - " + value_two + " - " + casesense_two;
384 }
385 return result;
386 }
387}
Note: See TracBrowser for help on using the repository browser.