001 /* MaskFormatter.java --
002 Copyright (C) 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.text;
040
041 import gnu.java.lang.CPStringBuilder;
042
043 import java.text.ParseException;
044
045 import javax.swing.JFormattedTextField;
046
047 /**
048 * @author Anthony Balkissoon abalkiss at redhat dot com
049 *
050 */
051 public class MaskFormatter extends DefaultFormatter
052 {
053 // The declaration of the valid mask characters
054 private static final char NUM_CHAR = '#';
055 private static final char ESCAPE_CHAR = '\'';
056 private static final char UPPERCASE_CHAR = 'U';
057 private static final char LOWERCASE_CHAR = 'L';
058 private static final char ALPHANUM_CHAR = 'A';
059 private static final char LETTER_CHAR = '?';
060 private static final char ANYTHING_CHAR = '*';
061 private static final char HEX_CHAR = 'H';
062
063 /** The mask for this MaskFormatter **/
064 private String mask;
065
066 /**
067 * A String made up of the characters that are not valid for input for
068 * this MaskFormatter.
069 */
070 private String invalidChars;
071
072 /**
073 * A String made up of the characters that are valid for input for
074 * this MaskFormatter.
075 */
076 private String validChars;
077
078 /** A String used in place of missing chracters if the value does not
079 * completely fill in the spaces in the mask.
080 */
081 private String placeHolder;
082
083 /** A character used in place of missing characters if the value does
084 * not completely fill in the spaces in the mask.
085 */
086 private char placeHolderChar = ' ';
087
088 /**
089 * Whether or not stringToValue should return literal characters in the mask.
090 */
091 private boolean valueContainsLiteralCharacters = true;
092
093 /** A String used for easy access to valid HEX characters **/
094 private static String hexString = "0123456789abcdefABCDEF";
095
096 /** An int to hold the length of the mask, accounting for escaped characters **/
097 int maskLength = 0;
098
099 public MaskFormatter ()
100 {
101 // Override super's default behaviour, in MaskFormatter the default
102 // is not to allow invalid values
103 setAllowsInvalid(false);
104 }
105
106 /**
107 * Creates a MaskFormatter with the specified mask.
108 * @specnote doesn't actually throw a ParseException although it
109 * is declared to do so
110 * @param mask
111 * @throws java.text.ParseException
112 */
113 public MaskFormatter (String mask) throws java.text.ParseException
114 {
115 this();
116 setMask (mask);
117 }
118
119 /**
120 * Returns the mask used in this MaskFormatter.
121 * @return the mask used in this MaskFormatter.
122 */
123 public String getMask()
124 {
125 return mask;
126 }
127
128 /**
129 * Returns a String containing the characters that are not valid for input
130 * for this MaskFormatter.
131 * @return a String containing the invalid characters.
132 */
133 public String getInvalidCharacters()
134 {
135 return invalidChars;
136 }
137
138 /**
139 * Sets characters that are not valid for input. If
140 * <code>invalidCharacters</code> is non-null then no characters contained
141 * in it will be allowed to be input.
142 *
143 * @param invalidCharacters the String specifying invalid characters.
144 */
145 public void setInvalidCharacters (String invalidCharacters)
146 {
147 this.invalidChars = invalidCharacters;
148 }
149
150 /**
151 * Returns a String containing the characters that are valid for input
152 * for this MaskFormatter.
153 * @return a String containing the valid characters.
154 */
155 public String getValidCharacters()
156 {
157 return validChars;
158 }
159
160 /**
161 * Sets characters that are valid for input. If
162 * <code>validCharacters</code> is non-null then no characters that are
163 * not contained in it will be allowed to be input.
164 *
165 * @param validCharacters the String specifying valid characters.
166 */
167 public void setValidCharacters (String validCharacters)
168 {
169 this.validChars = validCharacters;
170 }
171
172 /**
173 * Returns the place holder String that is used in place of missing
174 * characters when the value doesn't completely fill in the spaces
175 * in the mask.
176 * @return the place holder String.
177 */
178 public String getPlaceholder()
179 {
180 return placeHolder;
181 }
182
183 /**
184 * Sets the string to use if the value does not completely fill in the mask.
185 * If this is null, the place holder character will be used instead.
186 * @param placeholder the String to use if the value doesn't completely
187 * fill in the mask.
188 */
189 public void setPlaceholder (String placeholder)
190 {
191 this.placeHolder = placeholder;
192 }
193
194 /**
195 * Returns the character used in place of missing characters when the
196 * value doesn't completely fill the mask.
197 * @return the place holder character
198 */
199 public char getPlaceholderCharacter()
200 {
201 return placeHolderChar;
202 }
203
204 /**
205 * Sets the char to use if the value does not completely fill in the mask.
206 * This is only used if the place holder String has not been set or does
207 * not completely fill in the mask.
208 * @param placeholder the char to use if the value doesn't completely
209 * fill in the mask.
210 */
211 public void setPlaceholderCharacter (char placeholder)
212 {
213 this.placeHolderChar = placeholder;
214 }
215
216 /**
217 * Returns true if stringToValue should return the literal
218 * characters in the mask.
219 * @return true if stringToValue should return the literal
220 * characters in the mask
221 */
222 public boolean getValueContainsLiteralCharacters()
223 {
224 return valueContainsLiteralCharacters;
225 }
226
227 /**
228 * Determines whether stringToValue will return literal characters or not.
229 * @param containsLiteralChars if true, stringToValue will return the
230 * literal characters in the mask, otherwise it will not.
231 */
232 public void setValueContainsLiteralCharacters (boolean containsLiteralChars)
233 {
234 this.valueContainsLiteralCharacters = containsLiteralChars;
235 }
236
237 /**
238 * Sets the mask for this MaskFormatter.
239 * @specnote doesn't actually throw a ParseException even though it is
240 * declared to do so
241 * @param mask the new mask for this MaskFormatter
242 * @throws ParseException if <code>mask</code> is not valid.
243 */
244 public void setMask (String mask) throws ParseException
245 {
246 this.mask = mask;
247
248 // Update the cached maskLength.
249 int end = mask.length() - 1;
250 maskLength = 0;
251 for (int i = 0; i <= end; i++)
252 {
253 // Handle escape characters properly - they don't add to the maskLength
254 // but 2 escape characters in a row is really one escape character and
255 // one literal single quote, so that does add 1 to the maskLength.
256 if (mask.charAt(i) == '\'')
257 {
258 // Escape characters at the end of the mask don't do anything.
259 if (i != end)
260 maskLength++;
261 i++;
262 }
263 else
264 maskLength++;
265 }
266 }
267
268 /**
269 * Installs this MaskFormatter on the JFormattedTextField.
270 * Invokes valueToString to convert the current value from the
271 * JFormattedTextField to a String, then installs the Actions from
272 * getActions, the DocumentFilter from getDocumentFilter, and the
273 * NavigationFilter from getNavigationFilter.
274 *
275 * If valueToString throws a ParseException, this method sets the text
276 * to an empty String and marks the JFormattedTextField as invalid.
277 */
278 public void install (JFormattedTextField ftf)
279 {
280 super.install(ftf);
281 if (ftf != null)
282 {
283 try
284 {
285 valueToString(ftf.getValue());
286 }
287 catch (ParseException pe)
288 {
289 // Set the text to an empty String and mark the JFormattedTextField
290 // as invalid.
291 ftf.setText("");
292 setEditValid(false);
293 }
294 }
295 }
296
297 /**
298 * Parses the text using the mask, valid characters, and invalid characters
299 * to determine the appropriate Object to return. This strips the literal
300 * characters if necessary and invokes super.stringToValue. If the paramter
301 * is invalid for the current mask and valid/invalid character sets this
302 * method will throw a ParseException.
303 *
304 * @param value the String to parse
305 * @throws ParseException if value doesn't match the mask and valid/invalid
306 * character sets
307 */
308 public Object stringToValue (String value) throws ParseException
309 {
310 return super.stringToValue(convertStringToValue(value));
311 }
312
313 private String convertStringToValue(String value)
314 throws ParseException
315 {
316 CPStringBuilder result = new CPStringBuilder();
317 char valueChar;
318 boolean isPlaceHolder;
319
320 int length = mask.length();
321 for (int i = 0, j = 0; j < length; j++)
322 {
323 char maskChar = mask.charAt(j);
324
325 if (i < value.length())
326 {
327 isPlaceHolder = false;
328 valueChar = value.charAt(i);
329 if (maskChar != ESCAPE_CHAR && maskChar != valueChar)
330 {
331 if (invalidChars != null
332 && invalidChars.indexOf(valueChar) != -1)
333 throw new ParseException("Invalid character: " + valueChar, i);
334 if (validChars != null
335 && validChars.indexOf(valueChar) == -1)
336 throw new ParseException("Invalid character: " + valueChar, i);
337 }
338 }
339 else if (placeHolder != null && i < placeHolder.length())
340 {
341 isPlaceHolder = true;
342 valueChar = placeHolder.charAt(i);
343 }
344 else
345 {
346 isPlaceHolder = true;
347 valueChar = placeHolderChar;
348 }
349
350 // This switch block on the mask character checks that the character
351 // within <code>value</code> at that point is valid according to the
352 // mask and also converts to upper/lowercase as needed.
353 switch (maskChar)
354 {
355 case NUM_CHAR:
356 if (! Character.isDigit(valueChar))
357 throw new ParseException("Number expected: " + valueChar, i);
358 result.append(valueChar);
359 i++;
360 break;
361 case UPPERCASE_CHAR:
362 if (! Character.isLetter(valueChar))
363 throw new ParseException("Letter expected", i);
364 result.append(Character.toUpperCase(valueChar));
365 i++;
366 break;
367 case LOWERCASE_CHAR:
368 if (! Character.isLetter(valueChar))
369 throw new ParseException("Letter expected", i);
370 result.append(Character.toLowerCase(valueChar));
371 i++;
372 break;
373 case ALPHANUM_CHAR:
374 if (! Character.isLetterOrDigit(valueChar))
375 throw new ParseException("Letter or number expected", i);
376 result.append(valueChar);
377 i++;
378 break;
379 case LETTER_CHAR:
380 if (! Character.isLetter(valueChar))
381 throw new ParseException("Letter expected", i);
382 result.append(valueChar);
383 i++;
384 break;
385 case HEX_CHAR:
386 if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder)
387 throw new ParseException("Hexadecimal character expected", i);
388 result.append(valueChar);
389 i++;
390 break;
391 case ANYTHING_CHAR:
392 result.append(valueChar);
393 i++;
394 break;
395 case ESCAPE_CHAR:
396 // Escape character, check the next character to make sure that
397 // the literals match
398 j++;
399 if (j < length)
400 {
401 maskChar = mask.charAt(j);
402 if (! isPlaceHolder && getValueContainsLiteralCharacters()
403 && valueChar != maskChar)
404 throw new ParseException ("Invalid character: "+ valueChar, i);
405 if (getValueContainsLiteralCharacters())
406 {
407 result.append(maskChar);
408 }
409 i++;
410 }
411 else if (! isPlaceHolder)
412 throw new ParseException("Bad match at trailing escape: ", i);
413 break;
414 default:
415 if (! isPlaceHolder && getValueContainsLiteralCharacters()
416 && valueChar != maskChar)
417 throw new ParseException ("Invalid character: "+ valueChar, i);
418 if (getValueContainsLiteralCharacters())
419 {
420 result.append(maskChar);
421 }
422 i++;
423 }
424 }
425 return result.toString();
426 }
427
428 /**
429 * Returns a String representation of the Object value based on the mask.
430 *
431 * @param value the value to convert
432 * @throws ParseException if value is invalid for this mask and valid/invalid
433 * character sets
434 */
435 public String valueToString(Object value) throws ParseException
436 {
437 String string = value != null ? value.toString() : "";
438 return convertValueToString(string);
439 }
440
441 /**
442 * This method takes in a String and runs it through the mask to make
443 * sure that it is valid. If <code>convert</code> is true, it also
444 * converts letters to upper/lowercase as required by the mask.
445 * @param value the String to convert
446 * @return the converted String
447 * @throws ParseException if the given String isn't valid for the mask
448 */
449 private String convertValueToString(String value)
450 throws ParseException
451 {
452 CPStringBuilder result = new CPStringBuilder();
453 char valueChar;
454 boolean isPlaceHolder;
455
456 int length = mask.length();
457 for (int i = 0, j = 0; j < length; j++)
458 {
459 char maskChar = mask.charAt(j);
460 if (i < value.length())
461 {
462 isPlaceHolder = false;
463 valueChar = value.charAt(i);
464 if (maskChar != ESCAPE_CHAR && valueChar != maskChar)
465 {
466 if (invalidChars != null
467 && invalidChars.indexOf(valueChar) != -1)
468 throw new ParseException("Invalid character: " + valueChar,
469 i);
470 if (validChars != null && validChars.indexOf(valueChar) == -1)
471 throw new ParseException("Invalid character: " + valueChar +" maskChar: " + maskChar,
472 i);
473 }
474 }
475 else if (placeHolder != null && i < placeHolder.length())
476 {
477 isPlaceHolder = true;
478 valueChar = placeHolder.charAt(i);
479 }
480 else
481 {
482 isPlaceHolder = true;
483 valueChar = placeHolderChar;
484 }
485
486 // This switch block on the mask character checks that the character
487 // within <code>value</code> at that point is valid according to the
488 // mask and also converts to upper/lowercase as needed.
489 switch (maskChar)
490 {
491 case NUM_CHAR:
492 if ( ! isPlaceHolder && ! Character.isDigit(valueChar))
493 throw new ParseException("Number expected: " + valueChar, i);
494 result.append(valueChar);
495 i++;
496 break;
497 case UPPERCASE_CHAR:
498 if (! Character.isLetter(valueChar))
499 throw new ParseException("Letter expected", i);
500 result.append(Character.toUpperCase(valueChar));
501 i++;
502 break;
503 case LOWERCASE_CHAR:
504 if (! Character.isLetter(valueChar))
505 throw new ParseException("Letter expected", i);
506 result.append(Character.toLowerCase(valueChar));
507 i++;
508 break;
509 case ALPHANUM_CHAR:
510 if (! Character.isLetterOrDigit(valueChar))
511 throw new ParseException("Letter or number expected", i);
512 result.append(valueChar);
513 i++;
514 break;
515 case LETTER_CHAR:
516 if (! Character.isLetter(valueChar))
517 throw new ParseException("Letter expected", i);
518 result.append(valueChar);
519 i++;
520 break;
521 case HEX_CHAR:
522 if (hexString.indexOf(valueChar) == -1 && ! isPlaceHolder)
523 throw new ParseException("Hexadecimal character expected", i);
524 result.append(valueChar);
525 i++;
526 break;
527 case ANYTHING_CHAR:
528 result.append(valueChar);
529 i++;
530 break;
531 case ESCAPE_CHAR:
532 // Escape character, check the next character to make sure that
533 // the literals match
534 j++;
535 if (j < length)
536 {
537 maskChar = mask.charAt(j);
538 if (! isPlaceHolder && getValueContainsLiteralCharacters()
539 && valueChar != maskChar)
540 throw new ParseException ("Invalid character: "+ valueChar, i);
541 if (getValueContainsLiteralCharacters())
542 i++;
543 result.append(maskChar);
544 }
545 break;
546 default:
547 if (! isPlaceHolder && getValueContainsLiteralCharacters()
548 && valueChar != maskChar)
549 throw new ParseException ("Invalid character: "+ valueChar, i);
550 if (getValueContainsLiteralCharacters())
551 i++;
552 result.append(maskChar);
553 }
554 }
555 return result.toString();
556 }
557
558 }