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 }