View Javadoc

1   /*
2    * Copyright 2006-2016 The JGUIraffe Team.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package net.sf.jguiraffe.gui.builder.window.ctrl;
17  
18  import javax.swing.event.EventListenerList;
19  import java.util.EventListener;
20  import java.util.HashSet;
21  import java.util.Set;
22  
23  import net.sf.jguiraffe.gui.builder.BuilderData;
24  import net.sf.jguiraffe.gui.builder.components.ComponentBuilderData;
25  import net.sf.jguiraffe.gui.builder.event.FormActionEvent;
26  import net.sf.jguiraffe.gui.builder.event.FormActionListener;
27  import net.sf.jguiraffe.gui.builder.event.FormFocusEvent;
28  import net.sf.jguiraffe.gui.builder.event.FormFocusListener;
29  import net.sf.jguiraffe.gui.builder.utils.MessageOutput;
30  import net.sf.jguiraffe.gui.builder.window.Window;
31  import net.sf.jguiraffe.gui.builder.window.WindowBuilderData;
32  import net.sf.jguiraffe.gui.builder.window.WindowEvent;
33  import net.sf.jguiraffe.gui.builder.window.WindowListener;
34  import net.sf.jguiraffe.gui.cmd.Command;
35  import net.sf.jguiraffe.gui.forms.DefaultFormValidatorResults;
36  import net.sf.jguiraffe.gui.forms.Form;
37  import net.sf.jguiraffe.gui.forms.FormValidationMessageFormat;
38  import net.sf.jguiraffe.gui.forms.FormValidator;
39  import net.sf.jguiraffe.gui.forms.FormValidatorResults;
40  import net.sf.jguiraffe.transform.DefaultValidationMessageHandler;
41  import net.sf.jguiraffe.transform.TransformerContext;
42  import net.sf.jguiraffe.transform.ValidationMessageConstants;
43  
44  /**
45   * <p>
46   * A base class for form controllers.
47   * </p>
48   * <p>
49   * The form builder library follows the MVC paradigm when dealing with forms:
50   * <ul>
51   * <li>The dialog window displaying the input fields plays the role of the
52   * <em>view</em>.</li>
53   * <li>The <em>model</em> is represented by a data object, the so-called <em>form bean</em>
54   * or <em>model object</em>. This object has
55   * properties that are bound to the input fields of the form. It stores the data
56   * entered by the user. In the initialization phase it provides the data for
57   * filling the input fields. Access to the model object is controlled by a
58   * {@link net.sf.jguiraffe.gui.forms.BindingStrategy BindingStrategy}.</li>
59   * <li>The <em>controller</em> can be an instance of this class or one of its
60   * subclasses. It controls the form's life-cycle and ensures that user input is
61   * validated and correctly saved in the model.</li>
62   * </ul>
63   * </p>
64   * <p>
65   * This class provides a fully functional controller implementation, which
66   * handles all phases of the form's life-cycle. It can be used out of the box.
67   * In most cases adaptation to a specific application's needs is possible by
68   * configuring some of the helper objects used by this class. This way for
69   * instance the validation handling can be changed or the way fields containing
70   * invalid data are displayed. Refer to the documentation of the corresponding
71   * set methods for further details.
72   * </p>
73   * <p>
74   * One of the main tasks of this class is to ensure that input validation is
75   * performed when necessary. When clicking the <em>OK</em> button, a
76   * validation has to be performed in any case. But it is also possible to
77   * perform validation earlier, e.g. when the user left an input field. This can
78   * be achieved by configuring a corresponding <code>FormValidationTrigger</code>.
79   * A <code>FieldMarker</code> is used for defining the appearance of input
80   * fields depending on their validation status.
81   * </p>
82   * <p>
83   * Implementation note: This class is not thread-safe. It is intended to be
84   * associated with a single form instance and not to be used concurrently with
85   * multiple forms or threads.
86   * </p>
87   *
88   * @author Oliver Heger
89   * @version $Id: FormController.java 205 2012-01-29 18:29:57Z oheger $
90   */
91  public class FormController implements WindowListener, FormFocusListener,
92          FormActionListener
93  {
94      /** Constant for the name of the bean with the validation message format. */
95      static final String BEAN_VALIDATION_MESSAGE_FORMAT = "jguiraffe.validationMessageFormat";
96  
97      /** Stores the component builder data. */
98      private ComponentBuilderData componentBuilderData;
99  
100     /** Stores the window builder data. */
101     private WindowBuilderData windowBuilderData;
102 
103     /** Stores the form validation trigger. */
104     private FormValidationTrigger validationTrigger;
105 
106     /** A set with the fields that have been visited so far. */
107     private final Set<String> visitedFields;
108 
109     /** The list with all event listeners. */
110     private final EventListenerList eventListeners;
111 
112     /** Stores a form validator. */
113     private FormValidator formValidator;
114 
115     /** Stores the result of the last validation. */
116     private FormValidatorResults lastValidationResults;
117 
118     /** Stores a message output object to be used. */
119     private MessageOutput messageOutput;
120 
121     /** Stores the object for formatting validation messages. */
122     private FormValidationMessageFormat validationMessageFormat;
123 
124     /** The command to be executed when the form is committed.*/
125     private Command okCommand;
126 
127     /** The command to be executed when the form is canceled.*/
128     private Command cancelCommand;
129 
130     /** Stores the name of the OK button. */
131     private String btnOkName;
132 
133     /** Stores the name of the cancel button. */
134     private String btnCancelName;
135 
136     /**
137      * Stores the caption of the message box for displaying validation error
138      * messages.
139      */
140     private String validationMessageBoxCaption;
141 
142     /** A flag whether the form was committed.*/
143     private boolean committed;
144 
145     /**
146      * Creates a new instance of {@code FormController}.
147      */
148     public FormController()
149     {
150         visitedFields = new HashSet<String>();
151         eventListeners = new EventListenerList();
152     }
153 
154     /**
155      * Returns the <code>ComponentBuilderData</code> object.
156      *
157      * @return the object with information about all components that belong to
158      *         the current form
159      */
160     public ComponentBuilderData getComponentBuilderData()
161     {
162         return componentBuilderData;
163     }
164 
165     /**
166      * Sets the <code>ComponentBuilderData</code> object. This object must
167      * have been set before an instance of this class can be used. It allows
168      * access to all components involved and the current form as well.
169      *
170      * @param componentBuilderData the component builder data object
171      */
172     public void setComponentBuilderData(
173             ComponentBuilderData componentBuilderData)
174     {
175         this.componentBuilderData = componentBuilderData;
176     }
177 
178     /**
179      * Returns the <code>WindowBuilderData</code> object.
180      *
181      * @return the data object with information about the current window
182      */
183     public WindowBuilderData getWindowBuilderData()
184     {
185         return windowBuilderData;
186     }
187 
188     /**
189      * Sets the <code>WindowBuilderData</code> object. This object must have
190      * been set before an instance of this class can be used. It allows access
191      * to important information about the current window including its form
192      * bean.
193      *
194      * @param windowBuilderData the window builder data object
195      */
196     public void setWindowBuilderData(WindowBuilderData windowBuilderData)
197     {
198         this.windowBuilderData = windowBuilderData;
199     }
200 
201     /**
202      * Returns a {@code FormValidator} for validating the associated form.
203      * Result can be <b>null</b> if no specific {@code FormValidator} was set.
204      * Note that the result of this method need not always be in sync with the
205      * object returned by {@link Form#getFormValidator()}: this method always
206      * returns the object that was set on a previous
207      * {@link #setFormValidator(FormValidator)} call.
208      *
209      * @return the {@code FormValidator}
210      * @see #setFormValidator(FormValidator)
211      */
212     public FormValidator getFormValidator()
213     {
214         return formValidator;
215     }
216 
217     /**
218      * Sets a {@code FormValidator} for the the associated form. This method is
219      * intended to be called during a builder script. When the associated form
220      * is opened, the {@code FormValidator} is automatically installed. If this
221      * method is called later (if the window is already open and the form
222      * exists), the specified in {@code FormValidator} is directly passed to the
223      * {@code Form} object.
224      *
225      * @param formValidator the {@code FormValidator} for the associated {@code
226      *        Form}
227      */
228     public void setFormValidator(FormValidator formValidator)
229     {
230         this.formValidator = formValidator;
231 
232         if (getComponentBuilderData() != null)
233         {
234             getForm().setFormValidator(formValidator);
235         }
236     }
237 
238     /**
239      * Returns the current form bean. This is a convenience method which obtains
240      * the form bean from the {@link WindowBuilderData} object.
241      *
242      * @return the current form bean
243      */
244     public Object getFormBean()
245     {
246         return getWindowBuilderData().getFormBean();
247     }
248 
249     /**
250      * Returns the current form. This is a convenience method which obtains the
251      * form from the {@link ComponentBuilderData} object.
252      *
253      * @return the current form
254      */
255     public Form getForm()
256     {
257         return getComponentBuilderData().getForm();
258     }
259 
260     /**
261      * Returns the associated window. This is the window containing the form. It
262      * is obtained from the {@link WindowBuilderData} object.
263      *
264      * @return the associated window
265      */
266     public Window getWindow()
267     {
268         return getWindowBuilderData().getResultWindow();
269     }
270 
271     /**
272      * Returns the <code>MessageOutput</code> object to be used by this
273      * controller. This can be <b>null</b> if no specific object has been set.
274      *
275      * @return the <code>MessageOutput</code> object
276      * @see #setMessageOutput(MessageOutput)
277      */
278     public MessageOutput getMessageOutput()
279     {
280         return messageOutput;
281     }
282 
283     /**
284      * Sets the <code>MessageOutput</code> object to be used by this controller.
285      * The <code>MessageOutput</code> object is used for displaying validation
286      * error messages to the user. Per default the <code>MessageOutput</code>
287      * object defined in the {@link BuilderData} object will be used (which is
288      * typically the application-global output object). With this method it is
289      * possible to set a specific output object for this controller.
290      *
291      * @param messageOutput the <code>MessageOutput</code> object to use
292      * @see #fetchMessageOutput()
293      */
294     public void setMessageOutput(MessageOutput messageOutput)
295     {
296         this.messageOutput = messageOutput;
297     }
298 
299     /**
300      * Returns the <code>FormValidationMessageFormat</code> object to be used
301      * by this controller. This can be <b>null</b> if no specific object has
302      * been set.
303      *
304      * @return the <code>FormValidationMessageFormat</code> object
305      * @see #setValidationMessageFormat(FormValidationMessageFormat)
306      */
307     public FormValidationMessageFormat getValidationMessageFormat()
308     {
309         return validationMessageFormat;
310     }
311 
312     /**
313      * Sets the <code>FormValidationMessageFormat</code> object to be used by
314      * this controller. This object is used for generating error messages for a
315      * failed validation that are to be displayed to the user. With this method
316      * it is possible to set a specific format object for this purpose. If no
317      * specific object is set, the application-global default format object is
318      * used (which is obtained from the current <code>BeanContext</code>).
319      *
320      * @param validationMessageFormat the
321      *        <code>FormValidationMessageFormat</code> object to use
322      * @see #fetchValidationMessageFormat()
323      */
324     public void setValidationMessageFormat(
325             FormValidationMessageFormat validationMessageFormat)
326     {
327         this.validationMessageFormat = validationMessageFormat;
328     }
329 
330     /**
331      * Returns the name of the component representing the OK button.
332      *
333      * @return the name of the OK button
334      */
335     public String getBtnOkName()
336     {
337         return btnOkName;
338     }
339 
340     /**
341      * Sets the name of the component representing the OK button. This
342      * controller will register an action listener at this component for
343      * handling the commit operation accordingly.
344      *
345      * @param btnOkName the name of the OK button component
346      */
347     public void setBtnOkName(String btnOkName)
348     {
349         this.btnOkName = btnOkName;
350     }
351 
352     /**
353      * Returns the name of the component representing the cancel button.
354      *
355      * @return the name of the cancel button
356      */
357     public String getBtnCancelName()
358     {
359         return btnCancelName;
360     }
361 
362     /**
363      * Sets the name of the component representing the cancel button. This
364      * controller will register an action listener at this component for
365      * handling the cancel operation accordingly.
366      *
367      * @param btnCancelName the name of the cancel button component
368      */
369     public void setBtnCancelName(String btnCancelName)
370     {
371         this.btnCancelName = btnCancelName;
372     }
373 
374     /**
375      * Returns the caption of the message box for displaying validation error
376      * messages.
377      *
378      * @return the caption of the validation error message box
379      */
380     public String getValidationMessageBoxCaption()
381     {
382         return validationMessageBoxCaption;
383     }
384 
385     /**
386      * Sets the caption of the message box for displaying validation error
387      * messages. If the user hits the OK button, the data entered by the user is
388      * validated. If this validation fails, a message box with the found
389      * validation problems is displayed. This property allows defining the
390      * caption of this message box. If no caption is set, a default caption is
391      * used (which is defined as a resource ID and resolved using the current
392      * resource manager).
393      *
394      * @param validationMessageBoxCaption the caption of the validation error
395      *        message box
396      */
397     public void setValidationMessageBoxCaption(
398             String validationMessageBoxCaption)
399     {
400         this.validationMessageBoxCaption = validationMessageBoxCaption;
401     }
402 
403     /**
404      * Returns the <code>FormValidationTrigger</code>.
405      *
406      * @return the validation trigger (can be <b>null</b> if none was set)
407      */
408     public FormValidationTrigger getValidationTrigger()
409     {
410         return validationTrigger;
411     }
412 
413     /**
414      * Sets the <code>FormValidationTrigger</code>. This object is called
415      * once in the initialization phase to give it opportunity to register
416      * itself as event listener.
417      *
418      * @param validationTrigger the new validation trigger (can be <b>null</b>)
419      */
420     public void setValidationTrigger(FormValidationTrigger validationTrigger)
421     {
422         this.validationTrigger = validationTrigger;
423     }
424 
425     /**
426      * Adds a {@code FormControllerValidationListener} to this controller. The
427      * listener will be notified whenever a validation is performed.
428      *
429      * @param l the listener to be added (must not be <b>null</b>)
430      * @throws IllegalArgumentException if the event listener is <b>null</b>
431      */
432     public void addValidationListener(FormControllerValidationListener l)
433     {
434         addEventListener(l, FormControllerValidationListener.class);
435     }
436 
437     /**
438      * Removes the specified {@code FormControllerValidationListener} from this
439      * controller. If the listener is not registered, this method has no effect.
440      *
441      * @param l the listener to be removed
442      */
443     public void removeValidationListener(FormControllerValidationListener l)
444     {
445         eventListeners.remove(FormControllerValidationListener.class, l);
446     }
447 
448     /**
449      * Returns an array with all {@code FormControllerValidationListener}
450      * objects registered at this {@code FormController}.
451      *
452      * @return an array with all registered {@code
453      *         FormControllerValidationListener} objects
454      */
455     public FormControllerValidationListener[] getValidationListeners()
456     {
457         return eventListeners.getListeners(FormControllerValidationListener.class);
458     }
459 
460     /**
461      * Adds a {@code FormControllerFieldStatusListener} to this controller. The
462      * listener will be notified whenever the visited status of a field in the
463      * controller's form changes.
464      *
465      * @param l the listener to be added (must not be <b>null</b>)
466      * @throws IllegalArgumentException if the event listener is <b>null</b>
467      */
468     public void addFieldStatusListener(FormControllerFieldStatusListener l)
469     {
470         addEventListener(l, FormControllerFieldStatusListener.class);
471     }
472 
473     /**
474      * Removes the specified {@code FormControllerFieldStatusListener} from this
475      * controller. If the listener is not registered, this method has no effect.
476      *
477      * @param l the listener to be removed
478      */
479     public void removeFieldStatusListener(FormControllerFieldStatusListener l)
480     {
481         eventListeners.remove(FormControllerFieldStatusListener.class, l);
482     }
483 
484     /**
485      * Returns an array with all {@code FormControllerFieldStatusListener}
486      * objects registered at this {@code FormController}.
487      *
488      * @return an array with all registered {@code
489      *         FormControllerFieldStatusListener} objects
490      */
491     public FormControllerFieldStatusListener[] getFieldStatusListeners()
492     {
493         return eventListeners.getListeners(FormControllerFieldStatusListener.class);
494     }
495 
496     /**
497      * Adds a {@code FormControllerFormListener} to this controller. The
498      * listener will be notified when the form associated with this controller
499      * is closed.
500      *
501      * @param l the listener to be added (must not be <b>null</b>)
502      * @throws IllegalArgumentException if the listener is <b>null</b>
503      */
504     public void addFormListener(FormControllerFormListener l)
505     {
506         addEventListener(l, FormControllerFormListener.class);
507     }
508 
509     /**
510      * Removes the specified {@code FormControllerFormListener} from this
511      * controller. If the listener is not registered, this method has no effect.
512      *
513      * @param l the listener to be removed
514      */
515     public void removeFormListener(FormControllerFormListener l)
516     {
517         eventListeners.remove(FormControllerFormListener.class, l);
518     }
519 
520     /**
521      * Returns an array with all {@code FormControllerFormListener} objects
522      * registered at this {@code FormController}.
523      *
524      * @return an array with all registered {@code FormControllerFormListener}
525      *         objects
526      */
527     public FormControllerFormListener[] getFormListeners()
528     {
529         return eventListeners.getListeners(FormControllerFormListener.class);
530     }
531 
532     /**
533      * Returns the command to be executed when the form is closed in reaction of
534      * the OK button.
535      *
536      * @return the command to be executed after OK was clicked
537      */
538     public Command getOkCommand()
539     {
540         return okCommand;
541     }
542 
543     /**
544      * Sets the command to be executed when the form is closed in reaction of
545      * the OK button. When the user commits a form often some actions have to be
546      * performed (e.g. saving some data in the database, triggering some other
547      * components, etc.). These actions can be implemented as a
548      * <code>{@link Command}</code> object and associated with this
549      * controller. In its action handler for the OK event the controller will
550      * check (after a successful validation) whether a command was set. If this
551      * is the case, it will be passed to the current command queue.
552      *
553      * @param okCommand the command to be executed when the form is committed
554      */
555     public void setOkCommand(Command okCommand)
556     {
557         this.okCommand = okCommand;
558     }
559 
560     /**
561      * Returns the command to be executed when the form is canceled.
562      *
563      * @return the command to be executed after the cancel button was clicked
564      */
565     public Command getCancelCommand()
566     {
567         return cancelCommand;
568     }
569 
570     /**
571      * Sets the command to be executed when the form is canceled. This method is
572      * similar to <code>{@link #setOkCommand(Command)}</code>, but it allows
573      * to associate a <code>{@link Command}</code> object with the cancel
574      * button (or any other close action that does not mean a commit). This way
575      * it is possible to execute some action when the user decides to throw away
576      * its input.
577      *
578      * @param cancelCommand the command to be executed when the form is canceled
579      */
580     public void setCancelCommand(Command cancelCommand)
581     {
582         this.cancelCommand = cancelCommand;
583     }
584 
585     /**
586      * Performs a validation of the associated form. After that the
587      * {@link FormControllerValidationListener} objects registered at this
588      * controller will be notified.
589      *
590      * @return a data object with information about the result of the validation
591      */
592     public FormValidatorResults validate()
593     {
594         FormValidatorResults results = getForm().validate(getFormBean());
595         lastValidationResults = results;
596         fireValidationEvent(results);
597         return results;
598     }
599 
600     /**
601      * Performs a validation of the associated form and displays validation
602      * messages if this is not successful. This method delegates to
603      * {@link #validate()}. If validation results indicate errors, a message
604      * window is displayed containing corresponding validation error messages.
605      * This method is intended to do a validation in reaction on a user action,
606      * e.g. when the user clicks an <em>apply</em> button.
607      *
608      * @return a data object with information about the result of the validation
609      * @since 1.3.1
610      */
611     public FormValidatorResults validateAndDisplayMessages()
612     {
613         markFieldsAsVisited();
614         FormValidatorResults results = validate();
615         if (!results.isValid())
616         {
617             String msg =
618                     fetchValidationMessageFormat().format(results, getForm());
619             fetchMessageOutput().show(getWindow(), msg,
620                     fetchValidationMessageBoxCaption(),
621                     MessageOutput.MESSAGE_ERROR, MessageOutput.BTN_OK);
622         }
623         return results;
624     }
625 
626     /**
627      * Returns the results of the last validation operation. The object returned
628      * by this method is the same as was returned by the last
629      * {@link #validate()} call. This is useful for instance to determine which
630      * input fields are currently invalid. If no validation has been performed
631      * so far, a valid result object is returned.
632      *
633      * @return a {@code FormValidatorResults} object with the last validation
634      *         results
635      */
636     public FormValidatorResults getLastValidationResults()
637     {
638         return (lastValidationResults != null) ? lastValidationResults
639                 : DefaultFormValidatorResults.validResultsForForm(getForm());
640     }
641 
642     /**
643      * Dummy implementation of this window event.
644      *
645      * @param event the received event
646      */
647     public void windowActivated(WindowEvent event)
648     {
649     }
650 
651     /**
652      * The associated window was closed. This implementation checks whether
653      * commands were registered for either the OK or the cancel button. If this
654      * is the case, the correct command is executed. Also, registered
655      * {@link FormControllerFormListener} objects are notified.
656      *
657      * @param event the received event
658      */
659     public void windowClosed(WindowEvent event)
660     {
661         Command cmd = isCommitted() ? getOkCommand() : getCancelCommand();
662         if (cmd != null)
663         {
664             getBuilderData().getCommandQueue().execute(cmd);
665         }
666 
667         fireFormEvent();
668     }
669 
670     /**
671      * Dummy implementation of this window event.
672      *
673      * @param event the received event
674      */
675     public void windowDeactivated(WindowEvent event)
676     {
677     }
678 
679     /**
680      * Dummy implementation of this window event.
681      *
682      * @param event the received event
683      */
684     public void windowDeiconified(WindowEvent event)
685     {
686     }
687 
688     /**
689      * Dummy implementation of this window event.
690      *
691      * @param event the received event
692      */
693     public void windowIconified(WindowEvent event)
694     {
695     }
696 
697     /**
698      * Dummy implementation of this window event.
699      *
700      * @param event the received event
701      */
702     public void windowClosing(WindowEvent event)
703     {
704     }
705 
706     /**
707      * The window containing the associated form was opened. This is the main
708      * initialization method. The controller has to perform some setup here.
709      *
710      * @param event the event
711      * @throws IllegalStateException if a required field is missing
712      */
713     public void windowOpened(WindowEvent event)
714     {
715         checkRequiredFields();
716 
717         getComponentBuilderData().getEventManager().addFocusListener(this);
718         registerActionListener(getBtnOkName());
719         registerActionListener(getBtnCancelName());
720 
721         if (getValidationTrigger() != null)
722         {
723             getValidationTrigger().initTrigger(this);
724         }
725 
726         if (getFormValidator() != null)
727         {
728             getForm().setFormValidator(getFormValidator());
729         }
730         initFormFields();
731         validate();
732     }
733 
734     /**
735      * A component of the associated window was given the focus.
736      *
737      * @param e the focus event
738      */
739     public void focusGained(FormFocusEvent e)
740     {
741     }
742 
743     /**
744      * A component of the associated window lost the focus. We track this event
745      * to mark the field as visited. This has an influence on validation. If the
746      * visited status for this field has changed, the {@code FieldMarker} also
747      * needs to be notified.
748      *
749      * @param e the focus event
750      */
751     public void focusLost(FormFocusEvent e)
752     {
753         if (visitedFields.add(e.getName()))
754         {
755             // the field was visited for the first time => status change
756             fireFieldStatusEvent(e.getName());
757         }
758     }
759 
760     /**
761      * Processes action events. This method tests whether the event was caused
762      * by the OK or the cancel button. If this is the case, the corresponding
763      * processing method is called.
764      *
765      * @param e the event
766      * @see #okButtonClicked(FormActionEvent)
767      * @see #cancelButtonClicked(FormActionEvent)
768      */
769     public void actionPerformed(FormActionEvent e)
770     {
771         if (e.getName() != null)
772         {
773             if (e.getName().equals(getBtnCancelName()))
774             {
775                 cancelButtonClicked(e);
776             }
777             else if (e.getName().equals(getBtnOkName()))
778             {
779                 okButtonClicked(e);
780             }
781         }
782     }
783 
784     /**
785      * Tests whether the field with the given name has already been visited by
786      * the user.
787      *
788      * @param name the name of the field
789      * @return a flag whether this field has already been visited
790      */
791     public boolean isFieldVisited(String name)
792     {
793         return visitedFields.contains(name);
794     }
795 
796     /**
797      * Returns the current <code>BuilderData</code> object. This object can be
798      * used for gaining access to some application global objects and the
799      * complete configuration of the builder.
800      *
801      * @return the <code>BuilderData</code> object
802      */
803     protected BuilderData getBuilderData()
804     {
805         return (BuilderData) getComponentBuilderData().getBeanContext()
806                 .getBean(ComponentBuilderData.KEY_BUILDER_DATA);
807     }
808 
809     /**
810      * Returns the current <code>MessageOutput</code> object. If an output
811      * object was set explicitly using the <code>setMessageOutput()</code>
812      * method, this object will be returned. Otherwise this method obtains the
813      * <code>MessageOutput</code> object from the current
814      * <code>{@link BuilderData}</code> object.
815      *
816      * @return the <code>MessageOutput</code> object to be used
817      */
818     protected MessageOutput fetchMessageOutput()
819     {
820         MessageOutput result = getMessageOutput();
821 
822         if (result == null)
823         {
824             result = getBuilderData().getMessageOutput();
825         }
826 
827         return result;
828     }
829 
830     /**
831      * Obtains the <code>FormValidationMessageFormat</code> object to be used.
832      * If a format object was set explicitly using the
833      * <code>setValidationMessageFormat()</code> method, this object will be
834      * returned. Otherwise this method queries the current
835      * <code>BeanContext</code> for the default format object.
836      *
837      * @return the <code>FormValidationMessageFormat</code> object to be used
838      */
839     protected FormValidationMessageFormat fetchValidationMessageFormat()
840     {
841         FormValidationMessageFormat fmt = getValidationMessageFormat();
842 
843         if (fmt == null)
844         {
845             fmt = (FormValidationMessageFormat) getComponentBuilderData()
846                     .getBeanContext().getBean(BEAN_VALIDATION_MESSAGE_FORMAT);
847         }
848 
849         return fmt;
850     }
851 
852     /**
853      * Returns the caption for a message box for displaying validation error
854      * messages. This method checks whether such a caption was explicitly set
855      * (using the <code>setValidationMessageBoxCaption()</code>). If this is
856      * the case, it is returned. Otherwise the resource key for the default
857      * caption is resolved.
858      *
859      * @return the caption for the message box for validation error messages
860      */
861     protected String fetchValidationMessageBoxCaption()
862     {
863         String caption = getValidationMessageBoxCaption();
864 
865         if (caption == null)
866         {
867             TransformerContext tctx = getBuilderData().getTransformerContext();
868             caption = tctx
869                     .getResourceManager()
870                     .getText(
871                             tctx.getLocale(),
872                             DefaultValidationMessageHandler.DEFAULT_RESOURCE_GROUP_NAME,
873                             ValidationMessageConstants.ERR_MESSAGE_CAPTION);
874         }
875 
876         return caption;
877     }
878 
879     /**
880      * The OK button was clicked. This method performs a validation. If this is
881      * successful, the form is closed. Otherwise an error message is created
882      * using the {@link FormValidationMessageFormat} object and
883      * displayed to the user via the current {@link MessageOutput}
884      * object. When the user clicks OK, he or she indicates that all fields have
885      * been properly filled in; so they are marked as visited.
886      *
887      * @param event the event object that triggered this method call
888      */
889     protected void okButtonClicked(FormActionEvent event)
890     {
891         FormValidatorResults vres = validateAndDisplayMessages();
892 
893         if (vres.isValid())
894         {
895             committed = true;
896             closeForm();
897         }
898     }
899 
900     /**
901      * The cancel button was clicked. This will cause the form to be closed
902      * without a validation.
903      *
904      * @param event the event object that triggered this method call
905      */
906     protected void cancelButtonClicked(FormActionEvent event)
907     {
908         closeForm();
909     }
910 
911     /**
912      * Closes the associated form. This method is called by both the action
913      * handler of the OK and the cancel button when the form can be closed. It
914      * invokes the <code>close()</code> method on the associated window
915      * object.
916      */
917     protected void closeForm()
918     {
919         getWindow().close(true);
920     }
921 
922     /**
923      * Returns a flag whether the form was committed. This method can be used to
924      * find out whether the form was closed using the OK button (in this case
925      * the return value is <b>true</b>) or whether it was canceled. Of course,
926      * calling this method makes only sense after the form was closed. It is
927      * invoked by the handler for the window closed event to find out, which
928      * command object is to be executed (if any).
929      *
930      * @return a flag whether the form was closed using the OK button
931      */
932     protected boolean isCommitted()
933     {
934         return committed;
935     }
936 
937     /**
938      * Notifies all registered validation listeners about a validation
939      * operation.
940      *
941      * @param results the validation results
942      */
943     protected void fireValidationEvent(FormValidatorResults results)
944     {
945         FormControllerValidationEvent event = null;
946         Object[] listeners = eventListeners.getListenerList();
947 
948         for (int i = listeners.length - 2; i >= 0; i -= 2)
949         {
950             if (listeners[i] == FormControllerValidationListener.class)
951             {
952                 if (event == null)
953                 {
954                     event = new FormControllerValidationEvent(this, results);
955                 }
956                 ((FormControllerValidationListener) listeners[i + 1])
957                         .validationPerformed(event);
958             }
959         }
960     }
961 
962     /**
963      * Notifies all registered field status listeners about a change in the
964      * status of a field.
965      *
966      * @param fieldName the name of the affected field
967      */
968     protected void fireFieldStatusEvent(String fieldName)
969     {
970         FormControllerFieldStatusEvent event = null;
971         Object[] listeners = eventListeners.getListenerList();
972 
973         for (int i = listeners.length - 2; i >= 0; i -= 2)
974         {
975             if (listeners[i] == FormControllerFieldStatusListener.class)
976             {
977                 if (event == null)
978                 {
979                     event = new FormControllerFieldStatusEvent(this, fieldName);
980                 }
981                 ((FormControllerFieldStatusListener) listeners[i + 1])
982                         .fieldStatusChanged(event);
983             }
984         }
985     }
986 
987     /**
988      * Notifies all registered form listeners that the form has been closed.
989      */
990     protected void fireFormEvent()
991     {
992         FormControllerFormEvent event = null;
993         Object[] listeners = eventListeners.getListenerList();
994 
995         for (int i = listeners.length - 2; i >= 0; i -= 2)
996         {
997             if (listeners[i] == FormControllerFormListener.class)
998             {
999                 if (event == null)
1000                 {
1001                     event = createFormEvent();
1002                 }
1003                 ((FormControllerFormListener) listeners[i + 1])
1004                         .formClosed(event);
1005             }
1006         }
1007     }
1008 
1009     /**
1010      * Tests whether all required fields are set. If this is not the case, an
1011      * exception will be thrown.
1012      *
1013      * @throws IllegalStateException if a required field is missing
1014      */
1015     private void checkRequiredFields()
1016     {
1017         if (getComponentBuilderData() == null)
1018         {
1019             throw new IllegalStateException("No component builder data is set!");
1020         }
1021         if (getWindowBuilderData() == null)
1022         {
1023             throw new IllegalStateException("No window builder data is set!");
1024         }
1025         if (getWindowBuilderData().getResultWindow() == null)
1026         {
1027             throw new IllegalStateException("No associated window found!");
1028         }
1029     }
1030 
1031     /**
1032      * Registers this controller as action listener at the specified component.
1033      * This is only done if the passed in name is not <b>null</b>.
1034      *
1035      * @param name the name of the component
1036      */
1037     private void registerActionListener(String name)
1038     {
1039         if (name != null)
1040         {
1041             getComponentBuilderData().getEventManager().addActionListener(name,
1042                     this);
1043         }
1044     }
1045 
1046     /**
1047      * Initializes the fields of the form with the data of the form bean. If no
1048      * form bean is specified, no initialization will be performed.
1049      */
1050     private void initFormFields()
1051     {
1052         getForm().initFields(getFormBean());
1053     }
1054 
1055     /**
1056      * Marks all form fields as visited. This method is called when the user
1057      * presses the OK button.
1058      */
1059     private void markFieldsAsVisited()
1060     {
1061         for (String fld : getForm().getFieldNames())
1062         {
1063             visitedFields.add(fld);
1064         }
1065     }
1066 
1067     /**
1068      * Helper method for adding an event listener. The listener is checked for
1069      * <b>null</b> and then added to the central event listener list.
1070      *
1071      * @param <T> the type of the listener
1072      * @param l the listener to be added (must not be <b>null</b>)
1073      * @param listenerClass the event listener class
1074      */
1075     private <T extends EventListener> void addEventListener(T l,
1076             Class<T> listenerClass)
1077     {
1078         if (l == null)
1079         {
1080             throw new IllegalArgumentException(
1081                     "Event listener must not be null!");
1082         }
1083         eventListeners.add(listenerClass, l);
1084     }
1085 
1086     /**
1087      * Creates a {@code FormControllerFormEvent} based on the current committed
1088      * status.
1089      *
1090      * @return the new event
1091      */
1092     private FormControllerFormEvent createFormEvent()
1093     {
1094         return new FormControllerFormEvent(this,
1095                 isCommitted() ? FormControllerFormEvent.Type.FORM_COMMITTED
1096                         : FormControllerFormEvent.Type.FORM_CANCELED);
1097     }
1098 }