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