Clover coverage report - DrJava Test Coverage (drjava-20120304-r5456)
Coverage timestamp: Sun Mar 4 2012 03:13:23 CST
file stats: LOC: 767   Methods: 73
NCLOC: 537   Classes: 8
 
 Source file Conditionals Statements Methods TOTAL
PredictiveInputModel.java 45.5% 49.9% 53.4% 48.8%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.drjava.ui.predictive;
 38   
 39    import java.util.Collection;
 40    import java.util.List;
 41    import java.util.ArrayList;
 42    import java.util.Collections;
 43    import java.util.regex.Pattern;
 44    import java.util.regex.Matcher;
 45    import java.util.regex.PatternSyntaxException;
 46   
 47    /** Model class for predictive string input. */
 48    public class PredictiveInputModel<T extends Comparable<? super T>> {
 49   
 50    /** Strategy used for matching and mask extension. */
 51    public static interface MatchingStrategy<X extends Comparable<? super X>> {
 52   
 53    /** Returns true if the item is a match.
 54    * @param item item to check
 55    * @param pim predictive input model
 56    * @return true if the item is a match
 57    */
 58    public boolean isMatch(X item, PredictiveInputModel<X> pim);
 59   
 60    /** Returns true if the item is perfect a match.
 61    * @param item item to check
 62    * @param pim predictive input model
 63    * @return true if the item is a match
 64    */
 65    public boolean isPerfectMatch(X item, PredictiveInputModel<X> pim);
 66   
 67    /** Returns true if the two items are equivalent under this matching strategy.
 68    * @param item1 first item
 69    * @param item2 second item
 70    * @param pim predictive input model
 71    * @return true if equivalent
 72    */
 73    public boolean equivalent(X item1, X item2, PredictiveInputModel<X> pim);
 74   
 75    /** Compare the two items and return -1, 0, or 1 if item1 is less than, equal to, or greater than item2.
 76    * @param item1 first item
 77    * @param item2 second item
 78    * @param pim predictive input model
 79    * @return -1, 0, or 1
 80    */
 81    public int compare(X item1, X item2, PredictiveInputModel<X> pim);
 82   
 83    /** Returns the item from the list that is the longest match.
 84    * @param item target item
 85    * @param items list with items
 86    * @param pim predictive input model
 87    * @return longest match
 88    */
 89    public X getLongestMatch(X item, List<X> items, PredictiveInputModel<X> pim);
 90   
 91    /** Returns the shared mask extension for the list of items.
 92    * @param items items for which the mask extension should be generated
 93    * @param pim predictive input model
 94    * @return the shared mask extension
 95    */
 96    public String getSharedMaskExtension(List<X> items, PredictiveInputModel<X> pim);
 97   
 98    /** Returns the mask extended by the shared extension.
 99    * @param items items for which the mask extension should be generated
 100    * @param pim predictive input model
 101    * @return the extended shared mask
 102    */
 103    public String getExtendedSharedMask(List<X> items, PredictiveInputModel<X> pim);
 104   
 105    /** Force the mask to fit this entry. The matching strategies that accept line numbers
 106    * can combine the current item with the line number. Other strategies just return the
 107    * current item.
 108    * @return forced string
 109    */
 110    public String force(X item, String mask);
 111    }
 112   
 113    /** Matching based on string prefix. */
 114    public static class PrefixStrategy<X extends Comparable<? super X>> implements MatchingStrategy<X> {
 115  0 public String toString() { return "Prefix"; }
 116  239 public boolean isMatch(X item, PredictiveInputModel<X> pim) {
 117  239 String a = (pim._ignoreCase) ? (item.toString().toLowerCase()) : (item.toString());
 118  239 String b = (pim._ignoreCase) ? (pim._mask.toLowerCase()) : (pim._mask);
 119  239 return a.startsWith(b);
 120    }
 121  271 public boolean isPerfectMatch(X item, PredictiveInputModel<X> pim) {
 122  271 String a = (pim._ignoreCase) ? (item.toString().toLowerCase()) : (item.toString());
 123  271 String b = (pim._ignoreCase) ? (pim._mask.toLowerCase()) : (pim._mask);
 124  271 return a.equals(b);
 125    }
 126  354 public boolean equivalent(X item1, X item2, PredictiveInputModel<X> pim) {
 127  354 String a = (pim._ignoreCase) ? (item1.toString().toLowerCase()) : (item1.toString());
 128  354 String b = (pim._ignoreCase) ? (item2.toString().toLowerCase()) : (item2.toString());
 129  354 return a.equals(b);
 130    }
 131  18 public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
 132  18 String a = (pim._ignoreCase) ? (item1.toString().toLowerCase()) : (item1.toString());
 133  18 String b = (pim._ignoreCase) ? (item2.toString().toLowerCase()) : (item2.toString());
 134  18 return a.compareTo(b);
 135    }
 136  16 public X getLongestMatch(X item, List<X> items, PredictiveInputModel<X> pim) {
 137  16 X longestMatch = null;
 138  16 int matchLength = -1;
 139  16 for(X i: items) {
 140  54 String s = (pim._ignoreCase) ? (i.toString().toLowerCase()) : (i.toString());
 141  54 String t = (pim._ignoreCase) ? (item.toString().toLowerCase()) : (item.toString());
 142  54 int ml = 0;
 143  54 while((s.length() > ml) && (t.length() > ml) && (s.charAt(ml) == t.charAt(ml))) {
 144  426 ++ml;
 145    }
 146  54 if (ml>matchLength) {
 147  26 matchLength = ml;
 148  26 longestMatch = i;
 149    }
 150    }
 151  16 return longestMatch;
 152    }
 153  99 public String getSharedMaskExtension(List<X> items, PredictiveInputModel<X> pim) {
 154  99 StringBuilder res = new StringBuilder();
 155  99 String ext = "";
 156  99 if (items.size() == 0) {
 157  17 return ext;
 158    }
 159  82 boolean allMatching = true;
 160  82 int len = pim._mask.length();
 161  82 while((allMatching) && (pim._mask.length() + ext.length() < items.get(0).toString().length())) {
 162  477 char origCh = items.get(0).toString().charAt(pim._mask.length() + ext.length());
 163  477 char ch = (pim._ignoreCase) ? (Character.toLowerCase(origCh)) : (origCh);
 164  477 allMatching = true;
 165  477 for (X i: items) {
 166  926 String a = (pim._ignoreCase) ? (i.toString().toLowerCase()) : (i.toString());
 167  926 if (a.charAt(len) != ch) {
 168  75 allMatching = false;
 169  75 break;
 170    }
 171    }
 172  477 if (allMatching) {
 173  402 ext = ext + ch;
 174  402 res.append(origCh);
 175  402 ++len;
 176    }
 177    }
 178  82 return res.toString();
 179    }
 180  0 public String getExtendedSharedMask(List<X> items, PredictiveInputModel<X> pim) {
 181  0 return pim._mask + getSharedMaskExtension(items, pim);
 182    }
 183  0 public String force(X item, String mask) { return item.toString(); }
 184    };
 185   
 186    /** Matching based on string fragments. */
 187    public static class FragmentStrategy<X extends Comparable<? super X>> implements MatchingStrategy<X> {
 188  0 public String toString() { return "Fragments"; }
 189  200 public boolean isMatch(X item, PredictiveInputModel<X> pim) {
 190  200 String a = (pim._ignoreCase) ? (item.toString().toLowerCase()) : (item.toString());
 191  200 String b = (pim._ignoreCase) ? (pim._mask.toLowerCase()) : (pim._mask);
 192   
 193  200 java.util.StringTokenizer tok = new java.util.StringTokenizer(b);
 194  200 while(tok.hasMoreTokens()) {
 195  76 if (a.indexOf(tok.nextToken()) < 0) return false;
 196    }
 197  124 return true;
 198    }
 199  244 public boolean isPerfectMatch(X item, PredictiveInputModel<X> pim) {
 200  244 String a = (pim._ignoreCase) ? (item.toString().toLowerCase()) : (item.toString());
 201  244 String b = (pim._ignoreCase) ? (pim._mask.toLowerCase()) : (pim._mask);
 202  244 return a.equals(b);
 203    }
 204  258 public boolean equivalent(X item1, X item2, PredictiveInputModel<X> pim) {
 205  258 String a = (pim._ignoreCase)?(item1.toString().toLowerCase()):(item1.toString());
 206  258 String b = (pim._ignoreCase)?(item2.toString().toLowerCase()):(item2.toString());
 207  258 return a.equals(b);
 208    }
 209  13 public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
 210  13 String a = (pim._ignoreCase)?(item1.toString().toLowerCase()):(item1.toString());
 211  13 String b = (pim._ignoreCase)?(item2.toString().toLowerCase()):(item2.toString());
 212  13 return a.compareTo(b);
 213    }
 214  12 public X getLongestMatch(X item, List<X> items, PredictiveInputModel<X> pim) {
 215  12 if (items.size() > 0) return items.get(0);
 216  0 else return null;
 217    }
 218  2 public String getSharedMaskExtension(List<X> items, PredictiveInputModel<X> pim) {
 219  2 return ""; // can't thing of a good way
 220    }
 221  0 public String getExtendedSharedMask(List<X> items, PredictiveInputModel<X> pim) {
 222  0 return pim._mask;
 223    }
 224  0 public String force(X item, String mask) { return item.toString(); }
 225    };
 226   
 227    /** Matching based on string regular expressions. */
 228    public static class RegExStrategy<X extends Comparable<? super X>> implements MatchingStrategy<X> {
 229  0 public String toString() { return "RegEx"; }
 230  15 public boolean isMatch(X item, PredictiveInputModel<X> pim) {
 231  15 String a = item.toString();
 232   
 233  15 try {
 234  15 Pattern p = Pattern.compile(pim._mask,
 235  15 (pim._ignoreCase)?(Pattern.CASE_INSENSITIVE):(0));
 236  15 Matcher m = p.matcher(a);
 237  15 return m.matches();
 238    }
 239    catch (PatternSyntaxException e) {
 240  0 return false;
 241    }
 242    }
 243  15 public boolean isPerfectMatch(X item, PredictiveInputModel<X> pim) {
 244  15 String a = (pim._ignoreCase)?(item.toString().toLowerCase()):(item.toString());
 245  15 String b = (pim._ignoreCase)?(pim._mask.toLowerCase()):(pim._mask);
 246  15 return a.equals(b);
 247    }
 248  19 public boolean equivalent(X item1, X item2, PredictiveInputModel<X> pim) {
 249  19 String a = (pim._ignoreCase)?(item1.toString().toLowerCase()):(item1.toString());
 250  19 String b = (pim._ignoreCase)?(item2.toString().toLowerCase()):(item2.toString());
 251  19 return a.equals(b);
 252    }
 253  0 public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
 254  0 String a = (pim._ignoreCase)?(item1.toString().toLowerCase()):(item1.toString());
 255  0 String b = (pim._ignoreCase)?(item2.toString().toLowerCase()):(item2.toString());
 256  0 return a.compareTo(b);
 257    }
 258  1 public X getLongestMatch(X item, List<X> items, PredictiveInputModel<X> pim) {
 259  1 if (items.size() > 0) return items.get(0); // can't thing of a good way
 260  0 else return null;
 261    }
 262  0 public String getSharedMaskExtension(List<X> items, PredictiveInputModel<X> pim) {
 263  0 return ""; // can't thing of a good way
 264    }
 265  0 public String getExtendedSharedMask(List<X> items, PredictiveInputModel<X> pim) {
 266  0 return pim._mask;
 267    }
 268  0 public String force(X item, String mask) { return item.toString(); }
 269    };
 270   
 271    /** Matching based on string prefix, supporting line numbers separated by :. */
 272    public static class PrefixLineNumStrategy<X extends Comparable<? super X>> implements MatchingStrategy<X> {
 273  3 public String toString() { return "Prefix"; }
 274  0 public boolean isMatch(X item, PredictiveInputModel<X> pim) {
 275  0 int posB = pim._mask.lastIndexOf(':');
 276  0 if (posB < 0) { posB = pim._mask.length(); }
 277  0 String mask = pim._mask.substring(0,posB);
 278   
 279  0 String a = (pim._ignoreCase)?(item.toString().toLowerCase()):(item.toString());
 280  0 String b = (pim._ignoreCase)?(mask.toLowerCase()):(mask);
 281  0 return a.startsWith(b);
 282    }
 283  0 public boolean isPerfectMatch(X item, PredictiveInputModel<X> pim) {
 284  0 int posB = pim._mask.lastIndexOf(':');
 285  0 if (posB < 0) { posB = pim._mask.length(); }
 286  0 String mask = pim._mask.substring(0,posB);
 287   
 288  0 String a = (pim._ignoreCase)?(item.toString().toLowerCase()):(item.toString());
 289  0 String b = (pim._ignoreCase)?(mask.toLowerCase()):(mask);
 290  0 return a.equals(b);
 291    }
 292  0 public boolean equivalent(X item1, X item2, PredictiveInputModel<X> pim) {
 293  0 int posA = item1.toString().lastIndexOf(':');
 294  0 if (posA < 0) { posA = item1.toString().length(); }
 295  0 String i1 = item1.toString().substring(0,posA);
 296   
 297  0 int posB = item2.toString().lastIndexOf(':');
 298  0 if (posB < 0) { posB = item2.toString().length(); }
 299  0 String i2 = item2.toString().substring(0,posB);
 300   
 301  0 String a = (pim._ignoreCase)?(i1.toLowerCase()):(i1);
 302  0 String b = (pim._ignoreCase)?(i2.toLowerCase()):(i2);
 303  0 return a.equals(b);
 304    }
 305  0 public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
 306  0 int posA = item1.toString().lastIndexOf(':');
 307  0 if (posA < 0) { posA = item1.toString().length(); }
 308  0 String i1 = item1.toString().substring(0,posA);
 309   
 310  0 int posB = item2.toString().lastIndexOf(':');
 311  0 if (posB < 0) { posB = item2.toString().length(); }
 312  0 String i2 = item2.toString().substring(0,posB);
 313   
 314  0 String a = (pim._ignoreCase)?(i1.toLowerCase()):(i1);
 315  0 String b = (pim._ignoreCase)?(i2.toLowerCase()):(i2);
 316  0 return a.compareTo(b);
 317    }
 318  0 public X getLongestMatch(X item, List<X> items, PredictiveInputModel<X> pim) {
 319  0 X longestMatch = null;
 320  0 int matchLength = -1;
 321  0 for(X i: items) {
 322  0 int posA = i.toString().lastIndexOf(':');
 323  0 if (posA < 0) { posA = i.toString().length(); }
 324  0 String i1 = i.toString().substring(0,posA);
 325   
 326  0 int posB = item.toString().lastIndexOf(':');
 327  0 if (posB < 0) { posB = item.toString().length(); }
 328  0 String i2 = item.toString().substring(0,posB);
 329   
 330  0 String s = (pim._ignoreCase)?(i1.toLowerCase()):(i1);
 331  0 String t = (pim._ignoreCase)?(i2.toLowerCase()):(i2);
 332  0 int ml = 0;
 333  0 while((s.length() > ml) && (t.length() > ml) && (s.charAt(ml) == t.charAt(ml))) {
 334  0 ++ml;
 335    }
 336  0 if (ml>matchLength) {
 337  0 matchLength = ml;
 338  0 longestMatch = i;
 339    }
 340    }
 341  0 return longestMatch;
 342    }
 343  0 public String getSharedMaskExtension(List<X> items, PredictiveInputModel<X> pim) {
 344  0 StringBuilder res = new StringBuilder();
 345  0 String ext = "";
 346  0 if (items.size() == 0) {
 347  0 return ext;
 348    }
 349   
 350  0 int posB = pim._mask.lastIndexOf(':');
 351  0 if (posB < 0) { posB = pim._mask.length(); }
 352  0 String mask = pim._mask.substring(0,posB);
 353   
 354  0 boolean allMatching = true;
 355  0 int len = mask.length();
 356  0 while((allMatching) && (mask.length() + ext.length() < items.get(0).toString().length())) {
 357  0 char origCh = items.get(0).toString().charAt(mask.length()+ext.length());
 358  0 char ch = (pim._ignoreCase)?(Character.toLowerCase(origCh)):(origCh);
 359  0 allMatching = true;
 360  0 for (X i: items) {
 361  0 String a = (pim._ignoreCase)?(i.toString().toLowerCase()):(i.toString());
 362  0 if (a.charAt(len) != ch) {
 363  0 allMatching = false;
 364  0 break;
 365    }
 366    }
 367  0 if (allMatching) {
 368  0 ext = ext + ch;
 369  0 res.append(origCh);
 370  0 ++len;
 371    }
 372    }
 373  0 return res.toString();
 374    }
 375  0 public String getExtendedSharedMask(List<X> items, PredictiveInputModel<X> pim) {
 376  0 int pos = pim._mask.lastIndexOf(':');
 377  0 if (pos < 0) {
 378  0 return pim._mask + getSharedMaskExtension(items, pim);
 379    }
 380    else {
 381  0 return pim._mask.substring(0,pos) + getSharedMaskExtension(items, pim) + pim._mask.substring(pos);
 382    }
 383    }
 384  0 public String force(X item, String mask) {
 385  0 int pos = mask.lastIndexOf(':');
 386  0 if (pos < 0) {
 387  0 return item.toString();
 388    }
 389    else {
 390  0 return item.toString()+mask.substring(pos);
 391    }
 392    }
 393    };
 394   
 395    /** Matching based on string fragments, supporting line numbers. */
 396    public static class FragmentLineNumStrategy<X extends Comparable<? super X>> implements MatchingStrategy<X> {
 397  3 public String toString() { return "Fragments"; }
 398  24 public boolean isMatch(X item, PredictiveInputModel<X> pim) {
 399  24 int posB = pim._mask.lastIndexOf(':');
 400  24 if (posB < 0) { posB = pim._mask.length(); }
 401  24 String mask = pim._mask.substring(0,posB);
 402   
 403  24 String a = (pim._ignoreCase)?(item.toString().toLowerCase()):(item.toString());
 404  24 String b = (pim._ignoreCase)?(mask.toLowerCase()):(mask);
 405   
 406  24 java.util.StringTokenizer tok = new java.util.StringTokenizer(b);
 407  24 while(tok.hasMoreTokens()) {
 408  3 if (a.indexOf(tok.nextToken()) < 0) return false;
 409    }
 410  21 return true;
 411    }
 412  24 public boolean isPerfectMatch(X item, PredictiveInputModel<X> pim) {
 413  24 int posB = pim._mask.lastIndexOf(':');
 414  24 if (posB < 0) { posB = pim._mask.length(); }
 415  24 String mask = pim._mask.substring(0,posB);
 416   
 417  24 String a = (pim._ignoreCase)?(item.toString().toLowerCase()):(item.toString());
 418  24 String b = (pim._ignoreCase)?(mask.toLowerCase()):(mask);
 419  24 return a.equals(b);
 420    }
 421  42 public boolean equivalent(X item1, X item2, PredictiveInputModel<X> pim) {
 422  42 int posA = item1.toString().lastIndexOf(':');
 423  42 if (posA < 0) { posA = item1.toString().length(); }
 424  42 String i1 = item1.toString().substring(0,posA);
 425   
 426  42 int posB = item2.toString().lastIndexOf(':');
 427  42 if (posB < 0) { posB = item2.toString().length(); }
 428  42 String i2 = item2.toString().substring(0,posB);
 429   
 430  42 String a = (pim._ignoreCase)?(i1.toLowerCase()):(i1);
 431  42 String b = (pim._ignoreCase)?(i2.toLowerCase()):(i2);
 432  42 return a.equals(b);
 433    }
 434  0 public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
 435  0 int posA = item1.toString().lastIndexOf(':');
 436  0 if (posA < 0) { posA = item1.toString().length(); }
 437  0 String i1 = item1.toString().substring(0,posA);
 438   
 439  0 int posB = item2.toString().lastIndexOf(':');
 440  0 if (posB < 0) { posB = item2.toString().length(); }
 441  0 String i2 = item2.toString().substring(0,posB);
 442   
 443  0 String a = (pim._ignoreCase)?(i1.toLowerCase()):(i1);
 444  0 String b = (pim._ignoreCase)?(i2.toLowerCase()):(i2);
 445  0 return a.compareTo(b);
 446    }
 447  0 public X getLongestMatch(X item, List<X> items, PredictiveInputModel<X> pim) {
 448  0 if (items.size() > 0) return items.get(0);
 449  0 else return null;
 450    }
 451  30 public String getSharedMaskExtension(List<X> items, PredictiveInputModel<X> pim) {
 452  30 return ""; // can't thing of a good way
 453    }
 454  0 public String getExtendedSharedMask(List<X> items, PredictiveInputModel<X> pim) {
 455  0 return pim._mask;
 456    }
 457  0 public String force(X item, String mask) {
 458  0 int pos = mask.lastIndexOf(':');
 459  0 if (pos < 0) {
 460  0 return item.toString();
 461    }
 462    else {
 463  0 return item.toString()+mask.substring(pos);
 464    }
 465    }
 466    };
 467   
 468    /** Matching based on string regular expressions, supporting line numbers. */
 469    public static class RegExLineNumStrategy<X extends Comparable<? super X>> implements MatchingStrategy<X> {
 470  3 public String toString() { return "RegEx"; }
 471  0 public boolean isMatch(X item, PredictiveInputModel<X> pim) {
 472  0 int posB = pim._mask.lastIndexOf(':');
 473  0 if (posB < 0) { posB = pim._mask.length(); }
 474  0 String mask = pim._mask.substring(0,posB);
 475   
 476  0 String a = item.toString();
 477   
 478  0 try {
 479  0 Pattern p = Pattern.compile(mask,
 480  0 (pim._ignoreCase)?(Pattern.CASE_INSENSITIVE):(0));
 481  0 Matcher m = p.matcher(a);
 482  0 return m.matches();
 483    }
 484    catch (PatternSyntaxException e) {
 485  0 return false;
 486    }
 487    }
 488  0 public boolean isPerfectMatch(X item, PredictiveInputModel<X> pim) {
 489  0 int posB = pim._mask.lastIndexOf(':');
 490  0 if (posB < 0) { posB = pim._mask.length(); }
 491  0 String mask = pim._mask.substring(0,posB);
 492   
 493  0 String a = (pim._ignoreCase)?item.toString().toLowerCase():item.toString();
 494  0 return a.equals((pim._ignoreCase)?mask.toLowerCase():mask);
 495    }
 496  0 public boolean equivalent(X item1, X item2, PredictiveInputModel<X> pim) {
 497  0 int posA = item1.toString().lastIndexOf(':');
 498  0 if (posA < 0) { posA = item1.toString().length(); }
 499  0 String i1 = item1.toString().substring(0,posA);
 500   
 501  0 int posB = item2.toString().lastIndexOf(':');
 502  0 if (posB < 0) { posB = item2.toString().length(); }
 503  0 String i2 = item2.toString().substring(0,posB);
 504   
 505  0 String a = (pim._ignoreCase)?(i1.toLowerCase()):(i1);
 506  0 String b = (pim._ignoreCase)?(i2.toLowerCase()):(i2);
 507  0 return a.equals(b);
 508    }
 509  0 public int compare(X item1, X item2, PredictiveInputModel<X> pim) {
 510  0 int posA = item1.toString().lastIndexOf(':');
 511  0 if (posA < 0) { posA = item1.toString().length(); }
 512  0 String i1 = item1.toString().substring(0,posA);
 513   
 514  0 int posB = item2.toString().lastIndexOf(':');
 515  0 if (posB < 0) { posB = item2.toString().length(); }
 516  0 String i2 = item2.toString().substring(0,posB);
 517   
 518  0 String a = (pim._ignoreCase)?(i1.toLowerCase()):(i1);
 519  0 String b = (pim._ignoreCase)?(i2.toLowerCase()):(i2);
 520  0 return a.compareTo(b);
 521    }
 522  0 public X getLongestMatch(X item, List<X> items, PredictiveInputModel<X> pim) {
 523  0 if (items.size() > 0) return items.get(0); // can't thing of a good way
 524  0 else return null;
 525    }
 526  0 public String getSharedMaskExtension(List<X> items, PredictiveInputModel<X> pim) {
 527  0 return ""; // can't thing of a good way
 528    }
 529  0 public String getExtendedSharedMask(List<X> items, PredictiveInputModel<X> pim) {
 530  0 return pim._mask;
 531    }
 532  0 public String force(X item, String mask) {
 533  0 int pos = mask.lastIndexOf(':');
 534  0 if (pos < 0) {
 535  0 return item.toString();
 536    }
 537    else {
 538  0 return item.toString()+mask.substring(pos);
 539    }
 540    }
 541    };
 542   
 543    /** Array of items. */
 544    private volatile ArrayList<T> _items = new ArrayList<T>();
 545   
 546    /** Index of currently selected full string. */
 547    private volatile int _index = 0;
 548   
 549    /** Array of matching items. */
 550    private final ArrayList<T> _matchingItems = new ArrayList<T>();
 551   
 552    /** Currently entered mask. */
 553    private volatile String _mask = "";
 554   
 555    /** True if case should be ignored. */
 556    private volatile boolean _ignoreCase = false;
 557   
 558    /** Matching strategy. */
 559    private volatile MatchingStrategy<T> _strategy;
 560   
 561    /** Create a new predictive input model.
 562    * @param ignoreCase true if case should be ignored
 563    * @param pim other predictive input model
 564    */
 565  1 public PredictiveInputModel(boolean ignoreCase, PredictiveInputModel<T> pim) {
 566  1 this(ignoreCase, pim._strategy, pim._items);
 567  1 setMask(pim.getMask());
 568    }
 569   
 570    /** Create a new predictive input model.
 571    * @param ignoreCase true if case should be ignored
 572    * @param strategy matching strategy to use
 573    * @param items list of items
 574    */
 575  9 public PredictiveInputModel(boolean ignoreCase, MatchingStrategy<T> strategy, Collection<T> items) {
 576  9 _ignoreCase = ignoreCase;
 577  9 _strategy = strategy;
 578  9 setItems(items);
 579    }
 580   
 581    /** Create a new predictive input model.
 582    * @param ignoreCase true if case should be ignored
 583    * @param strategy matching strategy to use
 584    * @param items varargs/array of items
 585    */
 586  31 public PredictiveInputModel(boolean ignoreCase, MatchingStrategy<T> strategy, T... items) {
 587  31 _ignoreCase = ignoreCase;
 588  31 _strategy = strategy;
 589  31 setItems(items);
 590    }
 591   
 592    /** Sets the strategy
 593    */
 594  6 public void setStrategy(MatchingStrategy<T> strategy) {
 595  6 _strategy = strategy;
 596  6 updateMatchingStrings(_items);
 597    }
 598   
 599    /** Returns a copy of the list of items.
 600    * @return list of items
 601    */
 602  0 public List<T> getItems() {
 603  0 return new ArrayList<T>(_items);
 604    }
 605   
 606    /** Sets the list.
 607    * @param items list of items
 608    */
 609  9 public void setItems(Collection<T> items) {
 610  9 _items = new ArrayList<T>(items);
 611  9 Collections.sort(_items);
 612  9 updateMatchingStrings(_items);
 613    }
 614   
 615    /** Sets the list
 616    * @param items varargs/array of items
 617    */
 618  34 public void setItems(T... items) {
 619  34 _items = new ArrayList<T>(items.length);
 620  126 for(T s: items) _items.add(s);
 621  34 Collections.sort(_items);
 622  34 updateMatchingStrings(_items);
 623    }
 624   
 625    /** Sets the list.
 626    * @param pim other predictive input model
 627    */
 628  0 public void setItems(PredictiveInputModel<T> pim) { setItems(pim._items); }
 629   
 630    /** Return the current mask.
 631    * @return current mask
 632    */
 633  180 public String getMask() { return _mask; }
 634   
 635    /** Set the current mask.
 636    * @param mask new mask
 637    */
 638  84 public void setMask(String mask) {
 639  84 _mask = mask;
 640  84 updateMatchingStrings(_items);
 641    }
 642   
 643    /** Helper function that does indexOf with ignoreCase option.
 644    * @param l list
 645    * @param item item for which the index should be retrieved
 646    * @return index of item in list, or -1 if not found
 647    */
 648  556 private int indexOf(ArrayList<T> l, T item) {
 649  556 int index = 0;
 650  556 for (T i: l) {
 651  408 if (_strategy.equivalent(item, i, this)) return index;
 652  265 ++index;
 653    }
 654  148 return -1;
 655    }
 656   
 657    /** Update the list of matching strings and current index.
 658    * @param items list of items to base the matching on
 659    */
 660  178 private void updateMatchingStrings(ArrayList<T> items) {
 661  178 items = new ArrayList<T>(items); // create a new copy, otherwise we might be clearing the list in the next line
 662  178 _matchingItems.clear();
 663  178 for(T s: items) {
 664  301 if (_strategy.isMatch(s, this)) _matchingItems.add(s);
 665    }
 666  178 if (_items.size() > 0) {
 667  159 for(int i = 0; i < _items.size(); ++i) {
 668  554 if (_strategy.isPerfectMatch(_items.get(i), this)) {
 669  1 _index = i;
 670  1 break;
 671    }
 672    }
 673  159 setCurrentItem(_items.get(_index));
 674    }
 675  19 else _index = 0;
 676    }
 677   
 678    /** Get currently selected item.
 679    * @return currently selected item
 680    */
 681  178 public T getCurrentItem() {
 682  159 if (_items.size() > 0) return _items.get(_index);
 683  19 else return null;
 684    }
 685   
 686    /** Set currently selected item. Will select the item, or if the item does not match the mask, an item as closely
 687    * preceding as possible.
 688    * @param item currently selected item
 689    */
 690  211 public void setCurrentItem(T item) {
 691  211 if (_items.size() == 0) {
 692  0 _index = 0;
 693  0 return;
 694    }
 695  211 boolean found = false;
 696  211 int index = indexOf(_items, item);
 697  211 if (index < 0) {
 698    // not in list of items, pick first item
 699  14 pickClosestMatch(item);
 700    }
 701    else {
 702  197 for (int i=index; i < _items.size(); ++i) {
 703  297 if (0 <= indexOf(_matchingItems, _items.get(i))) {
 704  163 _index = i;
 705  163 found = true;
 706  163 break;
 707    }
 708    }
 709  197 if (!found) {
 710  34 pickClosestMatch(item);
 711    }
 712    }
 713    }
 714   
 715    /** Select as current item the item in the list of current matches that lexicographically precedes it most closely.
 716    * @param item item for witch to find the closest match
 717    */
 718  48 private void pickClosestMatch(T item) {
 719  48 if (_matchingItems.size() > 0) {
 720    // pick item that lexicographically follows
 721  19 T follows = _matchingItems.get(0);
 722  19 for (T i: _matchingItems) {
 723  31 if (_strategy.compare(item, i, this) < 0) {
 724  10 break;
 725    }
 726  21 follows = i;
 727    }
 728  19 _index = indexOf(_items, follows);
 729    }
 730    else {
 731  29 _index = indexOf(_items, _strategy.getLongestMatch(item, _items, this));
 732    }
 733    }
 734   
 735    /** Get matching items.
 736    * @return list of matching items
 737    */
 738  204 public List<T> getMatchingItems() {
 739  204 return new ArrayList<T>(_matchingItems);
 740    }
 741   
 742    /** Returns the shared mask extension.
 743    * The shared mask extension is the string that can be added to the mask such that the list of
 744    * matching strings does not change.
 745    * @return shared mask extension
 746    */
 747  131 public String getSharedMaskExtension() {
 748  131 return _strategy.getSharedMaskExtension(_matchingItems, this);
 749    }
 750   
 751    /** Extends the mask. This operation can only narrow the list of matching strings and is thus faster than
 752    * setting the mask.
 753    * @param extension string to append to mask
 754    */
 755  45 public void extendMask(String extension) {
 756  45 _mask = _mask + extension;
 757  45 updateMatchingStrings(_matchingItems);
 758    }
 759   
 760   
 761    /** Extends the mask by the shared string.
 762    */
 763  0 public void extendSharedMask() {
 764  0 _mask = _strategy.getExtendedSharedMask(_matchingItems, this);
 765  0 updateMatchingStrings(_matchingItems);
 766    }
 767    }