001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.ui.predictive;
038:
039: import java.util.List;
040: import java.util.ArrayList;
041: import java.util.Collections;
042: import java.util.regex.Pattern;
043: import java.util.regex.Matcher;
044: import java.util.regex.PatternSyntaxException;
045:
046: /** Model class for predictive string input. */
047: public class PredictiveInputModel<T extends Comparable<? super T>> {
048:
049: /** Strategy used for matching and mask extension. */
050: public static interface MatchingStrategy<X extends Comparable<? super X>> {
051:
052: /** Returns true if the item is a match.
053: * @param item item to check
054: * @param pim predictive input model
055: * @return true if the item is a match
056: */
057: public boolean isMatch(X item, PredictiveInputModel<X> pim);
058:
059: /** Returns true if the item is perfect a match.
060: * @param item item to check
061: * @param pim predictive input model
062: * @return true if the item is a match
063: */
064: public boolean isPerfectMatch(X item,
065: PredictiveInputModel<X> pim);
066:
067: /** Returns true if the two items are equivalent under this matching strategy.
068: * @param item1 first item
069: * @param item2 second item
070: * @param pim predictive input model
071: * @return true if equivalent
072: */
073: public boolean equivalent(X item1, X item2,
074: PredictiveInputModel<X> pim);
075:
076: /** Compare the two items and return -1, 0, or 1 if item1 is less than, equal to, or greater than item2.
077: * @param item1 first item
078: * @param item2 second item
079: * @param pim predictive input model
080: * @return -1, 0, or 1
081: */
082: public int compare(X item1, X item2, PredictiveInputModel<X> pim);
083:
084: /** Returns the item from the list that is the longest match.
085: * @param item target item
086: * @param items list with items
087: * @param pim predictive input model
088: * @return longest match
089: */
090: public X getLongestMatch(X item, List<X> items,
091: PredictiveInputModel<X> pim);
092:
093: /** Returns the shared mask extension for the list of items.
094: * @param items items for which the mask extension should be generated
095: * @param pim predictive input model
096: * @return the shared mask extension
097: */
098: public String getSharedMaskExtension(List<X> items,
099: PredictiveInputModel<X> pim);
100:
101: /** Returns the mask extended by the shared extension.
102: * @param items items for which the mask extension should be generated
103: * @param pim predictive input model
104: * @return the extended shared mask
105: */
106: public String getExtendedSharedMask(List<X> items,
107: PredictiveInputModel<X> pim);
108:
109: /** Force the mask to fit this entry. The matching strategies that accept line numbers
110: * can combine the current item with the line number. Other strategies just return the
111: * current item.
112: * @return forced string
113: */
114: public String force(X item, String mask);
115: }
116:
117: /** Matching based on string prefix. */
118: public static class PrefixStrategy<X extends Comparable<? super X>>
119: implements MatchingStrategy<X> {
120: public String toString() {
121: return "Prefix";
122: }
123:
124: public boolean isMatch(X item, PredictiveInputModel<X> pim) {
125: String a = (pim._ignoreCase) ? (item.toString()
126: .toLowerCase()) : (item.toString());
127: String b = (pim._ignoreCase) ? (pim._mask.toLowerCase())
128: : (pim._mask);
129: return a.startsWith(b);
130: }
131:
132: public boolean isPerfectMatch(X item,
133: PredictiveInputModel<X> pim) {
134: String a = (pim._ignoreCase) ? (item.toString()
135: .toLowerCase()) : (item.toString());
136: String b = (pim._ignoreCase) ? (pim._mask.toLowerCase())
137: : (pim._mask);
138: return a.equals(b);
139: }
140:
141: public boolean equivalent(X item1, X item2,
142: PredictiveInputModel<X> pim) {
143: String a = (pim._ignoreCase) ? (item1.toString()
144: .toLowerCase()) : (item1.toString());
145: String b = (pim._ignoreCase) ? (item2.toString()
146: .toLowerCase()) : (item2.toString());
147: return a.equals(b);
148: }
149:
150: public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
151: String a = (pim._ignoreCase) ? (item1.toString()
152: .toLowerCase()) : (item1.toString());
153: String b = (pim._ignoreCase) ? (item2.toString()
154: .toLowerCase()) : (item2.toString());
155: return a.compareTo(b);
156: }
157:
158: public X getLongestMatch(X item, List<X> items,
159: PredictiveInputModel<X> pim) {
160: X longestMatch = null;
161: int matchLength = -1;
162: for (X i : items) {
163: String s = (pim._ignoreCase) ? (i.toString()
164: .toLowerCase()) : (i.toString());
165: String t = (pim._ignoreCase) ? (item.toString()
166: .toLowerCase()) : (item.toString());
167: int ml = 0;
168: while ((s.length() > ml) && (t.length() > ml)
169: && (s.charAt(ml) == t.charAt(ml))) {
170: ++ml;
171: }
172: if (ml > matchLength) {
173: matchLength = ml;
174: longestMatch = i;
175: }
176: }
177: return longestMatch;
178: }
179:
180: public String getSharedMaskExtension(List<X> items,
181: PredictiveInputModel<X> pim) {
182: String res = "";
183: String ext = "";
184: if (items.size() == 0) {
185: return ext;
186: }
187: boolean allMatching = true;
188: int len = pim._mask.length();
189: while ((allMatching)
190: && (pim._mask.length() + ext.length() < items
191: .get(0).toString().length())) {
192: char origCh = items.get(0).toString().charAt(
193: pim._mask.length() + ext.length());
194: char ch = (pim._ignoreCase) ? (Character
195: .toLowerCase(origCh)) : (origCh);
196: allMatching = true;
197: for (X i : items) {
198: String a = (pim._ignoreCase) ? (i.toString()
199: .toLowerCase()) : (i.toString());
200: if (a.charAt(len) != ch) {
201: allMatching = false;
202: break;
203: }
204: }
205: if (allMatching) {
206: ext = ext + ch;
207: res = res + origCh;
208: ++len;
209: }
210: }
211: return res;
212: }
213:
214: public String getExtendedSharedMask(List<X> items,
215: PredictiveInputModel<X> pim) {
216: return pim._mask + getSharedMaskExtension(items, pim);
217: }
218:
219: public String force(X item, String mask) {
220: return item.toString();
221: }
222: };
223:
224: /** Matching based on string fragments. */
225: public static class FragmentStrategy<X extends Comparable<? super X>>
226: implements MatchingStrategy<X> {
227: public String toString() {
228: return "Fragments";
229: }
230:
231: public boolean isMatch(X item, PredictiveInputModel<X> pim) {
232: String a = (pim._ignoreCase) ? (item.toString()
233: .toLowerCase()) : (item.toString());
234: String b = (pim._ignoreCase) ? (pim._mask.toLowerCase())
235: : (pim._mask);
236:
237: java.util.StringTokenizer tok = new java.util.StringTokenizer(
238: b);
239: while (tok.hasMoreTokens()) {
240: if (a.indexOf(tok.nextToken()) < 0)
241: return false;
242: }
243: return true;
244: }
245:
246: public boolean isPerfectMatch(X item,
247: PredictiveInputModel<X> pim) {
248: String a = (pim._ignoreCase) ? (item.toString()
249: .toLowerCase()) : (item.toString());
250: String b = (pim._ignoreCase) ? (pim._mask.toLowerCase())
251: : (pim._mask);
252: return a.equals(b);
253: }
254:
255: public boolean equivalent(X item1, X item2,
256: PredictiveInputModel<X> pim) {
257: String a = (pim._ignoreCase) ? (item1.toString()
258: .toLowerCase()) : (item1.toString());
259: String b = (pim._ignoreCase) ? (item2.toString()
260: .toLowerCase()) : (item2.toString());
261: return a.equals(b);
262: }
263:
264: public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
265: String a = (pim._ignoreCase) ? (item1.toString()
266: .toLowerCase()) : (item1.toString());
267: String b = (pim._ignoreCase) ? (item2.toString()
268: .toLowerCase()) : (item2.toString());
269: return a.compareTo(b);
270: }
271:
272: public X getLongestMatch(X item, List<X> items,
273: PredictiveInputModel<X> pim) {
274: if (items.size() > 0)
275: return items.get(0);
276: else
277: return null;
278: }
279:
280: public String getSharedMaskExtension(List<X> items,
281: PredictiveInputModel<X> pim) {
282: return ""; // can't thing of a good way
283: }
284:
285: public String getExtendedSharedMask(List<X> items,
286: PredictiveInputModel<X> pim) {
287: return pim._mask;
288: }
289:
290: public String force(X item, String mask) {
291: return item.toString();
292: }
293: };
294:
295: /** Matching based on string regular expressions. */
296: public static class RegExStrategy<X extends Comparable<? super X>>
297: implements MatchingStrategy<X> {
298: public String toString() {
299: return "RegEx";
300: }
301:
302: public boolean isMatch(X item, PredictiveInputModel<X> pim) {
303: String a = item.toString();
304:
305: try {
306: Pattern p = Pattern.compile(pim._mask,
307: (pim._ignoreCase) ? (Pattern.CASE_INSENSITIVE)
308: : (0));
309: Matcher m = p.matcher(a);
310: return m.matches();
311: } catch (PatternSyntaxException e) {
312: return false;
313: }
314: }
315:
316: public boolean isPerfectMatch(X item,
317: PredictiveInputModel<X> pim) {
318: String a = (pim._ignoreCase) ? (item.toString()
319: .toLowerCase()) : (item.toString());
320: String b = (pim._ignoreCase) ? (pim._mask.toLowerCase())
321: : (pim._mask);
322: return a.equals(b);
323: }
324:
325: public boolean equivalent(X item1, X item2,
326: PredictiveInputModel<X> pim) {
327: String a = (pim._ignoreCase) ? (item1.toString()
328: .toLowerCase()) : (item1.toString());
329: String b = (pim._ignoreCase) ? (item2.toString()
330: .toLowerCase()) : (item2.toString());
331: return a.equals(b);
332: }
333:
334: public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
335: String a = (pim._ignoreCase) ? (item1.toString()
336: .toLowerCase()) : (item1.toString());
337: String b = (pim._ignoreCase) ? (item2.toString()
338: .toLowerCase()) : (item2.toString());
339: return a.compareTo(b);
340: }
341:
342: public X getLongestMatch(X item, List<X> items,
343: PredictiveInputModel<X> pim) {
344: if (items.size() > 0)
345: return items.get(0); // can't thing of a good way
346: else
347: return null;
348: }
349:
350: public String getSharedMaskExtension(List<X> items,
351: PredictiveInputModel<X> pim) {
352: return ""; // can't thing of a good way
353: }
354:
355: public String getExtendedSharedMask(List<X> items,
356: PredictiveInputModel<X> pim) {
357: return pim._mask;
358: }
359:
360: public String force(X item, String mask) {
361: return item.toString();
362: }
363: };
364:
365: /** Matching based on string prefix, supporting line numbers separated by :. */
366: public static class PrefixLineNumStrategy<X extends Comparable<? super X>>
367: implements MatchingStrategy<X> {
368: public String toString() {
369: return "Prefix";
370: }
371:
372: public boolean isMatch(X item, PredictiveInputModel<X> pim) {
373: int posB = pim._mask.lastIndexOf(':');
374: if (posB < 0) {
375: posB = pim._mask.length();
376: }
377: String mask = pim._mask.substring(0, posB);
378:
379: String a = (pim._ignoreCase) ? (item.toString()
380: .toLowerCase()) : (item.toString());
381: String b = (pim._ignoreCase) ? (mask.toLowerCase())
382: : (mask);
383: return a.startsWith(b);
384: }
385:
386: public boolean isPerfectMatch(X item,
387: PredictiveInputModel<X> pim) {
388: int posB = pim._mask.lastIndexOf(':');
389: if (posB < 0) {
390: posB = pim._mask.length();
391: }
392: String mask = pim._mask.substring(0, posB);
393:
394: String a = (pim._ignoreCase) ? (item.toString()
395: .toLowerCase()) : (item.toString());
396: String b = (pim._ignoreCase) ? (mask.toLowerCase())
397: : (mask);
398: return a.equals(b);
399: }
400:
401: public boolean equivalent(X item1, X item2,
402: PredictiveInputModel<X> pim) {
403: int posA = item1.toString().lastIndexOf(':');
404: if (posA < 0) {
405: posA = item1.toString().length();
406: }
407: String i1 = item1.toString().substring(0, posA);
408:
409: int posB = item2.toString().lastIndexOf(':');
410: if (posB < 0) {
411: posB = item2.toString().length();
412: }
413: String i2 = item2.toString().substring(0, posB);
414:
415: String a = (pim._ignoreCase) ? (i1.toLowerCase()) : (i1);
416: String b = (pim._ignoreCase) ? (i2.toLowerCase()) : (i2);
417: return a.equals(b);
418: }
419:
420: public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
421: int posA = item1.toString().lastIndexOf(':');
422: if (posA < 0) {
423: posA = item1.toString().length();
424: }
425: String i1 = item1.toString().substring(0, posA);
426:
427: int posB = item2.toString().lastIndexOf(':');
428: if (posB < 0) {
429: posB = item2.toString().length();
430: }
431: String i2 = item2.toString().substring(0, posB);
432:
433: String a = (pim._ignoreCase) ? (i1.toLowerCase()) : (i1);
434: String b = (pim._ignoreCase) ? (i2.toLowerCase()) : (i2);
435: return a.compareTo(b);
436: }
437:
438: public X getLongestMatch(X item, List<X> items,
439: PredictiveInputModel<X> pim) {
440: X longestMatch = null;
441: int matchLength = -1;
442: for (X i : items) {
443: int posA = i.toString().lastIndexOf(':');
444: if (posA < 0) {
445: posA = i.toString().length();
446: }
447: String i1 = i.toString().substring(0, posA);
448:
449: int posB = item.toString().lastIndexOf(':');
450: if (posB < 0) {
451: posB = item.toString().length();
452: }
453: String i2 = item.toString().substring(0, posB);
454:
455: String s = (pim._ignoreCase) ? (i1.toLowerCase())
456: : (i1);
457: String t = (pim._ignoreCase) ? (i2.toLowerCase())
458: : (i2);
459: int ml = 0;
460: while ((s.length() > ml) && (t.length() > ml)
461: && (s.charAt(ml) == t.charAt(ml))) {
462: ++ml;
463: }
464: if (ml > matchLength) {
465: matchLength = ml;
466: longestMatch = i;
467: }
468: }
469: return longestMatch;
470: }
471:
472: public String getSharedMaskExtension(List<X> items,
473: PredictiveInputModel<X> pim) {
474: String res = "";
475: String ext = "";
476: if (items.size() == 0) {
477: return ext;
478: }
479:
480: int posB = pim._mask.lastIndexOf(':');
481: if (posB < 0) {
482: posB = pim._mask.length();
483: }
484: String mask = pim._mask.substring(0, posB);
485:
486: boolean allMatching = true;
487: int len = mask.length();
488: while ((allMatching)
489: && (mask.length() + ext.length() < items.get(0)
490: .toString().length())) {
491: char origCh = items.get(0).toString().charAt(
492: mask.length() + ext.length());
493: char ch = (pim._ignoreCase) ? (Character
494: .toLowerCase(origCh)) : (origCh);
495: allMatching = true;
496: for (X i : items) {
497: String a = (pim._ignoreCase) ? (i.toString()
498: .toLowerCase()) : (i.toString());
499: if (a.charAt(len) != ch) {
500: allMatching = false;
501: break;
502: }
503: }
504: if (allMatching) {
505: ext = ext + ch;
506: res = res + origCh;
507: ++len;
508: }
509: }
510: return res;
511: }
512:
513: public String getExtendedSharedMask(List<X> items,
514: PredictiveInputModel<X> pim) {
515: int pos = pim._mask.lastIndexOf(':');
516: if (pos < 0) {
517: return pim._mask + getSharedMaskExtension(items, pim);
518: } else {
519: return pim._mask.substring(0, pos)
520: + getSharedMaskExtension(items, pim)
521: + pim._mask.substring(pos);
522: }
523: }
524:
525: public String force(X item, String mask) {
526: int pos = mask.lastIndexOf(':');
527: if (pos < 0) {
528: return item.toString();
529: } else {
530: return item.toString() + mask.substring(pos);
531: }
532: }
533: };
534:
535: /** Matching based on string fragments, supporting line numbers. */
536: public static class FragmentLineNumStrategy<X extends Comparable<? super X>>
537: implements MatchingStrategy<X> {
538: public String toString() {
539: return "Fragments";
540: }
541:
542: public boolean isMatch(X item, PredictiveInputModel<X> pim) {
543: int posB = pim._mask.lastIndexOf(':');
544: if (posB < 0) {
545: posB = pim._mask.length();
546: }
547: String mask = pim._mask.substring(0, posB);
548:
549: String a = (pim._ignoreCase) ? (item.toString()
550: .toLowerCase()) : (item.toString());
551: String b = (pim._ignoreCase) ? (mask.toLowerCase())
552: : (mask);
553:
554: java.util.StringTokenizer tok = new java.util.StringTokenizer(
555: b);
556: while (tok.hasMoreTokens()) {
557: if (a.indexOf(tok.nextToken()) < 0)
558: return false;
559: }
560: return true;
561: }
562:
563: public boolean isPerfectMatch(X item,
564: PredictiveInputModel<X> pim) {
565: int posB = pim._mask.lastIndexOf(':');
566: if (posB < 0) {
567: posB = pim._mask.length();
568: }
569: String mask = pim._mask.substring(0, posB);
570:
571: String a = (pim._ignoreCase) ? (item.toString()
572: .toLowerCase()) : (item.toString());
573: String b = (pim._ignoreCase) ? (mask.toLowerCase())
574: : (mask);
575: return a.equals(b);
576: }
577:
578: public boolean equivalent(X item1, X item2,
579: PredictiveInputModel<X> pim) {
580: int posA = item1.toString().lastIndexOf(':');
581: if (posA < 0) {
582: posA = item1.toString().length();
583: }
584: String i1 = item1.toString().substring(0, posA);
585:
586: int posB = item2.toString().lastIndexOf(':');
587: if (posB < 0) {
588: posB = item2.toString().length();
589: }
590: String i2 = item2.toString().substring(0, posB);
591:
592: String a = (pim._ignoreCase) ? (i1.toLowerCase()) : (i1);
593: String b = (pim._ignoreCase) ? (i2.toLowerCase()) : (i2);
594: return a.equals(b);
595: }
596:
597: public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
598: int posA = item1.toString().lastIndexOf(':');
599: if (posA < 0) {
600: posA = item1.toString().length();
601: }
602: String i1 = item1.toString().substring(0, posA);
603:
604: int posB = item2.toString().lastIndexOf(':');
605: if (posB < 0) {
606: posB = item2.toString().length();
607: }
608: String i2 = item2.toString().substring(0, posB);
609:
610: String a = (pim._ignoreCase) ? (i1.toLowerCase()) : (i1);
611: String b = (pim._ignoreCase) ? (i2.toLowerCase()) : (i2);
612: return a.compareTo(b);
613: }
614:
615: public X getLongestMatch(X item, List<X> items,
616: PredictiveInputModel<X> pim) {
617: if (items.size() > 0)
618: return items.get(0);
619: else
620: return null;
621: }
622:
623: public String getSharedMaskExtension(List<X> items,
624: PredictiveInputModel<X> pim) {
625: return ""; // can't thing of a good way
626: }
627:
628: public String getExtendedSharedMask(List<X> items,
629: PredictiveInputModel<X> pim) {
630: return pim._mask;
631: }
632:
633: public String force(X item, String mask) {
634: int pos = mask.lastIndexOf(':');
635: if (pos < 0) {
636: return item.toString();
637: } else {
638: return item.toString() + mask.substring(pos);
639: }
640: }
641: };
642:
643: /** Matching based on string regular expressions, supporting line numbers. */
644: public static class RegExLineNumStrategy<X extends Comparable<? super X>>
645: implements MatchingStrategy<X> {
646: public String toString() {
647: return "RegEx";
648: }
649:
650: public boolean isMatch(X item, PredictiveInputModel<X> pim) {
651: int posB = pim._mask.lastIndexOf(':');
652: if (posB < 0) {
653: posB = pim._mask.length();
654: }
655: String mask = pim._mask.substring(0, posB);
656:
657: String a = item.toString();
658:
659: try {
660: Pattern p = Pattern.compile(mask,
661: (pim._ignoreCase) ? (Pattern.CASE_INSENSITIVE)
662: : (0));
663: Matcher m = p.matcher(a);
664: return m.matches();
665: } catch (PatternSyntaxException e) {
666: return false;
667: }
668: }
669:
670: public boolean isPerfectMatch(X item,
671: PredictiveInputModel<X> pim) {
672: int posB = pim._mask.lastIndexOf(':');
673: if (posB < 0) {
674: posB = pim._mask.length();
675: }
676: String mask = pim._mask.substring(0, posB);
677:
678: String a = (pim._ignoreCase) ? item.toString()
679: .toLowerCase() : item.toString();
680: return a.equals((pim._ignoreCase) ? mask.toLowerCase()
681: : mask);
682: }
683:
684: public boolean equivalent(X item1, X item2,
685: PredictiveInputModel<X> pim) {
686: int posA = item1.toString().lastIndexOf(':');
687: if (posA < 0) {
688: posA = item1.toString().length();
689: }
690: String i1 = item1.toString().substring(0, posA);
691:
692: int posB = item2.toString().lastIndexOf(':');
693: if (posB < 0) {
694: posB = item2.toString().length();
695: }
696: String i2 = item2.toString().substring(0, posB);
697:
698: String a = (pim._ignoreCase) ? (i1.toLowerCase()) : (i1);
699: String b = (pim._ignoreCase) ? (i2.toLowerCase()) : (i2);
700: return a.equals(b);
701: }
702:
703: public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
704: int posA = item1.toString().lastIndexOf(':');
705: if (posA < 0) {
706: posA = item1.toString().length();
707: }
708: String i1 = item1.toString().substring(0, posA);
709:
710: int posB = item2.toString().lastIndexOf(':');
711: if (posB < 0) {
712: posB = item2.toString().length();
713: }
714: String i2 = item2.toString().substring(0, posB);
715:
716: String a = (pim._ignoreCase) ? (i1.toLowerCase()) : (i1);
717: String b = (pim._ignoreCase) ? (i2.toLowerCase()) : (i2);
718: return a.compareTo(b);
719: }
720:
721: public X getLongestMatch(X item, List<X> items,
722: PredictiveInputModel<X> pim) {
723: if (items.size() > 0)
724: return items.get(0); // can't thing of a good way
725: else
726: return null;
727: }
728:
729: public String getSharedMaskExtension(List<X> items,
730: PredictiveInputModel<X> pim) {
731: return ""; // can't thing of a good way
732: }
733:
734: public String getExtendedSharedMask(List<X> items,
735: PredictiveInputModel<X> pim) {
736: return pim._mask;
737: }
738:
739: public String force(X item, String mask) {
740: int pos = mask.lastIndexOf(':');
741: if (pos < 0) {
742: return item.toString();
743: } else {
744: return item.toString() + mask.substring(pos);
745: }
746: }
747: };
748:
749: /** Array of items. */
750: private volatile ArrayList<T> _items = new ArrayList<T>();
751:
752: /** Index of currently selected full string. */
753: private volatile int _index = 0;
754:
755: /** Array of matching items. */
756: private final ArrayList<T> _matchingItems = new ArrayList<T>();
757:
758: /** Currently entered mask. */
759: private volatile String _mask = "";
760:
761: /** True if case should be ignored. */
762: private volatile boolean _ignoreCase = false;
763:
764: /** Matching strategy. */
765: private volatile MatchingStrategy<T> _strategy;
766:
767: /** Create a new predictive input model.
768: * @param ignoreCase true if case should be ignored
769: * @param pim other predictive input model
770: */
771: public PredictiveInputModel(boolean ignoreCase,
772: PredictiveInputModel<T> pim) {
773: this (ignoreCase, pim._strategy, pim._items);
774: setMask(pim.getMask());
775: }
776:
777: /** Create a new predictive input model.
778: * @param ignoreCase true if case should be ignored
779: * @param strategy matching strategy to use
780: * @param items list of items
781: */
782: public PredictiveInputModel(boolean ignoreCase,
783: MatchingStrategy<T> strategy, List<T> items) {
784: _ignoreCase = ignoreCase;
785: _strategy = strategy;
786: setList(items);
787: }
788:
789: /**
790: * Create a new predictive input model.
791: * @param ignoreCase true if case should be ignored
792: * @param strategy matching strategy to use
793: * @param items varargs/array of items
794: */
795: public PredictiveInputModel(boolean ignoreCase,
796: MatchingStrategy<T> strategy, T... items) {
797: _ignoreCase = ignoreCase;
798: _strategy = strategy;
799: setList(items);
800: }
801:
802: /**
803: * Sets the strategy
804: */
805: public void setStrategy(MatchingStrategy<T> strategy) {
806: _strategy = strategy;
807: updateMatchingStrings(_items);
808: }
809:
810: /**
811: * Sets the list.
812: * @param items list of items
813: */
814: public void setList(List<T> items) {
815: _items = new ArrayList<T>(items);
816: Collections.sort(_items);
817: updateMatchingStrings(_items);
818: }
819:
820: /** Sets the list
821: * @param items varargs/array of items
822: */
823: public void setList(T... items) {
824: _items = new ArrayList<T>(items.length);
825: for (T s : items)
826: _items.add(s);
827: Collections.sort(_items);
828: updateMatchingStrings(_items);
829: }
830:
831: /** Sets the list.
832: * @param pim other predictive input model
833: */
834: public void setList(PredictiveInputModel<T> pim) {
835: setList(pim._items);
836: }
837:
838: /** Return the current mask.
839: * @return current mask
840: */
841: public String getMask() {
842: return _mask;
843: }
844:
845: /** Set the current mask.
846: * @param mask new mask
847: */
848: public void setMask(String mask) {
849: _mask = mask;
850: updateMatchingStrings(_items);
851: }
852:
853: /** Helper function that does indexOf with ignoreCase option.
854: * @param l list
855: * @param item item for which the index should be retrieved
856: * @return index of item in list, or -1 if not found
857: */
858: private int indexOf(ArrayList<T> l, T item) {
859: int index = 0;
860: for (T i : l) {
861: if (_strategy.equivalent(item, i, this ))
862: return index;
863: ++index;
864: }
865: return -1;
866: }
867:
868: /** Update the list of matching strings and current index.
869: * @param items list of items to base the matching on
870: */
871: private void updateMatchingStrings(ArrayList<T> items) {
872: items = new ArrayList<T>(items); // create a new copy, otherwise we might be clearing the list in the next line
873: _matchingItems.clear();
874: for (T s : items) {
875: if (_strategy.isMatch(s, this ))
876: _matchingItems.add(s);
877: }
878: if (_items.size() > 0) {
879: for (int i = 0; i < _items.size(); ++i) {
880: if (_strategy.isPerfectMatch(_items.get(i), this )) {
881: _index = i;
882: break;
883: }
884: }
885: setCurrentItem(_items.get(_index));
886: } else
887: _index = 0;
888: }
889:
890: /** Get currently selected item.
891: * @return currently selected item
892: */
893: public T getCurrentItem() {
894: if (_items.size() > 0)
895: return _items.get(_index);
896: else
897: return null;
898: }
899:
900: /** Set currently selected item. Will select the item, or if the item does not match the mask, an item as closely
901: * preceding as possible.
902: * @param item currently selected item
903: */
904: public void setCurrentItem(T item) {
905: if (_items.size() == 0) {
906: _index = 0;
907: return;
908: }
909: boolean found = false;
910: int index = indexOf(_items, item);
911: if (index < 0) {
912: // not in list of items, pick first item
913: pickClosestMatch(item);
914: } else {
915: for (int i = index; i < _items.size(); ++i) {
916: if (0 <= indexOf(_matchingItems, _items.get(i))) {
917: _index = i;
918: found = true;
919: break;
920: }
921: }
922: if (!found) {
923: pickClosestMatch(item);
924: }
925: }
926: }
927:
928: /** Select as current item the item in the list of current matches that lexicographically precedes it most closely.
929: * @param item item for witch to find the closest match
930: */
931: private void pickClosestMatch(T item) {
932: if (_matchingItems.size() > 0) {
933: // pick item that lexicographically follows
934: T follows = _matchingItems.get(0);
935: for (T i : _matchingItems) {
936: if (_strategy.compare(item, i, this ) < 0) {
937: break;
938: }
939: follows = i;
940: }
941: _index = indexOf(_items, follows);
942: } else {
943: _index = indexOf(_items, _strategy.getLongestMatch(item,
944: _items, this ));
945: }
946: }
947:
948: /**
949: * Get matching items.
950: * @return list of matching items
951: */
952: public List<T> getMatchingItems() {
953: return new ArrayList<T>(_matchingItems);
954: }
955:
956: /**
957: * Returns the shared mask extension.
958: * The shared mask extension is the string that can be added to the mask such that the list of
959: * matching strings does not change.
960: * @return shared mask extension
961: */
962: public String getSharedMaskExtension() {
963: return _strategy.getSharedMaskExtension(_matchingItems, this );
964: }
965:
966: /**
967: * Extends the mask. This operation can only narrow the list of matching strings and is thus faster than
968: * setting the mask.
969: * @param extension string to append to mask
970: */
971: public void extendMask(String extension) {
972: _mask = _mask + extension;
973: updateMatchingStrings(_matchingItems);
974: }
975:
976: /**
977: * Extends the mask by the shared string.
978: */
979: public void extendSharedMask() {
980: _mask = _strategy.getExtendedSharedMask(_matchingItems, this);
981: updateMatchingStrings(_matchingItems);
982: }
983: }
|