blob: 78cc35b4bec02c390f87c402e2713ad1fa786afd [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2000-2003 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.swing.text;
27
28import java.io.*;
29import java.text.*;
30import java.util.*;
31import javax.swing.*;
32import javax.swing.text.*;
33
34/**
35 * <code>MaskFormatter</code> is used to format and edit strings. The behavior
36 * of a <code>MaskFormatter</code> is controlled by way of a String mask
37 * that specifies the valid characters that can be contained at a particular
38 * location in the <code>Document</code> model. The following characters can
39 * be specified:
40 *
41 * <table border=1 summary="Valid characters and their descriptions">
42 * <tr>
43 * <th>Character&nbsp;</th>
44 * <th><p align="left">Description</p></th>
45 * </tr>
46 * <tr>
47 * <td>#</td>
48 * <td>Any valid number, uses <code>Character.isDigit</code>.</td>
49 * </tr>
50 * <tr>
51 * <td>'</td>
52 * <td>Escape character, used to escape any of the
53 * special formatting characters.</td>
54 * </tr>
55 * <tr>
56 * <td>U</td><td>Any character (<code>Character.isLetter</code>). All
57 * lowercase letters are mapped to upper case.</td>
58 * </tr>
59 * <tr><td>L</td><td>Any character (<code>Character.isLetter</code>). All
60 * upper case letters are mapped to lower case.</td>
61 * </tr>
62 * <tr><td>A</td><td>Any character or number (<code>Character.isLetter</code>
63 * or <code>Character.isDigit</code>)</td>
64 * </tr>
65 * <tr><td>?</td><td>Any character
66 * (<code>Character.isLetter</code>).</td>
67 * </tr>
68 * <tr><td>*</td><td>Anything.</td></tr>
69 * <tr><td>H</td><td>Any hex character (0-9, a-f or A-F).</td></tr>
70 * </table>
71 *
72 * <p>
73 * Typically characters correspond to one char, but in certain languages this
74 * is not the case. The mask is on a per character basis, and will thus
75 * adjust to fit as many chars as are needed.
76 * <p>
77 * You can further restrict the characters that can be input by the
78 * <code>setInvalidCharacters</code> and <code>setValidCharacters</code>
79 * methods. <code>setInvalidCharacters</code> allows you to specify
80 * which characters are not legal. <code>setValidCharacters</code> allows
81 * you to specify which characters are valid. For example, the following
82 * code block is equivalent to a mask of '0xHHH' with no invalid/valid
83 * characters:
84 * <pre>
85 * MaskFormatter formatter = new MaskFormatter("0x***");
86 * formatter.setValidCharacters("0123456789abcdefABCDEF");
87 * </pre>
88 * <p>
89 * When initially formatting a value if the length of the string is
90 * less than the length of the mask, two things can happen. Either
91 * the placeholder string will be used, or the placeholder character will
92 * be used. Precedence is given to the placeholder string. For example:
93 * <pre>
94 * MaskFormatter formatter = new MaskFormatter("###-####");
95 * formatter.setPlaceholderCharacter('_');
96 * formatter.getDisplayValue(tf, "123");
97 * </pre>
98 * <p>
99 * Would result in the string '123-____'. If
100 * <code>setPlaceholder("555-1212")</code> was invoked '123-1212' would
101 * result. The placeholder String is only used on the initial format,
102 * on subsequent formats only the placeholder character will be used.
103 * <p>
104 * If a <code>MaskFormatter</code> is configured to only allow valid characters
105 * (<code>setAllowsInvalid(false)</code>) literal characters will be skipped as
106 * necessary when editing. Consider a <code>MaskFormatter</code> with
107 * the mask "###-####" and current value "555-1212". Using the right
108 * arrow key to navigate through the field will result in (| indicates the
109 * position of the caret):
110 * <pre>
111 * |555-1212
112 * 5|55-1212
113 * 55|5-1212
114 * 555-|1212
115 * 555-1|212
116 * </pre>
117 * The '-' is a literal (non-editable) character, and is skipped.
118 * <p>
119 * Similar behavior will result when editing. Consider inserting the string
120 * '123-45' and '12345' into the <code>MaskFormatter</code> in the
121 * previous example. Both inserts will result in the same String,
122 * '123-45__'. When <code>MaskFormatter</code>
123 * is processing the insert at character position 3 (the '-'), two things can
124 * happen:
125 * <ol>
126 * <li>If the inserted character is '-', it is accepted.
127 * <li>If the inserted character matches the mask for the next non-literal
128 * character, it is accepted at the new location.
129 * <li>Anything else results in an invalid edit
130 * </ol>
131 * <p>
132 * By default <code>MaskFormatter</code> will not allow invalid edits, you can
133 * change this with the <code>setAllowsInvalid</code> method, and will
134 * commit edits on valid edits (use the <code>setCommitsOnValidEdit</code> to
135 * change this).
136 * <p>
137 * By default, <code>MaskFormatter</code> is in overwrite mode. That is as
138 * characters are typed a new character is not inserted, rather the character
139 * at the current location is replaced with the newly typed character. You
140 * can change this behavior by way of the method <code>setOverwriteMode</code>.
141 * <p>
142 * <strong>Warning:</strong>
143 * Serialized objects of this class will not be compatible with
144 * future Swing releases. The current serialization support is
145 * appropriate for short term storage or RMI between applications running
146 * the same version of Swing. As of 1.4, support for long term storage
147 * of all JavaBeans<sup><font size="-2">TM</font></sup>
148 * has been added to the <code>java.beans</code> package.
149 * Please see {@link java.beans.XMLEncoder}.
150 *
151 * @since 1.4
152 */
153public class MaskFormatter extends DefaultFormatter {
154 // Potential values in mask.
155 private static final char DIGIT_KEY = '#';
156 private static final char LITERAL_KEY = '\'';
157 private static final char UPPERCASE_KEY = 'U';
158 private static final char LOWERCASE_KEY = 'L';
159 private static final char ALPHA_NUMERIC_KEY = 'A';
160 private static final char CHARACTER_KEY = '?';
161 private static final char ANYTHING_KEY = '*';
162 private static final char HEX_KEY = 'H';
163
164 private static final MaskCharacter[] EmptyMaskChars = new MaskCharacter[0];
165
166 /** The user specified mask. */
167 private String mask;
168
169 private transient MaskCharacter[] maskChars;
170
171 /** List of valid characters. */
172 private String validCharacters;
173
174 /** List of invalid characters. */
175 private String invalidCharacters;
176
177 /** String used for the passed in value if it does not completely
178 * fill the mask. */
179 private String placeholderString;
180
181 /** String used to represent characters not present. */
182 private char placeholder;
183
184 /** Indicates if the value contains the literal characters. */
185 private boolean containsLiteralChars;
186
187
188 /**
189 * Creates a MaskFormatter with no mask.
190 */
191 public MaskFormatter() {
192 setAllowsInvalid(false);
193 containsLiteralChars = true;
194 maskChars = EmptyMaskChars;
195 placeholder = ' ';
196 }
197
198 /**
199 * Creates a <code>MaskFormatter</code> with the specified mask.
200 * A <code>ParseException</code>
201 * will be thrown if <code>mask</code> is an invalid mask.
202 *
203 * @throws ParseException if mask does not contain valid mask characters
204 */
205 public MaskFormatter(String mask) throws ParseException {
206 this();
207 setMask(mask);
208 }
209
210 /**
211 * Sets the mask dictating the legal characters.
212 * This will throw a <code>ParseException</code> if <code>mask</code> is
213 * not valid.
214 *
215 * @throws ParseException if mask does not contain valid mask characters
216 */
217 public void setMask(String mask) throws ParseException {
218 this.mask = mask;
219 updateInternalMask();
220 }
221
222 /**
223 * Returns the formatting mask.
224 *
225 * @return Mask dictating legal character values.
226 */
227 public String getMask() {
228 return mask;
229 }
230
231 /**
232 * Allows for further restricting of the characters that can be input.
233 * Only characters specified in the mask, not in the
234 * <code>invalidCharacters</code>, and in
235 * <code>validCharacters</code> will be allowed to be input. Passing
236 * in null (the default) implies the valid characters are only bound
237 * by the mask and the invalid characters.
238 *
239 * @param validCharacters If non-null, specifies legal characters.
240 */
241 public void setValidCharacters(String validCharacters) {
242 this.validCharacters = validCharacters;
243 }
244
245 /**
246 * Returns the valid characters that can be input.
247 *
248 * @return Legal characters
249 */
250 public String getValidCharacters() {
251 return validCharacters;
252 }
253
254 /**
255 * Allows for further restricting of the characters that can be input.
256 * Only characters specified in the mask, not in the
257 * <code>invalidCharacters</code>, and in
258 * <code>validCharacters</code> will be allowed to be input. Passing
259 * in null (the default) implies the valid characters are only bound
260 * by the mask and the valid characters.
261 *
262 * @param invalidCharacters If non-null, specifies illegal characters.
263 */
264 public void setInvalidCharacters(String invalidCharacters) {
265 this.invalidCharacters = invalidCharacters;
266 }
267
268 /**
269 * Returns the characters that are not valid for input.
270 *
271 * @return illegal characters.
272 */
273 public String getInvalidCharacters() {
274 return invalidCharacters;
275 }
276
277 /**
278 * Sets the string to use if the value does not completely fill in
279 * the mask. A null value implies the placeholder char should be used.
280 *
281 * @param placeholder String used when formatting if the value does not
282 * completely fill the mask
283 */
284 public void setPlaceholder(String placeholder) {
285 this.placeholderString = placeholder;
286 }
287
288 /**
289 * Returns the String to use if the value does not completely fill
290 * in the mask.
291 *
292 * @return String used when formatting if the value does not
293 * completely fill the mask
294 */
295 public String getPlaceholder() {
296 return placeholderString;
297 }
298
299 /**
300 * Sets the character to use in place of characters that are not present
301 * in the value, ie the user must fill them in. The default value is
302 * a space.
303 * <p>
304 * This is only applicable if the placeholder string has not been
305 * specified, or does not completely fill in the mask.
306 *
307 * @param placeholder Character used when formatting if the value does not
308 * completely fill the mask
309 */
310 public void setPlaceholderCharacter(char placeholder) {
311 this.placeholder = placeholder;
312 }
313
314 /**
315 * Returns the character to use in place of characters that are not present
316 * in the value, ie the user must fill them in.
317 *
318 * @return Character used when formatting if the value does not
319 * completely fill the mask
320 */
321 public char getPlaceholderCharacter() {
322 return placeholder;
323 }
324
325 /**
326 * If true, the returned value and set value will also contain the literal
327 * characters in mask.
328 * <p>
329 * For example, if the mask is <code>'(###) ###-####'</code>, the
330 * current value is <code>'(415) 555-1212'</code>, and
331 * <code>valueContainsLiteralCharacters</code> is
332 * true <code>stringToValue</code> will return
333 * <code>'(415) 555-1212'</code>. On the other hand, if
334 * <code>valueContainsLiteralCharacters</code> is false,
335 * <code>stringToValue</code> will return <code>'4155551212'</code>.
336 *
337 * @param containsLiteralChars Used to indicate if literal characters in
338 * mask should be returned in stringToValue
339 */
340 public void setValueContainsLiteralCharacters(
341 boolean containsLiteralChars) {
342 this.containsLiteralChars = containsLiteralChars;
343 }
344
345 /**
346 * Returns true if <code>stringToValue</code> should return literal
347 * characters in the mask.
348 *
349 * @return True if literal characters in mask should be returned in
350 * stringToValue
351 */
352 public boolean getValueContainsLiteralCharacters() {
353 return containsLiteralChars;
354 }
355
356 /**
357 * Parses the text, returning the appropriate Object representation of
358 * the String <code>value</code>. This strips the literal characters as
359 * necessary and invokes supers <code>stringToValue</code>, so that if
360 * you have specified a value class (<code>setValueClass</code>) an
361 * instance of it will be created. This will throw a
362 * <code>ParseException</code> if the value does not match the current
363 * mask. Refer to {@link #setValueContainsLiteralCharacters} for details
364 * on how literals are treated.
365 *
366 * @throws ParseException if there is an error in the conversion
367 * @param value String to convert
368 * @see #setValueContainsLiteralCharacters
369 * @return Object representation of text
370 */
371 public Object stringToValue(String value) throws ParseException {
372 return stringToValue(value, true);
373 }
374
375 /**
376 * Returns a String representation of the Object <code>value</code>
377 * based on the mask. Refer to
378 * {@link #setValueContainsLiteralCharacters} for details
379 * on how literals are treated.
380 *
381 * @throws ParseException if there is an error in the conversion
382 * @param value Value to convert
383 * @see #setValueContainsLiteralCharacters
384 * @return String representation of value
385 */
386 public String valueToString(Object value) throws ParseException {
387 String sValue = (value == null) ? "" : value.toString();
388 StringBuffer result = new StringBuffer();
389 String placeholder = getPlaceholder();
390 int[] valueCounter = { 0 };
391
392 append(result, sValue, valueCounter, placeholder, maskChars);
393 return result.toString();
394 }
395
396 /**
397 * Installs the <code>DefaultFormatter</code> onto a particular
398 * <code>JFormattedTextField</code>.
399 * This will invoke <code>valueToString</code> to convert the
400 * current value from the <code>JFormattedTextField</code> to
401 * a String. This will then install the <code>Action</code>s from
402 * <code>getActions</code>, the <code>DocumentFilter</code>
403 * returned from <code>getDocumentFilter</code> and the
404 * <code>NavigationFilter</code> returned from
405 * <code>getNavigationFilter</code> onto the
406 * <code>JFormattedTextField</code>.
407 * <p>
408 * Subclasses will typically only need to override this if they
409 * wish to install additional listeners on the
410 * <code>JFormattedTextField</code>.
411 * <p>
412 * If there is a <code>ParseException</code> in converting the
413 * current value to a String, this will set the text to an empty
414 * String, and mark the <code>JFormattedTextField</code> as being
415 * in an invalid state.
416 * <p>
417 * While this is a public method, this is typically only useful
418 * for subclassers of <code>JFormattedTextField</code>.
419 * <code>JFormattedTextField</code> will invoke this method at
420 * the appropriate times when the value changes, or its internal
421 * state changes.
422 *
423 * @param ftf JFormattedTextField to format for, may be null indicating
424 * uninstall from current JFormattedTextField.
425 */
426 public void install(JFormattedTextField ftf) {
427 super.install(ftf);
428 // valueToString doesn't throw, but stringToValue does, need to
429 // update the editValid state appropriately
430 if (ftf != null) {
431 Object value = ftf.getValue();
432
433 try {
434 stringToValue(valueToString(value));
435 } catch (ParseException pe) {
436 setEditValid(false);
437 }
438 }
439 }
440
441 /**
442 * Actual <code>stringToValue</code> implementation.
443 * If <code>completeMatch</code> is true, the value must exactly match
444 * the mask, on the other hand if <code>completeMatch</code> is false
445 * the string must match the mask or the placeholder string.
446 */
447 private Object stringToValue(String value, boolean completeMatch) throws
448 ParseException {
449 int errorOffset = -1;
450
451 if ((errorOffset = getInvalidOffset(value, completeMatch)) == -1) {
452 if (!getValueContainsLiteralCharacters()) {
453 value = stripLiteralChars(value);
454 }
455 return super.stringToValue(value);
456 }
457 throw new ParseException("stringToValue passed invalid value",
458 errorOffset);
459 }
460
461 /**
462 * Returns -1 if the passed in string is valid, otherwise the index of
463 * the first bogus character is returned.
464 */
465 private int getInvalidOffset(String string, boolean completeMatch) {
466 int iLength = string.length();
467
468 if (iLength != getMaxLength()) {
469 // trivially false
470 return iLength;
471 }
472 for (int counter = 0, max = string.length(); counter < max; counter++){
473 char aChar = string.charAt(counter);
474
475 if (!isValidCharacter(counter, aChar) &&
476 (completeMatch || !isPlaceholder(counter, aChar))) {
477 return counter;
478 }
479 }
480 return -1;
481 }
482
483 /**
484 * Invokes <code>append</code> on the mask characters in
485 * <code>mask</code>.
486 */
487 private void append(StringBuffer result, String value, int[] index,
488 String placeholder, MaskCharacter[] mask)
489 throws ParseException {
490 for (int counter = 0, maxCounter = mask.length;
491 counter < maxCounter; counter++) {
492 mask[counter].append(result, value, index, placeholder);
493 }
494 }
495
496 /**
497 * Updates the internal representation of the mask.
498 */
499 private void updateInternalMask() throws ParseException {
500 String mask = getMask();
501 ArrayList fixed = new ArrayList();
502 ArrayList temp = fixed;
503
504 if (mask != null) {
505 for (int counter = 0, maxCounter = mask.length();
506 counter < maxCounter; counter++) {
507 char maskChar = mask.charAt(counter);
508
509 switch (maskChar) {
510 case DIGIT_KEY:
511 temp.add(new DigitMaskCharacter());
512 break;
513 case LITERAL_KEY:
514 if (++counter < maxCounter) {
515 maskChar = mask.charAt(counter);
516 temp.add(new LiteralCharacter(maskChar));
517 }
518 // else: Could actually throw if else
519 break;
520 case UPPERCASE_KEY:
521 temp.add(new UpperCaseCharacter());
522 break;
523 case LOWERCASE_KEY:
524 temp.add(new LowerCaseCharacter());
525 break;
526 case ALPHA_NUMERIC_KEY:
527 temp.add(new AlphaNumericCharacter());
528 break;
529 case CHARACTER_KEY:
530 temp.add(new CharCharacter());
531 break;
532 case ANYTHING_KEY:
533 temp.add(new MaskCharacter());
534 break;
535 case HEX_KEY:
536 temp.add(new HexCharacter());
537 break;
538 default:
539 temp.add(new LiteralCharacter(maskChar));
540 break;
541 }
542 }
543 }
544 if (fixed.size() == 0) {
545 maskChars = EmptyMaskChars;
546 }
547 else {
548 maskChars = new MaskCharacter[fixed.size()];
549 fixed.toArray(maskChars);
550 }
551 }
552
553 /**
554 * Returns the MaskCharacter at the specified location.
555 */
556 private MaskCharacter getMaskCharacter(int index) {
557 if (index >= maskChars.length) {
558 return null;
559 }
560 return maskChars[index];
561 }
562
563 /**
564 * Returns true if the placeholder character matches aChar.
565 */
566 private boolean isPlaceholder(int index, char aChar) {
567 return (getPlaceholderCharacter() == aChar);
568 }
569
570 /**
571 * Returns true if the passed in character matches the mask at the
572 * specified location.
573 */
574 private boolean isValidCharacter(int index, char aChar) {
575 return getMaskCharacter(index).isValidCharacter(aChar);
576 }
577
578 /**
579 * Returns true if the character at the specified location is a literal,
580 * that is it can not be edited.
581 */
582 private boolean isLiteral(int index) {
583 return getMaskCharacter(index).isLiteral();
584 }
585
586 /**
587 * Returns the maximum length the text can be.
588 */
589 private int getMaxLength() {
590 return maskChars.length;
591 }
592
593 /**
594 * Returns the literal character at the specified location.
595 */
596 private char getLiteral(int index) {
597 return getMaskCharacter(index).getChar((char)0);
598 }
599
600 /**
601 * Returns the character to insert at the specified location based on
602 * the passed in character. This provides a way to map certain sets
603 * of characters to alternative values (lowercase to
604 * uppercase...).
605 */
606 private char getCharacter(int index, char aChar) {
607 return getMaskCharacter(index).getChar(aChar);
608 }
609
610 /**
611 * Removes the literal characters from the passed in string.
612 */
613 private String stripLiteralChars(String string) {
614 StringBuffer sb = null;
615 int last = 0;
616
617 for (int counter = 0, max = string.length(); counter < max; counter++){
618 if (isLiteral(counter)) {
619 if (sb == null) {
620 sb = new StringBuffer();
621 if (counter > 0) {
622 sb.append(string.substring(0, counter));
623 }
624 last = counter + 1;
625 }
626 else if (last != counter) {
627 sb.append(string.substring(last, counter));
628 }
629 last = counter + 1;
630 }
631 }
632 if (sb == null) {
633 // Assume the mask isn't all literals.
634 return string;
635 }
636 else if (last != string.length()) {
637 if (sb == null) {
638 return string.substring(last);
639 }
640 sb.append(string.substring(last));
641 }
642 return sb.toString();
643 }
644
645
646 /**
647 * Subclassed to update the internal representation of the mask after
648 * the default read operation has completed.
649 */
650 private void readObject(ObjectInputStream s)
651 throws IOException, ClassNotFoundException {
652 s.defaultReadObject();
653 try {
654 updateInternalMask();
655 } catch (ParseException pe) {
656 // assert();
657 }
658 }
659
660 /**
661 * Returns true if the MaskFormatter allows invalid, or
662 * the offset is less than the max length and the character at
663 * <code>offset</code> is a literal.
664 */
665 boolean isNavigatable(int offset) {
666 if (!getAllowsInvalid()) {
667 return (offset < getMaxLength() && !isLiteral(offset));
668 }
669 return true;
670 }
671
672 /*
673 * Returns true if the operation described by <code>rh</code> will
674 * result in a legal edit. This may set the <code>value</code>
675 * field of <code>rh</code>.
676 * <p>
677 * This is overriden to return true for a partial match.
678 */
679 boolean isValidEdit(ReplaceHolder rh) {
680 if (!getAllowsInvalid()) {
681 String newString = getReplaceString(rh.offset, rh.length, rh.text);
682
683 try {
684 rh.value = stringToValue(newString, false);
685
686 return true;
687 } catch (ParseException pe) {
688 return false;
689 }
690 }
691 return true;
692 }
693
694 /**
695 * This method does the following (assuming !getAllowsInvalid()):
696 * iterate over the max of the deleted region or the text length, for
697 * each character:
698 * <ol>
699 * <li>If it is valid (matches the mask at the particular position, or
700 * matches the literal character at the position), allow it
701 * <li>Else if the position identifies a literal character, add it. This
702 * allows for the user to paste in text that may/may not contain
703 * the literals. For example, in pasing in 5551212 into ###-####
704 * when the 1 is evaluated it is illegal (by the first test), but there
705 * is a literal at this position (-), so it is used. NOTE: This has
706 * a problem that you can't tell (without looking ahead) if you should
707 * eat literals in the text. For example, if you paste '555' into
708 * #5##, should it result in '5555' or '555 '? The current code will
709 * result in the latter, which feels a little better as selecting
710 * text than pasting will always result in the same thing.
711 * <li>Else if at the end of the inserted text, the replace the item with
712 * the placeholder
713 * <li>Otherwise the insert is bogus and false is returned.
714 * </ol>
715 */
716 boolean canReplace(ReplaceHolder rh) {
717 // This method is rather long, but much of the burden is in
718 // maintaining a String and swapping to a StringBuffer only if
719 // absolutely necessary.
720 if (!getAllowsInvalid()) {
721 StringBuffer replace = null;
722 String text = rh.text;
723 int tl = (text != null) ? text.length() : 0;
724
725 if (tl == 0 && rh.length == 1 && getFormattedTextField().
726 getSelectionStart() != rh.offset) {
727 // Backspace, adjust to actually delete next non-literal.
728 while (rh.offset > 0 && isLiteral(rh.offset)) {
729 rh.offset--;
730 }
731 }
732 int max = Math.min(getMaxLength() - rh.offset,
733 Math.max(tl, rh.length));
734 for (int counter = 0, textIndex = 0; counter < max; counter++) {
735 if (textIndex < tl && isValidCharacter(rh.offset + counter,
736 text.charAt(textIndex))) {
737 char aChar = text.charAt(textIndex);
738 if (aChar != getCharacter(rh.offset + counter, aChar)) {
739 if (replace == null) {
740 replace = new StringBuffer();
741 if (textIndex > 0) {
742 replace.append(text.substring(0, textIndex));
743 }
744 }
745 }
746 if (replace != null) {
747 replace.append(getCharacter(rh.offset + counter,
748 aChar));
749 }
750 textIndex++;
751 }
752 else if (isLiteral(rh.offset + counter)) {
753 if (replace != null) {
754 replace.append(getLiteral(rh.offset + counter));
755 if (textIndex < tl) {
756 max = Math.min(max + 1, getMaxLength() -
757 rh.offset);
758 }
759 }
760 else if (textIndex > 0) {
761 replace = new StringBuffer(max);
762 replace.append(text.substring(0, textIndex));
763 replace.append(getLiteral(rh.offset + counter));
764 if (textIndex < tl) {
765 // Evaluate the character in text again.
766 max = Math.min(max + 1, getMaxLength() -
767 rh.offset);
768 }
769 else if (rh.cursorPosition == -1) {
770 rh.cursorPosition = rh.offset + counter;
771 }
772 }
773 else {
774 rh.offset++;
775 rh.length--;
776 counter--;
777 max--;
778 }
779 }
780 else if (textIndex >= tl) {
781 // placeholder
782 if (replace == null) {
783 replace = new StringBuffer();
784 if (text != null) {
785 replace.append(text);
786 }
787 }
788 replace.append(getPlaceholderCharacter());
789 if (tl > 0 && rh.cursorPosition == -1) {
790 rh.cursorPosition = rh.offset + counter;
791 }
792 }
793 else {
794 // Bogus character.
795 return false;
796 }
797 }
798 if (replace != null) {
799 rh.text = replace.toString();
800 }
801 else if (text != null && rh.offset + tl > getMaxLength()) {
802 rh.text = text.substring(0, getMaxLength() - rh.offset);
803 }
804 if (getOverwriteMode() && rh.text != null) {
805 rh.length = rh.text.length();
806 }
807 }
808 return super.canReplace(rh);
809 }
810
811
812 //
813 // Interal classes used to represent the mask.
814 //
815 private class MaskCharacter {
816 /**
817 * Subclasses should override this returning true if the instance
818 * represents a literal character. The default implementation
819 * returns false.
820 */
821 public boolean isLiteral() {
822 return false;
823 }
824
825 /**
826 * Returns true if <code>aChar</code> is a valid reprensentation of
827 * the receiver. The default implementation returns true if the
828 * receiver represents a literal character and <code>getChar</code>
829 * == aChar. Otherwise, this will return true is <code>aChar</code>
830 * is contained in the valid characters and not contained
831 * in the invalid characters.
832 */
833 public boolean isValidCharacter(char aChar) {
834 if (isLiteral()) {
835 return (getChar(aChar) == aChar);
836 }
837
838 aChar = getChar(aChar);
839
840 String filter = getValidCharacters();
841
842 if (filter != null && filter.indexOf(aChar) == -1) {
843 return false;
844 }
845 filter = getInvalidCharacters();
846 if (filter != null && filter.indexOf(aChar) != -1) {
847 return false;
848 }
849 return true;
850 }
851
852 /**
853 * Returns the character to insert for <code>aChar</code>. The
854 * default implementation returns <code>aChar</code>. Subclasses
855 * that wish to do some sort of mapping, perhaps lower case to upper
856 * case should override this and do the necessary mapping.
857 */
858 public char getChar(char aChar) {
859 return aChar;
860 }
861
862 /**
863 * Appends the necessary character in <code>formatting</code> at
864 * <code>index</code> to <code>buff</code>.
865 */
866 public void append(StringBuffer buff, String formatting, int[] index,
867 String placeholder)
868 throws ParseException {
869 boolean inString = index[0] < formatting.length();
870 char aChar = inString ? formatting.charAt(index[0]) : 0;
871
872 if (isLiteral()) {
873 buff.append(getChar(aChar));
874 if (getValueContainsLiteralCharacters()) {
875 if (inString && aChar != getChar(aChar)) {
876 throw new ParseException("Invalid character: " +
877 aChar, index[0]);
878 }
879 index[0] = index[0] + 1;
880 }
881 }
882 else if (index[0] >= formatting.length()) {
883 if (placeholder != null && index[0] < placeholder.length()) {
884 buff.append(placeholder.charAt(index[0]));
885 }
886 else {
887 buff.append(getPlaceholderCharacter());
888 }
889 index[0] = index[0] + 1;
890 }
891 else if (isValidCharacter(aChar)) {
892 buff.append(getChar(aChar));
893 index[0] = index[0] + 1;
894 }
895 else {
896 throw new ParseException("Invalid character: " + aChar,
897 index[0]);
898 }
899 }
900 }
901
902
903 /**
904 * Used to represent a fixed character in the mask.
905 */
906 private class LiteralCharacter extends MaskCharacter {
907 private char fixedChar;
908
909 public LiteralCharacter(char fixedChar) {
910 this.fixedChar = fixedChar;
911 }
912
913 public boolean isLiteral() {
914 return true;
915 }
916
917 public char getChar(char aChar) {
918 return fixedChar;
919 }
920 }
921
922
923 /**
924 * Represents a number, uses <code>Character.isDigit</code>.
925 */
926 private class DigitMaskCharacter extends MaskCharacter {
927 public boolean isValidCharacter(char aChar) {
928 return (Character.isDigit(aChar) &&
929 super.isValidCharacter(aChar));
930 }
931 }
932
933
934 /**
935 * Represents a character, lower case letters are mapped to upper case
936 * using <code>Character.toUpperCase</code>.
937 */
938 private class UpperCaseCharacter extends MaskCharacter {
939 public boolean isValidCharacter(char aChar) {
940 return (Character.isLetter(aChar) &&
941 super.isValidCharacter(aChar));
942 }
943
944 public char getChar(char aChar) {
945 return Character.toUpperCase(aChar);
946 }
947 }
948
949
950 /**
951 * Represents a character, upper case letters are mapped to lower case
952 * using <code>Character.toLowerCase</code>.
953 */
954 private class LowerCaseCharacter extends MaskCharacter {
955 public boolean isValidCharacter(char aChar) {
956 return (Character.isLetter(aChar) &&
957 super.isValidCharacter(aChar));
958 }
959
960 public char getChar(char aChar) {
961 return Character.toLowerCase(aChar);
962 }
963 }
964
965
966 /**
967 * Represents either a character or digit, uses
968 * <code>Character.isLetterOrDigit</code>.
969 */
970 private class AlphaNumericCharacter extends MaskCharacter {
971 public boolean isValidCharacter(char aChar) {
972 return (Character.isLetterOrDigit(aChar) &&
973 super.isValidCharacter(aChar));
974 }
975 }
976
977
978 /**
979 * Represents a letter, uses <code>Character.isLetter</code>.
980 */
981 private class CharCharacter extends MaskCharacter {
982 public boolean isValidCharacter(char aChar) {
983 return (Character.isLetter(aChar) &&
984 super.isValidCharacter(aChar));
985 }
986 }
987
988
989 /**
990 * Represents a hex character, 0-9a-fA-F. a-f is mapped to A-F
991 */
992 private class HexCharacter extends MaskCharacter {
993 public boolean isValidCharacter(char aChar) {
994 return ((aChar == '0' || aChar == '1' ||
995 aChar == '2' || aChar == '3' ||
996 aChar == '4' || aChar == '5' ||
997 aChar == '6' || aChar == '7' ||
998 aChar == '8' || aChar == '9' ||
999 aChar == 'a' || aChar == 'A' ||
1000 aChar == 'b' || aChar == 'B' ||
1001 aChar == 'c' || aChar == 'C' ||
1002 aChar == 'd' || aChar == 'D' ||
1003 aChar == 'e' || aChar == 'E' ||
1004 aChar == 'f' || aChar == 'F') &&
1005 super.isValidCharacter(aChar));
1006 }
1007
1008 public char getChar(char aChar) {
1009 if (Character.isDigit(aChar)) {
1010 return aChar;
1011 }
1012 return Character.toUpperCase(aChar);
1013 }
1014 }
1015}