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.components;
17
18 import java.util.Collection;
19 import java.util.HashMap;
20 import java.util.LinkedList;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Set;
24 import java.util.Stack;
25 import java.util.concurrent.CopyOnWriteArrayList;
26
27 import net.sf.jguiraffe.di.BeanContext;
28 import net.sf.jguiraffe.di.impl.DefaultBeanContext;
29 import net.sf.jguiraffe.di.impl.SimpleBeanStoreImpl;
30 import net.sf.jguiraffe.gui.builder.event.FormEventManager;
31 import net.sf.jguiraffe.gui.builder.event.PlatformEventManager;
32 import net.sf.jguiraffe.gui.forms.BindingStrategy;
33 import net.sf.jguiraffe.gui.forms.ComponentHandler;
34 import net.sf.jguiraffe.gui.forms.ComponentStore;
35 import net.sf.jguiraffe.gui.forms.FieldHandler;
36 import net.sf.jguiraffe.gui.forms.Form;
37 import net.sf.jguiraffe.transform.TransformerContext;
38
39 import org.apache.commons.jelly.JellyContext;
40
41 /**
42 * <p>
43 * A class for storing temporary data and the results of a form builder
44 * operation.
45 * </p>
46 * <p>
47 * For every call of a form builder an instance of this class is created. During
48 * the build process a couple of information is created. Some of this belongs to
49 * the final result, other parts need to be accessed later to resolve references
50 * or for different purposes. This class is a home for all these kinds of data.
51 * </p>
52 * <p>
53 * Especially the components created during a builder operation must be stored
54 * somewhere so that they can be combined to a resulting {@code Form}
55 * object. For this purpose this class provides an implementation of the
56 * {@code ComponentStore} interface and registers it as the default
57 * component store. To access the component store interested parties do not
58 * directly invoke the methods provided by {@code ComponentStore}, but
59 * access them through the {@code storeXXXX()} and {@code getXXXX()}
60 * methods defined in this class. These methods obtain the current store and
61 * delegate the call to it. During the builder operation a different
62 * {@code ComponentStore} can be temporarily set using the
63 * {@code pushComponentStore()} and {@code popComponentStore()}
64 * methods. This makes it possible for complex components to catch all the
65 * components created in their context; this way sub forms can be created that
66 * for instance represent a row in a table. With the
67 * {@code pushFormContext()} and {@code popFormContext()} methods a
68 * complete new form context can be created, i.e. all components will be added
69 * to the passed in form, and event registration can be performed on the
70 * components that belong to that form.
71 * </p>
72 * <p>
73 * To make the current form and all of its components available to the
74 * <em>dependency injection</em> framework, this class implements the
75 * {@link SimpleBeanStoreImpl.BeanContributor} interface and can therefore
76 * collaborate with a {@link SimpleBeanStoreImpl}. There is also a
77 * {@code getBeanContext()} method that returns a context for querying all
78 * available beans. Through this context the global beans (as defined by the
79 * application) can be queried and the objects created during the builder
80 * operation as well. To access the components and their associated handler
81 * classes the following naming scheme is used:
82 * <ul>
83 * <li>Components (i.e. the platform specific objects created by the
84 * {@code ComponentManager}) can be accessed using the name that is
85 * specified in the Jelly builder script. For instance (if
86 * {@code builderData} is an instance of {@code ComponentBuilderData})
87 * {@code builderData.getBeanContext().getBean("txtFirstName");} would
88 * return the input component associated with the name <em>txtFirstName</em>
89 * (probably a {@code JTextField} if Swing is used).</li>
90 * <li>For accessing the {@code ComponentHandler}s for the managed
91 * components the prefix <em>comp:</em>:
92 * {@code builderData.getBeanContext().getBean("comp:txtFirstName");} would
93 * return the {@code ComponentHandler} for the <em>txtFirstName</em>
94 * component.</li>
95 * <li>{@code FieldHandler}s are also accessed using a special prefix:
96 * <em>field:</em>. So for obtaining the {@code FieldHandler} for the
97 * <em>txtFirstName</em> field you would write
98 * {@code builderData.getBeanContext().getBean("field:txtFirstName");}.</li>
99 * <li>Each component is associated with a {@code WidgetHandler}. For
100 * obtaining a component's {@code WidgetHandler} the prefix
101 * <em>widget:</em> is used, as in
102 * {@code builderData.getBeanContext().getBean("widget:txtFirstName");}.</li>
103 * <li>The {@code Form} object constructed during the current builder
104 * operation can also be accessed under a special reserved key:
105 * {@code Form form = (Form) builderData.getBeanContext().getBean("CURRENT_FORM");}
106 * .</li>
107 * <li>The {@code BeanContext} maintained by this instance can also be
108 * accessed (e.g. to be injected into a bean defined by the current builder
109 * script). This can be done using the key <em>CURRENT_CONTEXT</em>.</li>
110 * <li>The current {@link net.sf.jguiraffe.gui.builder.BuilderData BuilderData}
111 * object is available under the key <em>BUILDER_DATA</em>.</li>
112 * <li>Finally the current {@code ComponentBuilderData} instance itself is
113 * exposed through the {@code BeanStore} implementation. The corresponding
114 * reserved key is named <em>COMPONENT_BUILDER_DATA</em>.</li>
115 * </ul>
116 * To avoid naming conflicts the identifiers for components in the builder
117 * scripts should be chosen in a way that they do not interfere with this
118 * reserved keys. Note that this class also defines constants for these keys.
119 * </p>
120 * <p>
121 * The current instance of this class for the running builder process is stored
122 * in the Jelly Context, where it can be accessed from all tags. From here also
123 * references to the factories needed for creating components can be obtained.
124 * The root container object is maintained by this object, too.
125 * </p>
126 * <p>
127 * Implementation note: This class is not thread-safe. If it is accessed
128 * concurrently by multiple threads, proper synchronization must be ensured.
129 * </p>
130 *
131 * @author Oliver Heger
132 * @version $Id: ComponentBuilderData.java 208 2012-02-11 20:57:33Z oheger $
133 */
134 public class ComponentBuilderData implements Composite,
135 SimpleBeanStoreImpl.BeanContributor
136 {
137 /**
138 * Constant for the prefix for accessing component handlers. This prefix has
139 * to be used for obtaining the {@code ComponentHandler} of a
140 * component from the bean context managed by this class.
141 */
142 public static final String KEY_COMPHANDLER_PREFIX = "comp";
143
144 /**
145 * Constant for the prefix for accessing field handlers. This prefix has to
146 * be used for obtaining the {@code FieldHandler} of a component from
147 * the bean context managed by this class.
148 */
149 public static final String KEY_FIELDHANDLER_PREFIX = "field";
150
151 /**
152 * Constant for the prefix for accessing widget handlers. This prefix has to
153 * be used for obtaining the {@code WidgetHandler} of a component
154 * from the bean context managed by this class.
155 */
156 public static final String KEY_WIDGETHANDLER_PREFIX = "widget";
157
158 /**
159 * Constant for the key for accessing the current form from the bean context
160 * managed by this class.
161 */
162 public static final String KEY_FORM = "CURRENT_FORM";
163
164 /**
165 * Constant for the key for accessing the current instance of this class
166 * from the managed bean context.
167 */
168 public static final String KEY_COMPONENT_BUILDER_DATA = "COMPONENT_BUILDER_DATA";
169
170 /**
171 * Constant for the key for accessing the current bean context. This name
172 * can be used to inject a context into beans defined in a Jelly builder
173 * script.
174 */
175 public static final String KEY_CURRENT_CONTEXT = "CURRENT_CONTEXT";
176
177 /**
178 * Constant for the key for accessing the current {@code BuilderData}
179 * object from the bean context managed by this class. The
180 * {@code BuilderData} object allows access to some important,
181 * application-global objects.
182 */
183 public static final String KEY_BUILDER_DATA = "BUILDER_DATA";
184
185 /** Constant for the key prefix separator. */
186 static final char PREFIX_SEPARATOR = ':';
187
188 /**
189 * Constant for the name under which an object is stored in the jelly
190 * context.
191 */
192 private static final String CTX_NAME = ComponentBuilderData.class.getName();
193
194 /** Stores a reference to the component manager. */
195 private ComponentManager componentManager;
196
197 /** Stores a reference to the field handler factory. */
198 private FieldHandlerFactory fieldHandlerFactory;
199
200 /** The container selector used during the builder operation. */
201 private ContainerSelector containerSelector;
202
203 /** A stack for managing the form-relevant information. */
204 private final Stack<FormContextData> formContextStack;
205
206 /** A stack for storing the forms of the open form contexts. */
207 private final Stack<Form> formStack;
208
209 /** Stores a map with the so far requested widget handlers. */
210 private final Map<Object, WidgetHandler> widgetHandlers;
211
212 /** A list for the context listeners registered at this object. */
213 private final List<FormContextListener> contextListeners;
214
215 /**
216 * Stores a reference to the main form object that is set up during the
217 * builder operation.
218 */
219 private Form form;
220
221 /** Stores the bean context. */
222 private BeanContext beanContext;
223
224 /** Stores the default resource group. */
225 private Object defaultResourceGroup;
226
227 /** Stores the root container for this form building operation. */
228 private Object rootContainer;
229
230 /** A map with the known event managers. */
231 private final Map<Form, FormEventManager> formEventManagers;
232
233 /** Stores the current event manager object. */
234 private FormEventManager eventManager;
235
236 /** Stores a reference to the platform event manager. */
237 private PlatformEventManager platformEventManager;
238
239 /** Stores the tool tip manager. */
240 private ToolTipManager toolTipManager;
241
242 /** Stores the name of the current builder. */
243 private String builderName;
244
245 /** Stores the name of the current default button. */
246 private String defaultButtonName;
247
248 /** A counter that determines whether callbacks are disabled. */
249 private int callBacksEnabledState;
250
251 /**
252 * Creates a new instance of {@code ComponentBuilderData}.
253 */
254 public ComponentBuilderData()
255 {
256 formContextStack = new Stack<FormContextData>();
257 formStack = new Stack<Form>();
258 formEventManagers = new HashMap<Form, FormEventManager>();
259 widgetHandlers = new HashMap<Object, WidgetHandler>();
260 contextListeners = new CopyOnWriteArrayList<FormContextListener>();
261 containerSelector = new DefaultContainerSelector();
262 }
263
264 /**
265 * Initializes the main form to be maintained by the {@code ComponentBuilderData}
266 * object. This method must be called after the construction of this object
267 * to explicitly initialize the {@code Form} object.
268 * @param tctx the {@code TransformerContext}
269 * @param strategy the {@code BindingStrategy} for the form
270 * @throws IllegalArgumentException if a required parameter is <b>null</b>
271 */
272 public void initializeForm(TransformerContext tctx, BindingStrategy strategy)
273 {
274 form = new Form(tctx, strategy);
275 // Open main form context
276 pushFormContext(form);
277 }
278
279 /**
280 * Returns the name of the current builder.
281 *
282 * @return the builder name
283 */
284 public String getBuilderName()
285 {
286 return builderName;
287 }
288
289 /**
290 * Sets the name of the current builder.
291 *
292 * @param builderName the builder name
293 */
294 public void setBuilderName(String builderName)
295 {
296 this.builderName = builderName;
297 }
298
299 /**
300 * Returns the root container.
301 *
302 * @return the root container
303 */
304 public Object getRootContainer()
305 {
306 return rootContainer;
307 }
308
309 /**
310 * Sets the root container. All component tags that are not nested inside a
311 * container tag will add their created objects to this container object.
312 *
313 * @param rootContainer the root container to use
314 */
315 public void setRootContainer(Object rootContainer)
316 {
317 this.rootContainer = rootContainer;
318 }
319
320 /**
321 * Returns the component manager.
322 *
323 * @return the component manager
324 */
325 public ComponentManager getComponentManager()
326 {
327 return componentManager;
328 }
329
330 /**
331 * Sets the component manager. This object will be used to create and
332 * manipulate GUI components.
333 *
334 * @param componentManager the component manager to use
335 */
336 public void setComponentManager(ComponentManager componentManager)
337 {
338 this.componentManager = componentManager;
339 }
340
341 /**
342 * Returns the field handler factory.
343 *
344 * @return the field handler factory
345 */
346 public FieldHandlerFactory getFieldHandlerFactory()
347 {
348 return fieldHandlerFactory;
349 }
350
351 /**
352 * Sets the field handler factory. This object is used by input component
353 * tags for creating the field handlers that are then passed to the internal
354 * form object.
355 *
356 * @param fieldHandlerFactory the handler factory
357 */
358 public void setFieldHandlerFactory(FieldHandlerFactory fieldHandlerFactory)
359 {
360 this.fieldHandlerFactory = fieldHandlerFactory;
361 }
362
363 /**
364 * Returns the {@code ContainerSelector} used by this object.
365 *
366 * @return the {@code ContainerSelector}
367 * @since 1.3
368 */
369 public ContainerSelector getContainerSelector()
370 {
371 return containerSelector;
372 }
373
374 /**
375 * Sets the {@code ContainerSelector} to be used by this object.
376 *
377 * @param containerSelector the {@code ContainerSelector}
378 * @since 1.3
379 */
380 public void setContainerSelector(ContainerSelector containerSelector)
381 {
382 this.containerSelector = containerSelector;
383 }
384
385 /**
386 * Returns the default resource group.
387 *
388 * @return the default resource group
389 */
390 public Object getDefaultResourceGroup()
391 {
392 return defaultResourceGroup;
393 }
394
395 /**
396 * Sets the default resource group. This group will be used if no specific
397 * group is specified in a resource request.
398 *
399 * @param defaultResourceGroup the default resource group
400 */
401 public void setDefaultResourceGroup(Object defaultResourceGroup)
402 {
403 this.defaultResourceGroup = defaultResourceGroup;
404 }
405
406 /**
407 * Returns the {@code Form} object. This object is created during the builder
408 * process. It contains all fields that have been created so far. <strong>Note:</strong>
409 * Before calling this method {@code initializeForm()} must have been invoked;
410 * otherwise an exception is thrown.
411 *
412 * @return the {@code Form} object
413 * @throws IllegalStateException if the form has not yet been initialized
414 */
415 public Form getForm()
416 {
417 if (form == null)
418 {
419 throw new IllegalStateException("Form has not been initialized!"
420 + "Call initializeForm() first.");
421 }
422 return form;
423 }
424
425 /**
426 * Returns the transformer context.
427 *
428 * @return the transformer context
429 */
430 public TransformerContext getTransformerContext()
431 {
432 return getForm().getTransformerContext();
433 }
434
435 /**
436 * Returns the {@code FormEventManager} used by this builder
437 * operation. This object can be used to register event handlers at
438 * components created during the building process.
439 *
440 * @return the event manager object
441 */
442 public FormEventManager getEventManager()
443 {
444 if (eventManager == null)
445 {
446 assert getContextForm() != null : "No context form set!";
447 eventManager = getEventManagerForForm(getContextForm());
448 }
449 return eventManager;
450 }
451
452 /**
453 * Allows to set an event manager. All event handling logic, e.g.
454 * registering event listeners, will be done by this object. Normally it is
455 * not necessary to set a specific event manager; there is a default
456 * instance. This method is intended for complex components that need to
457 * hook into the event logic.
458 *
459 * @param evMan the new event manager to be set (can be <b>null</b>, then
460 * the default event manager for the current form will be set)
461 */
462 public void setEventManager(FormEventManager evMan)
463 {
464 eventManager = evMan;
465 }
466
467 /**
468 * Returns the {@code ToolTipManager} associated with this object. If no
469 * specific {@code ToolTipManager} has been set, a default instance is
470 * created and returned.
471 *
472 * @return the {@code ToolTipManager}
473 */
474 public ToolTipManager getToolTipManager()
475 {
476 if (toolTipManager == null)
477 {
478 toolTipManager = createToolTipManager();
479 }
480
481 return toolTipManager;
482 }
483
484 /**
485 * Sets the {@code ToolTipManager} for this object. This {@code
486 * ToolTipManager} is then used for manipulating tool tips for components.
487 * Normally it is not necessary to set a specific tool tip manager. If none
488 * is set, a default instance is created. This method can be used to inject
489 * a custom tool tip manager.
490 *
491 * @param toolTipManager the {@code ToolTipManager} to be used
492 */
493 public void setToolTipManager(ToolTipManager toolTipManager)
494 {
495 this.toolTipManager = toolTipManager;
496 }
497
498 /**
499 * Returns a reference to the current component store. This store will be
500 * used for searching and storing components.
501 *
502 * @return the currently used {@code ComponentStore}
503 */
504 public ComponentStore getComponentStore()
505 {
506 return fetchFormContextData().componentStore;
507 }
508
509 /**
510 * Adds a new component store to this object that will replace the current
511 * store. All newly created components will be added to this store. It will
512 * be active until {@code popComponentStore()} is called, then the
513 * replaced component store will become the current store again. The purpose
514 * of this method is to allow complex tags to install their own store so
515 * that all components created in their context are put into this store.
516 * Thus it is possible to create sub forms or things like that. Call backs
517 * that are registered using the {@code addCallBack()} method will
518 * also be affected: they are always created in the context of the current
519 * component store and executed when {@code popComponentStore()} is
520 * invoked.
521 *
522 * @param store the new store (must not be <b>null</b>)
523 * @return the old active store; this store is replaced by the new one
524 * @throws IllegalArgumentException if the passed in store is <b>null</b>
525 * @see #popComponentStore()
526 */
527 public ComponentStore pushComponentStore(ComponentStore store)
528 {
529 if (store == null)
530 {
531 throw new IllegalArgumentException(
532 "Component store must not be null!");
533 }
534 ComponentStore result = formContextStack.isEmpty() ? null
535 : getComponentStore();
536 FormContextData fcd = new FormContextData(store);
537 formContextStack.push(fcd);
538 return result;
539 }
540
541 /**
542 * Removes a component store from this object. This method is the counter
543 * part of {@code pushComponentStore()}. It removes the last pushed
544 * component store, making the store before to the current store again. If
545 * any call backs have been registered for the popped component store, they
546 * will now be invoked.
547 *
548 * @return the store that was removed
549 * @throws FormBuilderException if an error occurs when invoking call backs
550 * @throws java.util.EmptyStackException if there are no more stores to pop
551 */
552 public ComponentStore popComponentStore() throws FormBuilderException
553 {
554 invokeCallBacks();
555 FormContextData fcd = formContextStack.pop();
556 return fcd.getComponentStore();
557 }
558
559 /**
560 * Returns the event manager for the specified form. An event manager is
561 * always associated with a {@code Form} object; it uses the form's
562 * {@link ComponentStore} for retrieving the components, for
563 * which event listeners are to be registered. With this method an event
564 * manager for a given form can be requested. If no such event manager
565 * exists, it will be created now. Per default there will be a single event
566 * manager for the main form constructed during the build process. However
567 * if complex components are involved that construct sub forms (which need
568 * their own event handling logic), it may be necessary to have a different
569 * event manager.
570 *
571 * @param f the form the event manager is associated with
572 * @return the event manager for this form
573 */
574 public FormEventManager getEventManagerForForm(Form f)
575 {
576 FormEventManager evMan = formEventManagers.get(f);
577 if (evMan == null)
578 {
579 evMan = createEventManager();
580 evMan.setComponentStore(f.getComponentStore());
581 formEventManagers.put(f, evMan);
582 }
583 return evMan;
584 }
585
586 /**
587 * Adds a {@code FormContextListener} object to this data object. The
588 * listener receives notifications when a new form context is created or the
589 * current context is closed. This method can be called from an arbitrary
590 * thread
591 *
592 * @param listener the listener to be registered (must not be <b>null</b>)
593 * @throws IllegalArgumentException if the listener is <b>null</b>
594 * @since 1.3
595 */
596 public void addFormContextListener(FormContextListener listener)
597 {
598 if (listener == null)
599 {
600 throw new IllegalArgumentException("Listener must not be null!");
601 }
602 contextListeners.add(listener);
603 }
604
605 /**
606 * Removes the specified {@code FormContextListener} from this object.
607 *
608 * @param listener the listener to be removed
609 * @since 1.3
610 */
611 public void removeFormContextListener(FormContextListener listener)
612 {
613 contextListeners.remove(listener);
614 }
615
616 /**
617 * Installs a new form context for the specified form. Works like the method
618 * with the same name, but passes <b>null</b> for the source.
619 *
620 * @param f the sub form of the new form context (must not be <b>null</b>)
621 * @throws IllegalArgumentException if the form instance is <b>null</b>
622 * @see #pushComponentStore(ComponentStore)
623 * @see #getEventManagerForForm(Form)
624 */
625 public void pushFormContext(Form f)
626 {
627 pushFormContext(f, null);
628 }
629
630 /**
631 * Installs a new form context for the specified form and passes information
632 * about the responsible source. This method can be called by complex
633 * components that create their own (sub) form instances. It has the
634 * following effect:
635 * <ul>
636 * <li>{@code pushComponentStore()} is called with the component store of
637 * the specified form. So newly created components will be added to this
638 * store.</li>
639 * <li>The event manager for this form is obtained using
640 * {@code getEventManagerForForm()} and made to the active event manager.
641 * This ensures that event listener registration logic for the sub form is
642 * handled by the appropriate event manager.</li>
643 * <li>Registered {@code FormContextListener} objects are notified about the
644 * newly created context.</li>
645 * </ul>
646 *
647 * @param form the sub form of the new form context (must not be
648 * <b>null</b>)
649 * @param source the source object responsible for the form context
650 * @throws IllegalArgumentException if the form instance is <b>null</b>
651 * @see #pushComponentStore(ComponentStore)
652 * @see #getEventManagerForForm(Form)
653 * @since 1.3
654 */
655 public void pushFormContext(Form form, Object source)
656 {
657 if (form == null)
658 {
659 throw new IllegalArgumentException(
660 "Form for context must not be null!");
661 }
662 formStack.push(form);
663 pushComponentStore(form.getComponentStore());
664 setEventManager(null);
665 fireFormContextCreated(form, source);
666 }
667
668 /**
669 * Removes the outer most form context. Works like the method with the same
670 * name, but no information about a source is provided.
671 *
672 * @return the {@code Form} instance of the removed form context
673 * @throws FormBuilderException if an error occurs when closing the current
674 * form context
675 * @throws IllegalStateException if {@code pushFormContext()} has not been
676 * called before (and the context to be removed is the root context)
677 */
678 public Form popFormContext() throws FormBuilderException
679 {
680 return popFormContext(null);
681 }
682
683 /**
684 * Removes the outer most form context passing in information about the
685 * responsible source. This method is the counter part of
686 * {@code pushFormContext()}. It makes the previous form to the active form
687 * again (and ensures that the correct component store and event manager are
688 * selected. This method must be called after processing of a sub form has
689 * completed. <em>Note:</em> This method calls {@code popComponentStore()}
690 * to make the component store of the previous form to the current one.
691 * Clients must be aware that the calls to the push and pop methods must be
692 * symmetric and correctly nested, otherwise the association between the
693 * current forms and their component stores may get lost!
694 *
695 * @param source the source object responsible for the form context
696 * @return the {@code Form} instance of the removed form context
697 * @throws FormBuilderException if an error occurs when closing the current
698 * form context
699 * @throws IllegalStateException if {@code pushFormContext()} has not been
700 * called before (and the context to be removed is the root context)
701 * @see #pushFormContext(Form, Object)
702 * @since 1.3
703 */
704 public Form popFormContext(Object source) throws FormBuilderException
705 {
706 if (formStack.size() <= 1)
707 {
708 // only root context present?
709 throw new IllegalStateException("Root context must not be closed!");
710 }
711
712 Form result = formStack.pop();
713 popComponentStore();
714 setEventManager(null);
715 fireFormContextClosed(result, source);
716 return result;
717 }
718
719 /**
720 * Returns the form of the current form context. While the
721 * {@code getForm()} method always returns the main form of this
722 * builder operation, this method takes the current form context into
723 * account, i.e. if {@code pushFormContext()} has been called before,
724 * the form passed to this method will be returned.
725 *
726 * @return the form of the current form context
727 * @see #pushFormContext(Form)
728 */
729 public Form getContextForm()
730 {
731 assert !formStack.isEmpty() : "No form context open!";
732 return formStack.peek();
733 }
734
735 /**
736 * Stores the specified component in the current {@code ComponentStore}.
737 * From there it can be accessed e.g. if another component defines a
738 * reference to it.
739 *
740 * @param name the name of this component
741 * @param component the component itself
742 */
743 public void storeComponent(String name, Object component)
744 {
745 getComponentStore().add(name, component);
746 }
747
748 /**
749 * Returns the component with the given name from the currently active
750 * {@code ComponentStore}. If no such component can be found, the
751 * method tries to find a component handler with this name and extract the
752 * component object from this handler. If this fails, too, <b>null</b> is
753 * returned.
754 *
755 * @param name the name of the desired component
756 * @return the component
757 */
758 public Object getComponent(String name)
759 {
760 Object component = getComponentStore().findComponent(name);
761 if (component == null)
762 {
763 ComponentHandler<?> handler = getComponentHandler(name);
764 return (handler != null) ? handler.getComponent() : null;
765 }
766 else
767 {
768 return component;
769 }
770 }
771
772 /**
773 * Stores the given component handler in the current
774 * {@code ComponentStore}. From there it can later be accessed,
775 * which is useful if it is referenced by other tags.
776 *
777 * @param name the name of this component handler
778 * @param handler the handler itself
779 */
780 public void storeComponentHandler(String name, ComponentHandler<?> handler)
781 {
782 getComponentStore().addComponentHandler(name, handler);
783 }
784
785 /**
786 * Returns the component handler with the specified name from the current
787 * {@code ComponentStore}. If no such handler can be found, return
788 * value is <b>null </b>.
789 *
790 * @param name the name of the desired handler
791 * @return the handler
792 */
793 public ComponentHandler<?> getComponentHandler(String name)
794 {
795 return getComponentStore().findComponentHandler(name);
796 }
797
798 /**
799 * Stores the specified field handler. This field will be added to the
800 * internally maintained form object. The component that is associated with
801 * the field handler will also be accessible by the
802 * {@link #getComponent(String)} and
803 * {@link #getComponentHandler(String)} methods.
804 *
805 * @param name the name of the field
806 * @param fld the field handler
807 */
808 public void storeFieldHandler(String name, FieldHandler fld)
809 {
810 getComponentStore().addFieldHandler(name, fld);
811 storeComponentHandler(name, fld.getComponentHandler());
812 }
813
814 /**
815 * Returns the field handler with the specified name from the current
816 * {@code ComponentStore} object. If no handler exists with this
817 * name, <b>null</b> is returned.
818 *
819 * @param name the name of the desired field handler
820 * @return the field handler with this name
821 */
822 public FieldHandler getFieldHandler(String name)
823 {
824 return getComponentStore().findFieldHandler(name);
825 }
826
827 /**
828 * Returns a {@code WidgetHandler} for accessing the component with
829 * the given name. A component with this name is searched in the current
830 * {@code ComponentStore} object. If it cannot be found, <b>null</b>
831 * will be returned. Otherwise the current {@code ComponentManager}
832 * is asked to create a {@code WidgetHandler} object for this
833 * component. A once created {@code WidgetHandler} object will be
834 * cached, so that it can be directly returned if it is queried for the
835 * second time.
836 *
837 * @param name the name of the component
838 * @return a {@code WidgetHandler} object wrapping this component
839 */
840 public WidgetHandler getWidgetHandler(String name)
841 {
842 return getWidgetHandlerForComponent(getComponent(name));
843 }
844
845 /**
846 * Returns a {@code WidgetHandler} object for the specified
847 * component. This method checks whether already a
848 * {@code WidgetHandler} for the passed in component has been created
849 * (by looking it up in the internal cache). If this is the case, it can be
850 * directly returned. Otherwise the current {@code ComponentManager}
851 * is asked to create a new {@code WidgetHandler} instance now. If
852 * the passed in component is <b>null</b>, <b>null</b> will be returned.
853 *
854 * @param component the component, for which a {@code WidgetHandler}
855 * is to be obtained
856 * @return the {@code WidgetHandler} for this component
857 */
858 public WidgetHandler getWidgetHandlerForComponent(Object component)
859 {
860 if (component == null)
861 {
862 return null;
863 }
864
865 WidgetHandler wh = widgetHandlers.get(component);
866 if (wh == null)
867 {
868 wh = getComponentManager().getWidgetHandlerFor(component);
869 widgetHandlers.put(component, wh);
870 }
871 return wh;
872 }
873
874 /**
875 * Adds the specified component to the root container. This method is called
876 * by component tags that are not nested inside container tags.
877 *
878 * @param comp the component to add
879 * @param constraints the constraints for this component
880 * @throws FormBuilderRuntimeException if no root container was set
881 */
882 public void addComponent(Object comp, Object constraints)
883 throws FormBuilderRuntimeException
884 {
885 if (getRootContainer() == null)
886 {
887 throw new FormBuilderRuntimeException("No root container was set!");
888 }
889 fetchComponentHandler().addContainerComponent(getRootContainer(), comp,
890 constraints);
891 }
892
893 /**
894 * Sets the layout for the root container. This method is called by layout
895 * tags that are not nested inside container tags.
896 *
897 * @param layout the layout object to set
898 */
899 public void setLayout(Object layout)
900 {
901 if (getRootContainer() == null)
902 {
903 throw new FormBuilderRuntimeException("No root container was set!");
904 }
905 fetchComponentHandler().setContainerLayout(getRootContainer(), layout);
906 }
907
908 /**
909 * Returns the concrete container component. In this case this is the root
910 * container.
911 *
912 * @return the container component
913 */
914 public Object getContainer()
915 {
916 return getRootContainer();
917 }
918
919 /**
920 * Disables the call back mechanism. Newly added callbacks are ignored and
921 * will not be executed by {@link #invokeCallBacks()}. Calls to this method
922 * can be nested. A corresponding number of {@link #enableCallBacks()} is
923 * necessary in order to enable callbacks again.
924 *
925 * @since 1.3
926 */
927 public void disableCallBacks()
928 {
929 callBacksEnabledState--;
930 }
931
932 /**
933 * Enables the call back mechanism. This is the counter part of
934 * {@link #disableCallBacks()}.
935 *
936 * @since 1.3
937 */
938 public void enableCallBacks()
939 {
940 callBacksEnabledState++;
941 }
942
943 /**
944 * Returns a flag whether the call back mechanism is currently enabled. If
945 * this method returns <b>false</b>, all callbacks added to this object are
946 * ignored.
947 *
948 * @return <b>true</b> if callbacks are enabled, <b>false</b> otherwise
949 * @since 1.3
950 */
951 public boolean isCallBacksEnabled()
952 {
953 return callBacksEnabledState >= 0;
954 }
955
956 /**
957 * Registers the specified call back at this builder data object. It will be
958 * invoked after the building operation is complete for the current form
959 * context.
960 *
961 * @param callBack the call back object
962 * @param param a parameter object; this object is passed to the call back
963 * when it is invoked
964 */
965 public void addCallBack(ComponentBuilderCallBack callBack, Object param)
966 {
967 if (isCallBacksEnabled())
968 {
969 fetchFormContextData().getCallBacks().add(
970 new CallBackData(callBack, param));
971 }
972 }
973
974 /**
975 * Invokes all call backs that are registered at this object for the current
976 * form context.
977 *
978 * @throws FormBuilderException if an exception is thrown by one of the call
979 * backs
980 */
981 public void invokeCallBacks() throws FormBuilderException
982 {
983 for (CallBackData cbd : fetchFormContextData().getCallBacks())
984 {
985 cbd.invokeCallBack(this);
986 }
987 }
988
989 /**
990 * Returns a set with the names of all contained bean. This implementation
991 * returns the names of all stored components. If these components are
992 * associated with handlers, the correspondingly prefixed names are also
993 * contained in the set.
994 *
995 * @param names the set in which to store the names of the managed beans
996 */
997 public void beanNames(Set<String> names)
998 {
999 ComponentStore cStore = getComponentStore();
1000 appendNames(names, cStore.getComponentNames(), null);
1001 appendNames(names, cStore.getComponentNames(), KEY_WIDGETHANDLER_PREFIX);
1002 appendNames(names, cStore.getComponentHandlerNames(),
1003 KEY_COMPHANDLER_PREFIX);
1004 appendNames(names, cStore.getFieldHandlerNames(),
1005 KEY_FIELDHANDLER_PREFIX);
1006 names.add(KEY_FORM);
1007 names.add(KEY_COMPONENT_BUILDER_DATA);
1008 }
1009
1010 /**
1011 * Returns the bean with the given name. This implementation supports the
1012 * names of the stored components. If for a component a
1013 * {@code ComponentHandler}, a {@code FieldHandler}, or a
1014 * {@code WidgetHandler} is available, the correspondingly prefixed
1015 * name is also supported. In addition the other reserved keys as described
1016 * in the header comment can be used.
1017 *
1018 * @param name the name of the desired bean provider
1019 * @return the bean with this name or <b>null</b>
1020 */
1021 public Object getBean(String name)
1022 {
1023 Object bean = null;
1024
1025 if (KEY_COMPONENT_BUILDER_DATA.equals(name))
1026 {
1027 bean = this;
1028 }
1029 else if (KEY_FORM.equals(name))
1030 {
1031 bean = getForm();
1032 }
1033 else if (KEY_CURRENT_CONTEXT.equals(name))
1034 {
1035 bean = getBeanContext();
1036 }
1037
1038 else
1039 {
1040 if (name != null)
1041 {
1042 int pos = name.indexOf(PREFIX_SEPARATOR);
1043 if (pos > 0)
1044 {
1045 String prefix = name.substring(0, pos);
1046 String cname = name.substring(pos + 1);
1047 bean = getPrefixedComponent(prefix, cname);
1048 }
1049 }
1050
1051 if (bean == null)
1052 {
1053 // always check for a component with this name
1054 bean = getComponent(name);
1055 }
1056 }
1057
1058 return bean;
1059 }
1060
1061 /**
1062 * Initializes the specified bean store object. This method is called by the
1063 * builder when the {@code BeanContext} used during the builder
1064 * operation is constructed. This implementation will add the static beans
1065 * to the given store and register this object as
1066 * {@code BeanContributor}.
1067 *
1068 * @param store the store to be initialized
1069 */
1070 public void initBeanStore(SimpleBeanStoreImpl store)
1071 {
1072 store.addBean(KEY_COMPONENT_BUILDER_DATA, this);
1073 store.addBeanContributor(this);
1074 }
1075
1076 /**
1077 * Returns the {@code BeanContext} managed by this instance. If not
1078 * context has been set, a default context will be returned, which allows
1079 * access only to the beans defined in this object (i.e. the components
1080 * created during the builder operation and their handlers). Typically the
1081 * builder will create a context in the initialization phase of a builder
1082 * operation.
1083 *
1084 * @return the context maintained by this instance
1085 */
1086 public BeanContext getBeanContext()
1087 {
1088 if (beanContext == null)
1089 {
1090 SimpleBeanStoreImpl store = new SimpleBeanStoreImpl();
1091 initBeanStore(store);
1092 // create a default context
1093 beanContext = new DefaultBeanContext(store);
1094 }
1095 return beanContext;
1096 }
1097
1098 /**
1099 * Allows to set a specific {@code BeanContext}. This is not necessary
1100 * normally, because the bean context is correctly set up automatically
1101 * taking account the appropriate hierarchy of contexts and bean stores.
1102 *
1103 * @param ctx the new bean context to be used
1104 */
1105 public void setBeanContext(BeanContext ctx)
1106 {
1107 beanContext = ctx;
1108 }
1109
1110 /**
1111 * Returns the name of the default button. This can be <b>null</b> if no
1112 * default button has been set.
1113 *
1114 * @return the name of the default button
1115 */
1116 public String getDefaultButtonName()
1117 {
1118 return defaultButtonName;
1119 }
1120
1121 /**
1122 * Sets the name of the default button. This method is called by a button
1123 * tag if the button is marked as default button of the current window.
1124 * Window tags can evaluate this property to decide whether some action is
1125 * necessary to actually make this button the window's default button.
1126 *
1127 * @param defaultButtonName the name of the default button; can be
1128 * <b>null</b> to clear the default button
1129 */
1130 public void setDefaultButtonName(String defaultButtonName)
1131 {
1132 this.defaultButtonName = defaultButtonName;
1133 }
1134
1135 /**
1136 * Stores this instance in the specified context. From there it can be
1137 * retrieved using the {@code get()} method.
1138 *
1139 * @param ctx the Jelly context (must not be <b>null</b>)
1140 * @throws IllegalArgumentException if the context is <b>null</b>
1141 */
1142 public void put(JellyContext ctx)
1143 {
1144 if (ctx == null)
1145 {
1146 throw new IllegalArgumentException("Context must not be null!");
1147 }
1148 ctx.setVariable(CTX_NAME, this);
1149 }
1150
1151 /**
1152 * Returns the instance of this class stored in the specified Jelly context.
1153 * If no such instance can be found, <b>null</b> is returned.
1154 *
1155 * @param ctx the Jelly context
1156 * @return the instance of this class stored in this context
1157 */
1158 public static ComponentBuilderData get(JellyContext ctx)
1159 {
1160 return (ctx != null) ? (ComponentBuilderData) ctx
1161 .findVariable(CTX_NAME) : null;
1162 }
1163
1164 /**
1165 * Returns the associated component manager. If this object is not set, a
1166 * runtime exception will be thrown.
1167 *
1168 * @return the component manager
1169 * @throws FormBuilderRuntimeException if no component manager was set
1170 */
1171 protected ComponentManager fetchComponentHandler()
1172 throws FormBuilderRuntimeException
1173 {
1174 if (getComponentManager() == null)
1175 {
1176 throw new FormBuilderRuntimeException("No component manager set!");
1177 }
1178 return getComponentManager();
1179 }
1180
1181 /**
1182 * Creates the event manager object. This method is called when the event
1183 * manager is accessed for the first time. It creates a new instance of
1184 * {@code FormEventManager} and initializes it with the platform
1185 * specific event manager obtained from the component manager.
1186 *
1187 * @return the new event manager
1188 * @throws FormBuilderRuntimeException if no component manager was set
1189 */
1190 protected FormEventManager createEventManager()
1191 {
1192 if (platformEventManager == null)
1193 {
1194 platformEventManager = createPlatformEventManager();
1195 }
1196
1197 FormEventManager evMan = new FormEventManager(platformEventManager);
1198 return evMan;
1199 }
1200
1201 /**
1202 * Creates the platform specific event manager. This method is called once
1203 * on first access to the platform event manager. This implementation
1204 * obtains the event manager from the component handler.
1205 *
1206 * @return the platform specific event manager
1207 * @throws FormBuilderRuntimeException if no component manager was set
1208 */
1209 protected PlatformEventManager createPlatformEventManager()
1210 {
1211 return fetchComponentHandler().createEventManager();
1212 }
1213
1214 /**
1215 * Creates the {@code ToolTipManager}. This method is called when the
1216 * {@code ToolTipManager} is accessed for the first time, but no specific
1217 * instance has been set. This implementation creates a default tool tip
1218 * manager object.
1219 *
1220 * @return the new {@code ToolTipManager} instance
1221 */
1222 protected ToolTipManager createToolTipManager()
1223 {
1224 return new DefaultToolTipManager(this);
1225 }
1226
1227 /**
1228 * Notifies registered listeners about a newly created form context.
1229 *
1230 * @param form the sub form of the new form context
1231 * @param source the source object responsible for the form context
1232 */
1233 private void fireFormContextCreated(Form form, Object source)
1234 {
1235 for (FormContextListener listener : contextListeners)
1236 {
1237 listener.formContextCreated(form, source);
1238 }
1239 }
1240
1241 /**
1242 * Notifies registered listeners about a form context that has been closed.
1243 *
1244 * @param form the sub form of the closed form context
1245 * @param source the source object responsible for the form context
1246 */
1247 private void fireFormContextClosed(Form form, Object source)
1248 {
1249 for (FormContextListener listener : contextListeners)
1250 {
1251 listener.formContextClosed(form, source);
1252 }
1253 }
1254
1255 /**
1256 * Returns a reference to the current form component data object that holds
1257 * information about the currently constructed form.
1258 *
1259 * @return the current form component data object
1260 */
1261 private FormContextData fetchFormContextData()
1262 {
1263 return formContextStack.peek();
1264 }
1265
1266 /**
1267 * Tries to resolve the specified prefixed name.
1268 *
1269 * @param prefix the prefix
1270 * @param name the name
1271 * @return the corresponding component
1272 */
1273 private Object getPrefixedComponent(String prefix, String name)
1274 {
1275 Object comp = null;
1276
1277 if (KEY_COMPHANDLER_PREFIX.equals(prefix))
1278 {
1279 comp = getComponentHandler(name);
1280 }
1281 else if (KEY_FIELDHANDLER_PREFIX.equals(prefix))
1282 {
1283 comp = getFieldHandler(name);
1284 }
1285 else if (KEY_WIDGETHANDLER_PREFIX.equals(prefix))
1286 {
1287 comp = getWidgetHandler(name);
1288 }
1289
1290 return comp;
1291 }
1292
1293 /**
1294 * Processes the names for elements with a given prefix and adds them to the
1295 * target name set.
1296 *
1297 * @param target the target name set
1298 * @param source the source name set
1299 * @param prefix the prefix to be used
1300 */
1301 private static void appendNames(Set<String> target, Set<String> source,
1302 String prefix)
1303 {
1304 for (String s : source)
1305 {
1306 target.add(prefixedName(prefix, s));
1307 }
1308 }
1309
1310 /**
1311 * Creates a prefixed name to be used for the bean store implementation.
1312 *
1313 * @param prefix the prefix (can be <b>null</b>)
1314 * @param name the name
1315 * @return the prefixed name
1316 */
1317 private static String prefixedName(String prefix, String name)
1318 {
1319 if (prefix == null)
1320 {
1321 return name;
1322 }
1323
1324 StringBuilder buf = new StringBuilder(prefix.length() + name.length()
1325 + 1);
1326 buf.append(prefix).append(PREFIX_SEPARATOR).append(name);
1327 return buf.toString();
1328 }
1329
1330 /**
1331 * A simple data class for storing information about registered call backs.
1332 */
1333 private static class CallBackData
1334 {
1335 /** Stores the call back. */
1336 private ComponentBuilderCallBack callBack;
1337
1338 /** Stores the parameter for the call back. */
1339 private Object param;
1340
1341 /**
1342 * Creates a new instance of {@code CallBackData} and initializes
1343 * it.
1344 *
1345 * @param cb the call back
1346 * @param p the parameter for the call back
1347 */
1348 public CallBackData(ComponentBuilderCallBack cb, Object p)
1349 {
1350 callBack = cb;
1351 param = p;
1352 }
1353
1354 /**
1355 * Invokes the stored call back.
1356 *
1357 * @param data the builder data object
1358 * @throws FormBuilderException if the call back throws an exception
1359 */
1360 public void invokeCallBack(ComponentBuilderData data)
1361 throws FormBuilderException
1362 {
1363 callBack.callBack(data, param);
1364 }
1365 }
1366
1367 /**
1368 * A simple data class that stores all information needed for the currently
1369 * defined form. Typically only a single form is constructed during a
1370 * builder process. But there may be complex components that create sub
1371 * forms. In this case the relevant information about the current form is
1372 * packed in an instance of this class and pushed on a stack. After the
1373 * complex component and its sub form are completely processed the old state
1374 * is restored by popping the information back from the stack.
1375 */
1376 private static class FormContextData
1377 {
1378 /** Stores a reference to the component store associated with the form. */
1379 private final ComponentStore componentStore;
1380
1381 /** A list for the registered call backs. */
1382 private final Collection<CallBackData> callBacks;
1383
1384 /**
1385 * Creates a new instance of {@code FormComponentData} and sets
1386 * the component store. An empty collection for the call backs will also
1387 * be created.
1388 *
1389 * @param store the associated component store
1390 */
1391 public FormContextData(ComponentStore store)
1392 {
1393 componentStore = store;
1394 callBacks = new LinkedList<CallBackData>();
1395 }
1396
1397 /**
1398 * Returns the {@code ComponentStore}.
1399 *
1400 * @return the {@code ComponentStore}
1401 */
1402 public ComponentStore getComponentStore()
1403 {
1404 return componentStore;
1405 }
1406
1407 /**
1408 * Returns the collection with callback objects.
1409 *
1410 * @return the callBacks the collection with callback objects
1411 */
1412 public Collection<CallBackData> getCallBacks()
1413 {
1414 return callBacks;
1415 }
1416 }
1417 }