001/*
002 * IzPack - Copyright 2001-2005 Julien Ponge, All Rights Reserved.
003 * 
004 * http://www.izforge.com/izpack/
005 * http://developer.berlios.de/projects/izpack/
006 * 
007 * Copyright 2002 Elmar Grom
008 * 
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *     http://www.apache.org/licenses/LICENSE-2.0
014 *     
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 */
021
022package com.izforge.izpack.panels;
023
024import java.awt.Toolkit;
025import java.awt.event.FocusEvent;
026import java.awt.event.FocusListener;
027import java.awt.event.KeyEvent;
028import java.awt.event.KeyListener;
029import java.util.Map;
030import java.util.StringTokenizer;
031import java.util.Vector;
032
033import javax.swing.JComponent;
034import javax.swing.JLabel;
035import javax.swing.JTextField;
036import javax.swing.event.CaretEvent;
037import javax.swing.event.CaretListener;
038
039import org.apache.regexp.RE;
040
041import com.izforge.izpack.installer.InstallData;
042import com.izforge.izpack.util.VariableSubstitutor;
043
044/*---------------------------------------------------------------------------*/
045/**
046 * This class assists the user in entering serial numbers. <BR>
047 * <BR>
048 * Serial numbers, license number, CD keys and the like are often lenghty alpha-numerical numbers.
049 * In many cases they are devided into multiple parts by dash or point separators. Entering these in
050 * a single text field can be a frustrating experience for the user. This class provides a way of
051 * presenting the user with an assembly of input fields that are arranged in the same way as the
052 * key, with the separators already in place. Immideate testing for format compliance if performed
053 * ans soon as each field is completed. In addition, the cursor is automatically advanced to make
054 * entering numbers as painless as possible. <br>
055 * <br>
056 * <b>Formatting:</b>
057 * 
058 * <ul>
059 * <li><code>N:X:Y </code>- numeric field, accepts digits only
060 * <li><code>H:X:Y </code>- hex field, accepts only hexadecimal digits
061 * <li><code>A:X:Y </code>- alpha field, accepts only letters, no digits
062 * <li><code>AN:X:Y</code>- alpha-numeric field, accepts digits and letters
063 * </ul>
064 * <b>Example:</b> <br>
065 * <br>
066 * <code>"N:4:4 - H:6:6 - AN:3:3 x A:5:5"</code><br>
067 * <br>
068 * This formatting string will produce a serial number field consisting of four separate input
069 * fields. The fisrt input field will accept four numeric digits, the second six hexa-decimal
070 * digits, the third three alpha-numeric digits and the fourth five letters. The first three input
071 * fields will be separated by '-' and the third and fourth by 'x'. The following snapshot was
072 * obtained with this setting: <br>
073 * <br>
074 * <img src="doc-files/RuleInputField-1.gif"/>
075 * 
076 * @version 0.0.1 / 10/19/02
077 * @author Elmar Grom
078 */
079/*---------------------------------------------------------------------------*/
080public class RuleInputField extends JComponent implements KeyListener, FocusListener,
081        CaretListener, ProcessingClient
082{
083
084    /**
085     * 
086     */
087    private static final long serialVersionUID = 3832616275124958257L;
088
089    /**
090     * Used to specify the retsult format. This constant specifies to return the contents of all
091     * fields concatenated into one long string, with separation between each component.
092     */
093    public static final int PLAIN_STRING = 1;
094
095    /**
096     * Used to specify the retsult format. This constant specifies to return the contents of all
097     * fields together with all separators as specified in the field format concatenated into one
098     * long string. In this case the resulting string looks just like the user saw it during data
099     * entry
100     */
101    public static final int DISPLAY_FORMAT = 2;
102
103    /**
104     * Used to specify the retsult format. This constant specifies to return the contents of all
105     * fields concatenated into one long string, with a special separator string inserted in between
106     * the individual components.
107     */
108    public static final int SPECIAL_SEPARATOR = 3;
109
110    /**
111     * Used to specify the retsult format. This constant specifies to return the contents of all
112     * fields in a somehow modified way. How the content is modified depends on the operation
113     * performed by a encryption service class. The class must be provided at object instatiation.
114     */
115    public static final int ENCRYPTED = 4;
116
117    /** Used internally to identify the default setting for the return format. */
118    private static int DEFAULT = DISPLAY_FORMAT;
119
120    private Vector items = new Vector();
121
122    /**
123     * This <code>Vector</code> holds a reference to each input field, in the order in which they
124     * appear on the screen.
125     */
126    private Vector inputFields = new Vector();
127
128    private boolean hasParams = false;
129
130    private Map validatorParams;
131
132    private RuleTextField activeField;
133
134    private boolean backstep = false;
135
136    private Toolkit toolkit;
137
138    private String separator;
139
140    private int resultFormat = DEFAULT;
141
142    private InstallData idata = null;
143    /**
144     * Holds an instance of the <code>Validator</code> if one was specified and available
145     */
146    private Validator validationService;
147
148    /**
149     * Holds an instance of the <code>Processor</code> if one was specified and available
150     */
151    private Processor encryptionService;
152
153    /**
154     * @return true if this instance has any parameters to pass to the Validator instance.
155     */
156    public boolean hasParams()
157    {
158        return hasParams;
159    }
160
161    /*--------------------------------------------------------------------------*/
162    /**
163     * Constructs a rule input field.
164     * 
165     * @param format a string that specifies the formatting and to a limited degree the behavior of
166     * this field.
167     * @param preset a string that specifies preset values for specific sub-fields.
168     * @param separator a string to be used for separating the contents of individual fields in the
169     * string returned by <code>getText()</code>
170     * @param validator A string that specifies a class to perform validation services. The string
171     * must completely identify the class, so that it can be instantiated. The class must implement
172     * the <code>RuleValidator</code> interface. If an attempt to instantiate this class fails, no
173     * validation will be performed.
174     * @param validatorParams A <code>java.util.Map</code> containing name/ value pairs, which
175     * will be forwarded to the validator.
176     * @param processor A string that specifies a class to perform processing services. The string
177     * must completely identify the class, so that it can be instantiated. The class must implement
178     * the <code>Processor</code> interface. If an attempt to instantiate this class fails, no
179     * processing will be performed. Instead, the text is returned in the default formatting.
180     * @param resultFormat specifies in which format the resulting text should be returned, wehn
181     * <code>getText()</code> is called. The following values are legal:<br>
182     * <ul>
183     * <li>PLAIN_STRING
184     * <li>DISPLAY_FORMAT
185     * <li>SPECIAL_SEPARATOR
186     * <li>ENCRYPTED
187     * </ul>
188     * @param toolkit needed to gain access to <code>beep()</code>
189     */
190    /*--------------------------------------------------------------------------*/
191    public RuleInputField(String format, String preset, String separator, String validator,
192            Map validatorParams, String processor, int resultFormat, Toolkit toolkit,
193            InstallData idata)
194    {
195        this(format, preset, separator, validator, processor, resultFormat, toolkit, idata);
196        this.validatorParams = validatorParams;
197        this.hasParams = true;
198    }
199
200    /*--------------------------------------------------------------------------*/
201    /**
202     * Constructs a rule input field.
203     * 
204     * @param format a string that specifies the formatting and to a limited degree the behavior of
205     * this field.
206     * @param preset a string that specifies preset values for specific sub-fields.
207     * @param separator a string to be used for separating the contents of individual fields in the
208     * string returned by <code>getText()</code>
209     * @param validator A string that specifies a class to perform validation services. The string
210     * must completely identify the class, so that it can be instantiated. The class must implement
211     * the <code>RuleValidator</code> interface. If an attempt to instantiate this class fails, no
212     * validation will be performed.
213     * @param processor A string that specifies a class to perform processing services. The string
214     * must completely identify the class, so that it can be instantiated. The class must implement
215     * the <code>Processor</code> interface. If an attempt to instantiate this class fails, no
216     * processing will be performed. Instead, the text is returned in the default formatting.
217     * @param resultFormat specifies in which format the resulting text should be returned, wehn
218     * <code>getText()</code> is called. The following values are legal:<br>
219     * <ul>
220     * <li>PLAIN_STRING
221     * <li>DISPLAY_FORMAT
222     * <li>SPECIAL_SEPARATOR
223     * <li>ENCRYPTED
224     * </ul>
225     * @param toolkit needed to gain access to <code>beep()</code>
226     */
227    /*--------------------------------------------------------------------------*/
228    public RuleInputField(String format, String preset, String separator, String validator,
229            String processor, int resultFormat, Toolkit toolkit, InstallData idata)
230    {
231        this.toolkit = toolkit;
232        this.separator = separator;
233        this.resultFormat = resultFormat;
234        this.idata = idata;
235
236        com.izforge.izpack.gui.FlowLayout layout = new com.izforge.izpack.gui.FlowLayout();
237        layout.setAlignment(com.izforge.izpack.gui.FlowLayout.LEFT);
238        setLayout(layout);
239
240        // ----------------------------------------------------
241        // attempt to create an instance of the Validator
242        // ----------------------------------------------------
243        try
244        {
245            validationService = (Validator) Class.forName(validator).newInstance();
246        }
247        catch (Throwable exception)
248        {
249            validationService = null;
250        }
251
252        // ----------------------------------------------------
253        // attempt to create an instance of the Processor
254        // ----------------------------------------------------
255        try
256        {
257            encryptionService = (Processor) Class.forName(processor).newInstance();
258        }
259        catch (Throwable exception)
260        {
261            encryptionService = null;
262        }
263
264        // ----------------------------------------------------
265        // create the fields and field separators
266        // ----------------------------------------------------
267        createItems(format);
268
269        if ((preset != null) && (preset.length() > 0))
270        {
271            setFields(preset);
272        }
273
274        // ----------------------------------------------------
275        // set the focus to the first field
276        // ----------------------------------------------------
277        activeField = (RuleTextField) inputFields.elementAt(0);
278        activeField.grabFocus();
279    }
280
281    /*--------------------------------------------------------------------------*/
282    /**
283     * Returns the number of sub-fields composing this <code>RuleInputField</code>.
284     * 
285     * @return the number of sub-fields
286     */
287    /*--------------------------------------------------------------------------*/
288    public int getNumFields()
289    {
290        return (inputFields.size());
291    }
292
293    /*--------------------------------------------------------------------------*/
294    /**
295     * Returns the contents of the field indicated by <code>index</code>.
296     * 
297     * @param index the index of the sub-field from which the contents is requested.
298     * 
299     * @return the contents of the indicated sub-field.
300     * 
301     * @exception IndexOutOfBoundsException if the index is out of bounds.
302     */
303    /*--------------------------------------------------------------------------*/
304    public String getFieldContents(int index) throws IndexOutOfBoundsException
305    {
306        if ((index < 0) || (index > (inputFields.size() - 1))) { throw (new IndexOutOfBoundsException()); }
307
308        return (((JTextField) inputFields.elementAt(index)).getText());
309    }
310
311    /*--------------------------------------------------------------------------*/
312    /**
313     * Returns the validator parameters, if any. The caller should check for the existence of
314     * validator parameters via the <code>hasParams()</code> method prior to invoking this method.
315     * 
316     * @return a java.util.Map containing the validator parameters.
317     */
318    public Map getValidatorParams()
319    {
320        return validatorParams;
321    }
322
323    /*---------------------------------------------------------------------------*/
324    /**
325     * Returns the field contents, assembled acording to the encryption and separator rules.
326     * 
327     * @return the field contents
328     */
329    /*--------------------------------------------------------------------------*/
330    public String getText()
331    {
332        Object item;
333        StringBuffer buffer = new StringBuffer();
334        int size = inputFields.size();
335
336        // ----------------------------------------------------
337        // have the encryption service class perfrom the task
338        // of assembling an output string. If we have no instance
339        // of this class available set the formatting
340        // instruction to the default setting.
341        // ----------------------------------------------------
342        if (resultFormat == ENCRYPTED)
343        {
344            if (encryptionService != null)
345            {
346                buffer.append(encryptionService.process(this));
347            }
348            else
349            {
350                resultFormat = DEFAULT;
351            }
352        }
353
354        // ----------------------------------------------------
355        // concatentate the field contents, with no separators
356        // in between.
357        // ----------------------------------------------------
358        else if (resultFormat == PLAIN_STRING)
359        {
360            for (int i = 0; i < inputFields.size(); i++)
361            {
362                buffer.append(((JTextField) inputFields.elementAt(i)).getText());
363            }
364        }
365
366        // ----------------------------------------------------
367        // concatenate the field contents and setarators, as
368        // specified for the display of the field.
369        // ----------------------------------------------------
370        else if (resultFormat == DISPLAY_FORMAT)
371        {
372            for (int i = 0; i < items.size(); i++)
373            {
374                item = items.elementAt(i);
375                if (item instanceof JTextField)
376                {
377                    buffer.append(((JTextField) item).getText());
378                }
379                else
380                {
381                    buffer.append((String) item);
382                }
383            }
384        }
385
386        // ----------------------------------------------------
387        // concatenate the field contents and insert the
388        // separator string in between.
389        // ----------------------------------------------------
390        else if (resultFormat == SPECIAL_SEPARATOR)
391        {
392            for (int i = 0; i < size; i++)
393            {
394                buffer.append(((JTextField) inputFields.elementAt(i)).getText());
395
396                if (i < (size - 1))
397                {
398                    buffer.append(separator);
399                }
400            }
401        }
402
403        return (buffer.toString());
404    }
405
406    /*--------------------------------------------------------------------------*/
407    /**
408     * Creates the items that make up this field. Both separators and input fields are considered
409     * items. The items created are stored in <code>items</code>. In addition, all fields are
410     * stored in <code>inputFields</code>.
411     * 
412     * @param format a string that specifies the layout of the input fields and separators.
413     */
414    /*--------------------------------------------------------------------------*/
415    /*
416     * $ @design
417     * 
418     * I used a simple StringTokenizer to break the format string into individual tokens. The
419     * approach in building up the field is to consider each token a potential definition for an
420     * input field. Therefore I attempt to create an instance of FieldSpec from each token.
421     * FieldSpec analyzes the token and if it does not represent a valid specification for an input
422     * field throws an exception. If I catch an exception, I know the token does not represent a
423     * valid field specification. In this case I treat the token as a separator, even though this
424     * might not be what the user had intended. However, this approach allows me to implement robust
425     * behavior (no exception thrown) even though the user might have made a mistake in the
426     * definition. The mistake should become immediately obvious when testing the code, since a
427     * input field definition would show up as separator between two fields.
428     * --------------------------------------------------------------------------
429     */
430    private void createItems(String format)
431    {
432        Object item;
433        JTextField field;
434        String token;
435        FieldSpec spec;
436        StringTokenizer tokenizer = new StringTokenizer(format);
437
438        while (tokenizer.hasMoreTokens())
439        {
440            token = tokenizer.nextToken();
441            try
442            {
443                spec = new FieldSpec(token);
444                field = new RuleTextField(spec.getColumns(), spec.getEditLength(), spec.getType(),
445                        spec.getUnlimitedEdit(), toolkit);
446
447                // ------------------------------------------------
448                // if the previous item is also a field, insert a
449                // space as separator
450                // ------------------------------------------------
451                if (items.size() > 0)
452                {
453                    item = items.lastElement();
454
455                    if (item instanceof JTextField)
456                    {
457                        items.add(" ");
458                    }
459                }
460
461                items.add(field);
462                inputFields.add(field);
463                field.addFocusListener(this);
464                field.addKeyListener(this);
465                field.addCaretListener(this);
466            }
467            // --------------------------------------------------
468            // if we were not successful creating an input field,
469            // the token must be a separator or the definition
470            // has an error. Simply insert it as separator,
471            // when testing the installer the error should become
472            // obvious to the developer.
473            // --------------------------------------------------
474            catch (Throwable exception)
475            {
476                if (items.size() == 0)
477                {
478                    items.add(token);
479                }
480                else
481                {
482                    item = items.lastElement();
483
484                    // ----------------------------------------------
485                    // if the previous item is also a separator,
486                    // simply concatenate the token with a space
487                    // inserted in between, don't add it as new
488                    // separator.
489                    // ----------------------------------------------
490                    if (item instanceof String)
491                    {
492                        items.setElementAt(item + " " + token, (items.size() - 1));
493                    }
494                    else
495                    {
496                        items.add(token);
497                    }
498                }
499            }
500        }
501
502        // ----------------------------------------------------
503        // add the fields and separators to the component
504        // ----------------------------------------------------
505        for (int i = 0; i < items.size(); i++)
506        {
507            item = items.elementAt(i);
508
509            if (item instanceof String)
510            {
511                add(new JLabel((String) item));
512            }
513            else
514            {
515                add((JTextField) item);
516            }
517        }
518    }
519
520    /*--------------------------------------------------------------------------*/
521    /**
522     * Sets each field to a pre-defined value.
523     * 
524     * @param data a <code>String</code> containing the preset values for each field. The format
525     * of the string is as follows: The content for the individuals fields must be separated by
526     * whitespace. Each data block is preceeded by the index of the field to set (counting starts at
527     * 0) followed by a colon ':'and after that the actual data for the field.
528     */
529    /*--------------------------------------------------------------------------*/
530    private void setFields(String data)
531    {
532        StringTokenizer tokenizer = new StringTokenizer(data);
533        String token;
534        String indexString;
535        int index;
536        boolean process = false;
537        String[] vals = null;
538        int i = 0;
539
540        vals = new String[tokenizer.countTokens()];
541        while (tokenizer.hasMoreTokens())
542        {
543            token = tokenizer.nextToken();
544            indexString = token.substring(0, token.indexOf(':'));
545
546            try
547            {
548                index = Integer.parseInt(indexString);
549                if (index < inputFields.size())
550                {
551                    String val = token.substring((token.indexOf(':') + 1), token.length());
552                    String className = "";
553                    if (val.indexOf(":") > -1)
554                    {
555                        className = val.substring(val.indexOf(":") + 1);
556                        val = val.substring(0, val.indexOf(":"));
557                    }
558
559                    if (!className.equals("") && !process)
560                    {
561                        process = true;
562                    }
563                    VariableSubstitutor vs = new VariableSubstitutor(idata.getVariables());
564                    val = vs.substitute(val, null);
565                    vals[i] = val;
566                    i++;
567                    ((JTextField) inputFields.elementAt(index)).setText(val);
568                }
569            }
570            catch (Throwable exception)
571            {
572                exception.printStackTrace();
573            }
574        }
575
576        if (process)
577        {
578            tokenizer = new StringTokenizer(data);
579            while (tokenizer.hasMoreTokens())
580            {
581                token = tokenizer.nextToken();
582                indexString = token.substring(0, token.indexOf(':'));
583
584                try
585                {
586                    index = Integer.parseInt(indexString);
587                    if (index < inputFields.size())
588                    {
589                        String val = token.substring((token.indexOf(':') + 1), token.length());
590                        String className = "";
591                        String presult = "";
592                        if (val.indexOf(":") > -1)
593                        {
594                            className = val.substring(val.indexOf(":") + 1);
595                            val = val.substring(0, val.indexOf(":"));
596                        }
597
598                        if (!className.equals(""))
599                        {
600                            Processor p = (Processor) Class.forName(className).newInstance();
601                            presult = p.process(this);
602                        }
603                        String[] td = new RE("\\*").split(presult);
604                        ((JTextField) inputFields.elementAt(index)).setText(td[index]);
605                    }
606                }
607                catch (Throwable exception)
608                {
609                    ;
610                }
611            }
612        }
613    }
614
615    /*--------------------------------------------------------------------------*/
616    /**
617     * This method validates the field content. Validating is performed through a user supplied
618     * service class that provides the validation rules.
619     * 
620     * @return <code>true</code> if the validation passes or no implementation of a validation
621     * rule exists. Otherwise <code>false</code> is returned.
622     */
623    /*--------------------------------------------------------------------------*/
624    public boolean validateContents()
625    {
626        if (validationService != null)
627        {
628            return (validationService.validate(this));
629        }
630        else
631        {
632            return (true);
633        }
634    }
635
636    /*---------------------------------------------------------------------------*
637     Implementation for KeyListener
638     *---------------------------------------------------------------------------*/
639
640    /*--------------------------------------------------------------------------*/
641    /**
642     * This method is invoked when a key has been typed. The event occurs when a key press is
643     * followed by a key release.
644     * 
645     * @param event the key event forwarded by the system.
646     */
647    /*--------------------------------------------------------------------------*/
648    public void keyTyped(KeyEvent event)
649    {
650    }
651
652    /*--------------------------------------------------------------------------*/
653    /**
654     * This method is invoked when a key has been pressed. This method verifies the condition of the
655     * input field in focus. Once the column count in the field has reached the specified maximum,
656     * the rule specified for the field in question is invoked. In case the test result is positive,
657     * focus is set to the next field. If hte test result is negative, the field content is marked
658     * and the caret set to the start of the field.
659     * 
660     * @param event the key event forwarded by the system.
661     */
662    /*--------------------------------------------------------------------------*/
663    public void keyPressed(KeyEvent event)
664    {
665        if (event.getKeyCode() == KeyEvent.VK_BACK_SPACE)
666        {
667            int caretPosition = activeField.getCaretPosition();
668
669            if (caretPosition == 0)
670            {
671                int activeIndex = inputFields.indexOf(activeField);
672
673                if (activeIndex > 0)
674                {
675                    activeIndex--;
676                    backstep = true;
677                    activeField = (RuleTextField) inputFields.elementAt(activeIndex);
678                    activeField.grabFocus();
679                }
680            }
681        }
682    }
683
684    /*--------------------------------------------------------------------------*/
685    /**
686     * This method is invoked when a key has been released.
687     * 
688     * @param event the key event forwarded by the system.
689     */
690    /*--------------------------------------------------------------------------*/
691    public void keyReleased(KeyEvent event)
692    {
693    }
694
695    /*---------------------------------------------------------------------------*
696     Implementation for FocusListener
697     *---------------------------------------------------------------------------*/
698
699    /*--------------------------------------------------------------------------*/
700    /**
701     * Invoked when a component gains the keyboard focus.
702     * 
703     * @param event the focus event forwardes by the sytem.
704     */
705    /*--------------------------------------------------------------------------*/
706    /*
707     * $ @design <- keep this tag in place and don't write on this line!
708     * 
709     * Enter design related documentation here.
710     * --------------------------------------------------------------------------
711     */
712    public void focusGained(FocusEvent event)
713    {
714        activeField = (RuleTextField) event.getSource();
715
716        if (backstep)
717        {
718            activeField.setCaretPosition(activeField.getText().length());
719            backstep = false;
720        }
721        else
722        {
723            activeField.selectAll();
724        }
725    }
726
727    /*--------------------------------------------------------------------------*/
728    /**
729     * Invoked when a component loses the keyboard focus. This method does nothing, we are only
730     * interested in 'focus gained' events.
731     * 
732     * @param event the focus event forwardes by the sytem.
733     */
734    /*--------------------------------------------------------------------------*/
735    public void focusLost(FocusEvent event)
736    {
737    }
738
739    /*---------------------------------------------------------------------------*
740     Implementation for CaretListener
741     *---------------------------------------------------------------------------*/
742
743    /*--------------------------------------------------------------------------*/
744    /**
745     * Called when the caret position is updated.
746     * 
747     * @param event the caret event received from the text field
748     */
749    /*--------------------------------------------------------------------------*/
750    public void caretUpdate(CaretEvent event)
751    {
752        String text = activeField.getText();
753        int fieldSize = activeField.getEditLength();
754        int caretPosition = activeField.getCaretPosition();
755        int selection = activeField.getSelectionEnd() - activeField.getSelectionStart();
756
757        if ((!inputFields.lastElement().equals(activeField)) && (!activeField.unlimitedEdit()))
758        {
759            if ((text.length() == fieldSize) && (selection == 0) && (caretPosition == fieldSize)
760                    && !backstep)
761            {
762                activeField.transferFocus();
763            }
764        }
765    }
766
767    // ----------------------------------------------------------------------------
768    //
769    // ----------------------------------------------------------------------------
770    private static class FieldSpec
771    {
772
773        private int MIN_TOKENS = 2;
774
775        private int MAX_TOKENS = 3;
776
777        private int type;
778
779        private int columns;
780
781        private int editLength;
782
783        private boolean unlimitedEdit = false;
784
785        public FieldSpec(String spec) throws Exception
786        {
787            StringTokenizer tokenizer = new StringTokenizer(spec, ":");
788
789            if ((tokenizer.countTokens() >= MIN_TOKENS) && (tokenizer.countTokens() <= MAX_TOKENS))
790            {
791                String token = tokenizer.nextToken().toUpperCase();
792                // ------------------------------------------------
793                // test the first token for a valid type identifier
794                // if it's valid assign the token to the type.
795                // ------------------------------------------------
796                if (token.equals("N"))
797                {
798                    type = RuleTextField.N;
799                }
800                else if (token.equals("H"))
801                {
802                    type = RuleTextField.H;
803                }
804                else if (token.equals("A"))
805                {
806                    type = RuleTextField.A;
807                }
808                else if (token.equals("O"))
809                {
810                    type = RuleTextField.O;
811                }
812                else if (token.equals("AN"))
813                {
814                    type = RuleTextField.AN;
815                }
816                else
817                {
818                    throw (new Exception());
819                }
820
821                // ------------------------------------------------
822                // test for a valid integer to define the size
823                // of the field and assing to columns.
824                // ------------------------------------------------
825                try
826                {
827                    token = tokenizer.nextToken();
828                    columns = Integer.parseInt(token);
829                }
830                catch (Throwable exception)
831                {
832                    throw (new Exception());
833                }
834
835                // ------------------------------------------------
836                // test for a valid integer to define the edit
837                // length and assign to editLength. If this fails
838                // test for identifier for unlimited edit length.
839                // If this works, set unlimitedEdit to true.
840                // ------------------------------------------------
841                try
842                {
843                    token = tokenizer.nextToken().toUpperCase();
844                    editLength = Integer.parseInt(token);
845                }
846                catch (Throwable exception)
847                {
848                    if (token.equals("U"))
849                    {
850                        unlimitedEdit = true;
851                    }
852                    else
853                    {
854                        throw (new Exception());
855                    }
856                }
857
858            }
859            else
860            {
861                throw (new Exception());
862            }
863        }
864
865        public int getColumns()
866        {
867            return (columns);
868        }
869
870        public int getEditLength()
871        {
872            return (editLength);
873        }
874
875        public int getType()
876        {
877            return (type);
878        }
879
880        public boolean getUnlimitedEdit()
881        {
882            return (unlimitedEdit);
883        }
884
885    }
886    // ----------------------------------------------------------------------------
887
888}
889/*---------------------------------------------------------------------------*/