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.app;
17  
18  import java.util.HashMap;
19  import java.util.Locale;
20  import java.util.Map;
21  import java.util.concurrent.ConcurrentHashMap;
22  
23  import net.sf.jguiraffe.di.BeanContext;
24  import net.sf.jguiraffe.di.BeanCreationEvent;
25  import net.sf.jguiraffe.di.BeanCreationListener;
26  import net.sf.jguiraffe.di.ClassLoaderProvider;
27  import net.sf.jguiraffe.di.MutableBeanStore;
28  import net.sf.jguiraffe.gui.builder.Builder;
29  import net.sf.jguiraffe.gui.builder.action.ActionStore;
30  import net.sf.jguiraffe.gui.builder.utils.GUISynchronizer;
31  import net.sf.jguiraffe.gui.builder.utils.MessageOutput;
32  import net.sf.jguiraffe.gui.builder.window.Window;
33  import net.sf.jguiraffe.gui.forms.BindingStrategy;
34  import net.sf.jguiraffe.resources.Message;
35  import net.sf.jguiraffe.resources.ResourceManager;
36  import net.sf.jguiraffe.transform.ValidationMessageHandler;
37  
38  import org.apache.commons.configuration.Configuration;
39  
40  /**
41   * <p>
42   * A default implementation of the <code>ApplicationContext</code> interface.
43   * </p>
44   * <p>
45   * This class is used by the <code>Application</code> class to store global
46   * information that is needed by different components of an application. Because
47   * an <code>ApplicationContext</code> implementation also implements the
48   * <code>TransformerContext</code> interface instances can also be passed to
49   * transformer objects.
50   * </p>
51   * <p>
52   * Note that this implementation is not thread-safe. The intended use case is
53   * that an instance is created at application startup and initialized with the
54   * central helper objects managed by the instance. Later these objects should
55   * not be changed any more. If used in this way, the instance can be used from
56   * both the event dispatch thread and from commands executing in background
57   * threads.
58   * </p>
59   *
60   * @see net.sf.jguiraffe.transform.TransformerContext
61   * @author Oliver Heger
62   * @version $Id: ApplicationContextImpl.java 205 2012-01-29 18:29:57Z oheger $
63   */
64  public class ApplicationContextImpl implements ApplicationContext
65  {
66      /** Stores the actual locale. */
67      private volatile Locale locale;
68  
69      /** Stores a reference to the resource manager to use. */
70      private ResourceManager resourceManager;
71  
72      /** Stores a reference to the global configuration object. */
73      private Configuration configuration;
74  
75      /** Stores the global bean context. */
76      private BeanContext beanContext;
77  
78      /** Stores the validation message handler.*/
79      private ValidationMessageHandler validationMessageHandler;
80  
81      /** Stores the object for displaying messages. */
82      private MessageOutput msgOut;
83  
84      /** Stores the application's main window. */
85      private Window mainWindow;
86  
87      /** Stores the application's main action store. */
88      private ActionStore actionStore;
89  
90      /** A map for maintaining properties. */
91      private final Map<String, Object> properties;
92  
93      /** The map for storing typed properties. */
94      private final Map<Class<?>, Object> typedProperties;
95  
96      /**
97       * Creates a new instance of <code>ApplicationContextImpl</code>. The
98       * instance is not yet initialized.
99       */
100     public ApplicationContextImpl()
101     {
102         properties = new HashMap<String, Object>();
103         typedProperties = new ConcurrentHashMap<Class<?>, Object>();
104         actionStore = new ActionStore();
105     }
106 
107     /**
108      * Creates a new instance of <code>ApplicationContextImpl</code> and sets
109      * the current locale.
110      *
111      * @param locale the <code>Locale</code>
112      */
113     public ApplicationContextImpl(Locale locale)
114     {
115         this();
116         setLocale(locale);
117     }
118 
119     /**
120      * Creates a new instance of <code>ApplicationContextImpl</code> and sets
121      * the current locale and the resource manager to use.
122      *
123      * @param locale the <code>Locale</code>
124      * @param resMan the <code>ResourceManager</code> to use
125      */
126     public ApplicationContextImpl(Locale locale, ResourceManager resMan)
127     {
128         this(locale);
129         setResourceManager(resMan);
130     }
131 
132     /**
133      * Returns the current locale. If no specific locale has been set yet, the
134      * system's default locale will be used.
135      *
136      * @return the <code>Locale</code>
137      */
138     public Locale getLocale()
139     {
140         return (locale != null) ? locale : Locale.getDefault();
141     }
142 
143     /**
144      * Sets the current locale. Note: This implementation is thread-safe; the
145      * newly set locale is visible for other threads, too.
146      *
147      * @param locale the new <code>Locale</code>
148      */
149     public void setLocale(Locale locale)
150     {
151         this.locale = locale;
152     }
153 
154     /**
155      * Returns the actual resource manager.
156      *
157      * @return the <code>ResourceManager</code> for accessing resources
158      */
159     public ResourceManager getResourceManager()
160     {
161         return resourceManager;
162     }
163 
164     /**
165      * Sets the resource manager. Access to system resources will be handled by
166      * this object.
167      *
168      * @param resourceManager the <code>ResourceManager</code>
169      */
170     public void setResourceManager(ResourceManager resourceManager)
171     {
172         this.resourceManager = resourceManager;
173     }
174 
175     /**
176      * Returns a map with properties maintained by this context.
177      *
178      * @return a map with properties
179      */
180     public Map<String, Object> properties()
181     {
182         return properties;
183     }
184 
185     /**
186      * Returns a reference to the global configuration.
187      *
188      * @return the configuration object
189      */
190     public Configuration getConfiguration()
191     {
192         return configuration;
193     }
194 
195     /**
196      * Sets the global configuration.
197      *
198      * @param configuration the configuration object
199      */
200     public void setConfiguration(Configuration configuration)
201     {
202         this.configuration = configuration;
203     }
204 
205     /**
206      * Returns the global bean context.
207      *
208      * @return the bean context
209      */
210     public BeanContext getBeanContext()
211     {
212         return beanContext;
213     }
214 
215     /**
216      * Sets the global bean context.
217      *
218      * @param beanContext the new bean context
219      */
220     public void setBeanContext(BeanContext beanContext)
221     {
222         this.beanContext = beanContext;
223     }
224 
225     /**
226      * Returns the {@code ClassLoaderProvider}. This implementation always returns
227      * the {@code ClassLoaderProvider} managed by the current {@link BeanContext}.
228      *
229      * @return the {@code ClassLoaderProvider}
230      */
231     public ClassLoaderProvider getClassLoaderProvider()
232     {
233         BeanContext bctx = getBeanContext();
234         return (bctx == null) ? null : bctx.getClassLoaderProvider();
235     }
236 
237     /**
238      * Sets the {@code ClassLoaderProvider}. This implementation does not store
239      * the {@code ClassLoaderProvider} in a separate member field. Rather, it is
240      * passed to the current {@link BeanContext}. To avoid inconsistencies when
241      * loading classes there must be only a single {@code ClassLoaderProvider}
242      * instance. If this method is called before a {@link BeanContext} was set,
243      * an {@code IllegalStateException} exception is thrown.
244      *
245      * @param classLoaderProvider the new class loader provider
246      * @throws IllegalStateException if no {@link BeanContext} has been set
247      */
248     public void setClassLoaderProvider(ClassLoaderProvider classLoaderProvider)
249     {
250         BeanContext bctx = getBeanContext();
251         if (bctx == null)
252         {
253             throw new IllegalStateException(
254                     "A BeanContext must be set before setting the CLP!");
255         }
256         bctx.setClassLoaderProvider(classLoaderProvider);
257     }
258 
259     /**
260      * Returns the <code>ValidationMessageHandler</code>.
261      *
262      * @return the <code>ValidationMessageHandler</code>
263      */
264     public ValidationMessageHandler getValidationMessageHandler()
265     {
266         return validationMessageHandler;
267     }
268 
269     /**
270      * Sets the <code>ValidationMessageHandler</code>. This object can be
271      * queried by validators to obtain a specific validation message.
272      *
273      * @param validationMessageHandler the new
274      *        <code>ValidationMessageHandler</code> (must not be <b>null</b>)
275      * @throws IllegalArgumentException if the passed validation message handler
276      *         is <b>null</b>
277      */
278     public void setValidationMessageHandler(
279             ValidationMessageHandler validationMessageHandler)
280     {
281         if (validationMessageHandler == null)
282         {
283             throw new IllegalArgumentException(
284                     "ValidationMessageHandler must not be null!");
285         }
286         this.validationMessageHandler = validationMessageHandler;
287     }
288 
289     /**
290      * Returns a reference to the object for displaying messages. This object
291      * can be used to create message boxes.
292      *
293      * @return the object for displaying messages
294      */
295     public MessageOutput getMessageOutput()
296     {
297         return msgOut;
298     }
299 
300     /**
301      * Sets the message output object to be used by this application.
302      *
303      * @param msg the new <code>MessageOutput</code> object
304      */
305     public void setMessageOutput(MessageOutput msg)
306     {
307         msgOut = msg;
308     }
309 
310     /**
311      * Convenience method for looking up a resource specified as group and
312      * resource ID.
313      *
314      * @param groupID the resource group ID
315      * @param resID the resource ID
316      * @return the found resource
317      * @throws java.util.MissingResourceException if the resource cannot be found
318      */
319     public Object getResource(Object groupID, Object resID)
320     {
321         return getResourceManager().getResource(getLocale(), groupID, resID);
322     }
323 
324     /**
325      * Convenience method for looking up a resource that is specified as a
326      * <code>Message</code> object.
327      *
328      * @param msg the resource definition (must not be <b>null</b>)
329      * @return the found resource
330      * @throws java.util.MissingResourceException if the resource cannot be found
331      * @throws IllegalArgumentException if then message is undefined
332      */
333     public Object getResource(Message msg)
334     {
335         return getResourceText(msg);
336     }
337 
338     /**
339      * Convenience method for looking up a resource. The passed in object is
340      * checked to be an instance of
341      * <code>{@link net.sf.jguiraffe.resources.Message Message}</code>. If
342      * this is the case, the resource group and the resource ID are extracted
343      * from this object. Otherwise the passed in object is interpreted as
344      * resource ID and the default resource group will be used.
345      *
346      * @param resID the resource ID
347      * @return the found resource
348      * @throws java.util.MissingResourceException if the resource cannot be found
349      */
350     public Object getResource(Object resID)
351     {
352         return (resID instanceof Message) ? getResource((Message) resID)
353                 : getResource(null, resID);
354     }
355 
356     /**
357      * Convenience method for looking up the text of a resource specified as
358      * group and resource ID.
359      *
360      * @param groupID the resource group ID
361      * @param resID the resource ID
362      * @return the found resource text
363      * @throws java.util.MissingResourceException if the resource cannot be found
364      */
365     public String getResourceText(Object groupID, Object resID)
366     {
367         return getResourceManager().getText(getLocale(), groupID, resID);
368     }
369 
370     /**
371      * Convenience method for looking up the text of a resource specified as a
372      * <code>Message</code> object.
373      *
374      * @param msg defines the resource (must not be <b>null</b>)
375      * @return the found resource
376      * @throws java.util.MissingResourceException if the resource cannot be found
377      * @throws IllegalArgumentException if the message is undefined
378      */
379     public String getResourceText(Message msg)
380     {
381         if (msg == null)
382         {
383             throw new IllegalArgumentException(
384                     "Resource definition must not be null!");
385         }
386         return msg.resolve(getResourceManager(), getLocale());
387     }
388 
389     /**
390      * Convenience method for looking up the text of a specified resource. This
391      * method works analogous to <code>getResourceText(Object)</code>,
392      * especially the passed in object can be an instance of
393      * {@link net.sf.jguiraffe.resources.Message Message}.
394      *
395      * @param resID defines the requested resource
396      * @return the found resource
397      * @throws java.util.MissingResourceException if the resource cannot be found
398      */
399     public String getResourceText(Object resID)
400     {
401         return (resID instanceof Message) ? getResourceText((Message) resID)
402                 : getResourceText(null, resID);
403     }
404 
405     /**
406      * A convenience method for displaying a message box. This method invokes
407      * the application's associated <code>MessageOutput</code> object. Before
408      * that the passed in resource IDs (which can be either resource IDs or
409      * instances of the {@link net.sf.jguiraffe.resources.Message Message}
410      * class) will be resolved.
411      *
412      * @param resMsg the resource defining the message to be displayed
413      * @param resTitle the resource defining the message box's title (can be
414      *        <b>null</b>)
415      * @param msgType the message type (one of the <code>MESSAGE_XXX</code>
416      *        constants of <code>MessageOutput</code>)
417      * @param btnType the button type (one of the <code>BTN_XXX</code> constants
418      *        of <code>MessageOutput</code>)
419      * @return the message box's return value (one of the <code>RET_XXX</code>
420      *         constants of <code>MessageOutput</code>)
421      * @see net.sf.jguiraffe.gui.builder.utils.MessageOutput
422      */
423     public int messageBox(Object resMsg, Object resTitle, int msgType,
424             int btnType)
425     {
426         String title = (resTitle != null) ? getResourceText(resTitle) : null;
427         return getMessageOutput().show(getMainWindow(), getResource(resMsg),
428                 title, msgType, btnType);
429     }
430 
431     /**
432      * Returns the <code>GUISynchronizer</code>. This implementation obtains
433      * the synchronizer from the bean context.
434      *
435      * @return the <code>GUISynchronizer</code>
436      */
437     public GUISynchronizer getGUISynchronizer()
438     {
439         return (GUISynchronizer) getBeanContext().getBean(
440                 Application.BEAN_GUI_SYNCHRONIZER);
441     }
442 
443     /**
444      * Returns the application's main window.
445      *
446      * @return the main window of this application
447      */
448     public Window getMainWindow()
449     {
450         return mainWindow;
451     }
452 
453     /**
454      * Allows to set the application's main window.
455      *
456      * @param mainWindow the new main window
457      */
458     public void setMainWindow(Window mainWindow)
459     {
460         this.mainWindow = mainWindow;
461     }
462 
463     /**
464      * Returns the application's <code>ActionStore</code>.
465      *
466      * @return the application's action store
467      */
468     public ActionStore getActionStore()
469     {
470         return actionStore;
471     }
472 
473     /**
474      * Sets the application's <code>ActionStore</code>. This object contains
475      * the definitions for all top level actions known to the application.
476      *
477      * @param actionStore the new action store
478      */
479     public void setActionStore(ActionStore actionStore)
480     {
481         this.actionStore = actionStore;
482     }
483 
484     /**
485      * Returns a new <code>{@link Builder}</code> instance. This
486      * implementation obtains the builder instance from the global bean context.
487      *
488      * @return the new <code>Builder</code> instance
489      */
490     public Builder newBuilder()
491     {
492         return (Builder) getBeanContext().getBean(Application.BEAN_BUILDER);
493     }
494 
495     /**
496      * Returns an initialized <code>ApplicationBuilderData</code> object that
497      * can be used for calling the GUI builder. Most of the properties of the
498      * returned object are already set to default values, so only specific
499      * settings must be performed. The following properties have already been
500      * initialized with information available directly in this object or from
501      * the configuration:
502      * <ul>
503      * <li>the parent {@link BeanContext}</li>
504      * <li>the default resource group</li>
505      * <li>the menu icon flag</li>
506      * <li>the tool bar text flag</li>
507      * <li>the transformer context</li>
508      * <li>the parent window</li>
509      * <li>the action store: Here the following strategy is used: if the
510      * <code>ActionStore</code> managed by this object is empty, it is
511      * directly used. Otherwise a new <code>ActionStore</code> instance is
512      * created with the managed <code>ActionStore</code> as parent. This has
513      * the effect that the first builder script will populate the global action
514      * store. Further scripts use their own store.</li>
515      * <li>the <code>MessageOutput</code> object</li>
516      * <li>the <code>CommandQueue</code></li>
517      * <li>the {@code BindingStrategy}</li>
518      * <li>a {@link BeanCreationListener} is set that can inject a reference to
519      * the central {@code Application} object into beans implementing the
520      * {@link ApplicationClient} interface</li>
521      * </ul>
522      *
523      * @return an initialized GUI builder parameter object
524      */
525     public ApplicationBuilderData initBuilderData()
526     {
527         ApplicationBuilderData data = new ApplicationBuilderData();
528         data.setDefaultResourceGroup(getResourceManager()
529                 .getDefaultResourceGroup());
530         data.setMenuIcon(getConfiguration().getBoolean(
531                 Application.PROP_BUILDER_MENU_ICON, false));
532         data.setToolbarText(getConfiguration().getBoolean(
533                 Application.PROP_BUILDER_TOOLBAR_TEXT, false));
534         data.setTransformerContext(this);
535         data.setParentWindow(getMainWindow());
536         data.setParentContext(getBeanContext());
537         data.setActionStore(actionStoreForBuilderData());
538         data.setMessageOutput(getMessageOutput());
539         Application app = Application.getInstance(getBeanContext());
540         data.setCommandQueue(app.getCommandQueue());
541         data.setBindingStrategy(fetchBindingStrategy());
542         data.addBeanCreationListener(new ApplicationInjectBeanCreationListener(
543                 app));
544 
545         return data;
546     }
547 
548     /**
549      * Returns the value of the specified typed property or <b>null</b> if it
550      * cannot be found.
551      *
552      * @param <T> the type of the property
553      * @param propCls the property class
554      * @return the value of this typed property
555      */
556     public <T> T getTypedProperty(Class<T> propCls)
557     {
558         @SuppressWarnings("unchecked")
559         T result = (propCls == null) ? null : (T) typedProperties.get(propCls);
560         return result;
561     }
562 
563     /**
564      * Sets the value of the given typed property. Note: This method is
565      * thread-safe. It ensures proper synchronization so that the property is
566      * visible to other threads, too.
567      *
568      * @param <T> the type of the property
569      * @param propCls the property class (must not be <b>null</b>)
570      * @param value the new value (<b>null</b> for clearing this property)
571      * @throws IllegalArgumentException if the property class is <b>null</b>
572      */
573     public <T> void setTypedProperty(Class<T> propCls, T value)
574     {
575         if (propCls == null)
576         {
577             throw new IllegalArgumentException(
578                     "Property class must not be null!");
579         }
580 
581         if (value == null)
582         {
583             typedProperties.remove(propCls);
584         }
585         else
586         {
587             typedProperties.put(propCls, value);
588         }
589     }
590 
591     /**
592      * Obtains the {@code BindingStrategy} for a builder operation. This method
593      * is invoked by {@link #initBuilderData()} for populating the {@code
594      * bindingStrategy} property of the {@code ApplicationBuilderData} object.
595      * The default algorithm looks up the {@code BindingStrategy} from the
596      * global {@code BeanContext}.
597      *
598      * @return the {@code BindingStrategy}
599      */
600     protected BindingStrategy fetchBindingStrategy()
601     {
602         return (BindingStrategy) getBeanContext().getBean(
603                 Application.BEAN_BINDING_STRATEGY);
604     }
605 
606     /**
607      * Makes the specified bean store to the given bean context's default store.
608      * The current default store will become the new store's parent.
609      *
610      * @param context the bean context
611      * @param store the new default bean store
612      * @throws IllegalArgumentException if either the bean context or the bean
613      * store is <b>null</b>
614      */
615     static void installBeanStore(BeanContext context, MutableBeanStore store)
616     {
617         if (context == null)
618         {
619             throw new IllegalArgumentException("BeanContext must not be null!");
620         }
621         if (store == null)
622         {
623             throw new IllegalArgumentException("BeanStore must not be null!");
624         }
625 
626         store.setParent(context.getDefaultBeanStore());
627         context.setDefaultBeanStore(store);
628     }
629 
630     /**
631      * Determines the action store to be used for the builder data. If
632      * necessary, a new store will be created.
633      *
634      * @return the store to be used
635      */
636     private ActionStore actionStoreForBuilderData()
637     {
638         if (getActionStore() == null
639                 || !getActionStore().getActionNames().isEmpty())
640         {
641             return new ActionStore(getActionStore());
642         }
643         else
644         {
645             return getActionStore();
646         }
647     }
648 
649     /**
650      * A specialized {@code BeanCreationListener} implementation that is able to
651      * inject the central {@code Application} instance into bean implementing
652      * the {@link ApplicationClient} interface. An instance of this class is
653      * added to the {@code BuilderData} object created by {@code
654      * initBuilderData()}. It ensures that the {@code Application} is
655      * automatically injected into all beans that are interested in this
656      * reference.
657      */
658     private static class ApplicationInjectBeanCreationListener implements
659             BeanCreationListener
660     {
661         /** Stores a reference to the central {@code Application} object. */
662         private final Application application;
663 
664         /**
665          * Creates a new instance of {@code
666          * ApplicationInjectBeanCreationListener} and initializes it with the
667          * central {@code Application} reference.
668          *
669          * @param app the {@code Application}
670          */
671         public ApplicationInjectBeanCreationListener(Application app)
672         {
673             application = app;
674         }
675 
676         /**
677          * A bean was created. This implementation checks whether this bean
678          * implements the {@code ApplicationClient} interface. If so, the
679          * {@code Application} is injected.
680          *
681          * @param event the {@code BeanCreationEvent}
682          */
683         public void beanCreated(BeanCreationEvent event)
684         {
685             Application.setApplicationReference(event.getBean(), application);
686         }
687     }
688 }