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 javax.swing.event.EventListenerList;
19 import java.awt.Rectangle;
20 import java.net.URL;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Locale;
27 import java.util.concurrent.atomic.AtomicReference;
28
29 import net.sf.jguiraffe.di.BeanContext;
30 import net.sf.jguiraffe.di.ClassLoaderProvider;
31 import net.sf.jguiraffe.di.MutableBeanStore;
32 import net.sf.jguiraffe.di.impl.DefaultBeanContext;
33 import net.sf.jguiraffe.di.impl.DefaultBeanStore;
34 import net.sf.jguiraffe.di.impl.providers.ConstantBeanProvider;
35 import net.sf.jguiraffe.gui.builder.BeanBuilder;
36 import net.sf.jguiraffe.gui.builder.BeanBuilderFactory;
37 import net.sf.jguiraffe.gui.builder.BeanBuilderResult;
38 import net.sf.jguiraffe.gui.builder.Builder;
39 import net.sf.jguiraffe.gui.builder.BuilderException;
40 import net.sf.jguiraffe.gui.builder.impl.JellyBeanBuilderFactory;
41 import net.sf.jguiraffe.gui.builder.utils.GUISynchronizer;
42 import net.sf.jguiraffe.gui.builder.utils.MessageOutput;
43 import net.sf.jguiraffe.gui.builder.window.Window;
44 import net.sf.jguiraffe.gui.cmd.Command;
45 import net.sf.jguiraffe.gui.cmd.CommandQueue;
46 import net.sf.jguiraffe.locators.ClassPathLocator;
47 import net.sf.jguiraffe.locators.Locator;
48 import net.sf.jguiraffe.locators.LocatorConverter;
49 import net.sf.jguiraffe.locators.LocatorUtils;
50 import org.apache.commons.configuration.CombinedConfiguration;
51 import org.apache.commons.configuration.Configuration;
52 import org.apache.commons.configuration.ConfigurationException;
53 import org.apache.commons.configuration.ConversionException;
54 import org.apache.commons.configuration.DefaultConfigurationBuilder;
55 import org.apache.commons.configuration.FileConfiguration;
56 import org.apache.commons.configuration.HierarchicalConfiguration;
57 import org.apache.commons.configuration.PropertyConverter;
58 import org.apache.commons.configuration.beanutils.BeanDeclaration;
59 import org.apache.commons.configuration.beanutils.BeanHelper;
60 import org.apache.commons.configuration.beanutils.XMLBeanDeclaration;
61 import org.apache.commons.logging.Log;
62 import org.apache.commons.logging.LogFactory;
63
64 /**
65 * <p>
66 * The main startup class of the GUI application framework.
67 * </p>
68 * <p>
69 * With this class a Java GUI application can be started. This works as follows:
70 * </p>
71 * <p>
72 * <ol>
73 * <li>The class uses the values of system properties to find out the name of
74 * the application's configuration file.</li>
75 * <li>This configuration file is loaded using commons-configuration.</li>
76 * <li>From properties defined in the application's configuration the
77 * {@code ApplicationContext} is created and initialized. This includes
78 * setting up a resource manager.</li>
79 * <li>The name of the application's main GUI builder script is also determined
80 * by configuration properties. This script is executed, and the resulting main
81 * window is made visible.</li>
82 * </ol>
83 * </p>
84 * <p>
85 * Per default the application's configuration file is expected to be located in
86 * the class path and has the name <em>config.xml</em>. This can be changed
87 * using system properties: The property
88 * {@code net.sf.jguiraffe.configName} allows to change the name of the
89 * configuration file. If defined, a file with this name will be searched in the
90 * class path. If the property {@code net.sf.jguiraffe.configURL} is
91 * provided, the class tries to load this file directly from this URL.
92 * </p>
93 * <p>
94 * A bunch of configuration properties is evaluated by this class to perform the
95 * correct setup. All of these must be placed in a section called
96 * {@code framework}. The following table lists the available properties:
97 * </p>
98 * <p>
99 * <table border="1">
100 * <tr>
101 * <th>Property</th>
102 * <th>Description</th>
103 * <th>Optional</th>
104 * </tr>
105 * <tr>
106 * <td valign="top">appctx</td>
107 * <td>In this section some properties of the application context are defined:
108 * <dl>
109 * <dt>{@code locale}</dt>
110 * <dd>Here the locale to be set at startup can be specified. If the property is
111 * missing, the system's default locale will be used.</dd>
112 * <dt>{@code defaultResourceGroup}</dt>
113 * <dd>Allows to define a default resource group that is used by the resource
114 * manager when no specific resource group is specified.</dd>
115 * </dl>
116 * </td>
117 * <td valign="top">Yes</td>
118 * </tr>
119 * <tr>
120 * <td valign="top">builder</td>
121 * <td>This section contains some setting related to the builders used for
122 * processing bean definitions and GUI scripts. All properties in this section
123 * are optional - meaningful default values are applied if a value is not set.
124 * The following sub elements are supported:
125 * <dl>
126 * <dt>{@code beanBuilderFactory}</dt>
127 * <dd>Specifies the full qualified name of the {@link BeanBuilderFactory}
128 * implementation that is used to obtain bean builder instances. Here the
129 * implementation class and additional initialization properties can be
130 * specified.</dd>
131 * <dt>{@code beandefinitions}</dt>
132 * <dd>In this subsection an arbitrary number of {@code beandefinition}
133 * elements can be specified. Each {@code beandefinition} element points to
134 * a script with bean definitions. These scripts will be processed by the
135 * default bean builder.</dd>
136 * <dt>{@code menuIcon}</dt>
137 * <dd>An optional boolean flag that determines whether menu items should be
138 * rendered with an icon if one is defined. Note that this may not work on all
139 * platforms. The default value for this flag is <b>false</b>.</dd>
140 * <dt>{@code toolbarText}</dt>
141 * <dd>An optional boolean flag that determines whether toolbar buttons should
142 * display their text. Note that this may not be supported by all platforms. The
143 * default value of this flag is <b>false</b>.</dd>
144 * <dt>{@code mainScript}</dt>
145 * <dd>With this property the name of the main builder script can be specified.
146 * If defined, the script will be executed using the application's builder. If
147 * this results in a window, this window will be displayed.</dd></td>
148 * <td valign="top">Yes</td>
149 * </tr>
150 * <tr>
151 * <td valign="top">frame</td>
152 * <td>In this section some properties of the application's main window can be
153 * defined, especially its location and size. The idea is that this information
154 * will be stored in a user configuration so that the last settings can be set
155 * again on next application start. The following properties can be defined in
156 * this section:
157 * <dl>
158 * <dt>{@code xpos}</dt>
159 * <dd>Defines the x position of the main window.</dd>
160 * <dt>{@code ypos}</dt>
161 * <dd>Defines the y position of the main window.</dd>
162 * <dt>{@code width}</dt>
163 * <dd>Defines the width position of the main window.</dd>
164 * <dt>{@code height}</dt>
165 * <dd>Defines the height position of the main window.</dd></td>
166 * <td valign="top">Yes</td>
167 * </tr>
168 * <tr>
169 * <td valign="top">storeuserconfig</td>
170 * <td>A boolean property that determines whether the user specific
171 * configuration should be stored when the application terminates. Defaults to
172 * <b>false</b>.</td>
173 * <td valign="top">Yes</td>
174 * </tr>
175 * <tr>
176 * <td valign="top">userconfigname</td>
177 * <td>Defines the name of the user configuration in the configuration
178 * definition file. (The configuration definition file can include an arbitrary
179 * number of configuration sources. To determine, which of these is the user
180 * configuration, its name must be specified. If no name is specified, the
181 * default <em>userConfig</em> will be used.</td>
182 * <td valign="top">Yes</td>
183 * </tr>
184 * </table>
185 * </p>
186 * <p>
187 * A major part of the configuration of the application is defined in terms of
188 * bean definitions. Here many helper classes used by the application are
189 * defined (e.g. the resource manager, the message output object, the GUI
190 * builder, and many more). At start up, the application creates a
191 * {@link BeanContext} that provides access to these beans and creates the
192 * required instances. There is a default bean definition file (
193 * <em>defaultbeans.jelly</em>) with default bean definitions for all available
194 * helper classes. It is loaded first. Concrete applications can override some
195 * or all of these beans. This is a powerful means of customizing the
196 * application.
197 * </p>
198 * <p>
199 * To override bean definitions, use the
200 * {@code framework.builder.beandefinitions} section in the application's
201 * main configuration file (see above). In this section the names of an
202 * arbitrary number of bean definition files can be specified (the files will be
203 * loaded from the class path). Using the predefined names for the default beans
204 * in these scripts causes the beans to be replaced by the custom ones. Have a
205 * look at the <em>defaultbeans.jelly</em> script for more information; in this
206 * file all available default beans are listed with a documentation for each.
207 * </p>
208 *
209 * @author Oliver Heger
210 * @version $Id: Application.java 211 2012-07-10 19:49:13Z oheger $
211 */
212 public class Application
213 {
214 /** Constant for the system property with the URL to the configuration file. */
215 public static final String PROP_CONFIG_URL = "net.sf.jguiraffe.configURL";
216
217 /**
218 * Constant for the system property with the resource name of the
219 * configuration file.
220 */
221 public static final String PROP_CONFIG_NAME = "net.sf.jguiraffe.configName";
222
223 /** Constant for the default name of the configuration file. */
224 public static final String DEF_CONFIG_NAME = "config.xml";
225
226 /** Constant for the prefix for bean definitions used by the framework. */
227 public static final String BEAN_PREFIX = "jguiraffe.";
228
229 /** Constant for the name of the bean with the global configuration. */
230 public static final String BEAN_CONFIGURATION = BEAN_PREFIX
231 + "configuration";
232
233 /** Constant for the name of the bean with the current application instance. */
234 public static final String BEAN_APPLICATION = BEAN_PREFIX + "application";
235
236 /** Constant for the name of the bean with the application context. */
237 public static final String BEAN_APPLICATION_CONTEXT = BEAN_PREFIX
238 + "applicationContext";
239
240 /** Constant for the name of the bean with the global bean context. */
241 public static final String BEAN_GLOBAL_CONTEXT = BEAN_PREFIX
242 + "globalContext";
243
244 /** Constant for the name of the builder bean. */
245 public static final String BEAN_BUILDER = BEAN_PREFIX + "builder";
246
247 /** Constant for the name of the command queue bean. */
248 public static final String BEAN_COMMAND_QUEUE = BEAN_PREFIX
249 + "commandQueue";
250
251 /** Constant for the name of the GUI synchronizer bean. */
252 public static final String BEAN_GUI_SYNCHRONIZER = BEAN_PREFIX
253 + "guiSynchronizer";
254
255 /** Constant for the name of the binding strategy bean. */
256 public static final String BEAN_BINDING_STRATEGY = BEAN_PREFIX
257 + "bindingStrategy";
258
259 /** Constant for the name of the bean with the locale. */
260 public static final String BEAN_LOCALE = BEAN_PREFIX + "locale";
261
262 /** Constant for the name of the bean with the default resource group. */
263 public static final String BEAN_DEF_RES_GROUP = BEAN_PREFIX
264 + "defaultResourceGroup";
265
266 /** Constant for the name of the bean for the class loader provider. */
267 public static final String BEAN_CLASS_LOADER_PROVIDER = BEAN_PREFIX
268 + "classLoaderProvider";
269
270 /** Constant for the configuration section for the framework. */
271 public static final String CONFIG_SECTION = "framework.";
272
273 /** Constant for the application context property in the config file. */
274 public static final String PROP_APPCTX = CONFIG_SECTION + "appctx";
275
276 /** Constant for the locale property in the config file. */
277 public static final String PROP_LOCALE = PROP_APPCTX + ".locale";
278
279 /** Constant for the default resource group property in the config file. */
280 public static final String PROP_DEFRESGROUP = PROP_APPCTX
281 + ".defaultResourceGroup";
282
283 /** Constant for the section with the builder information. */
284 public static final String BUILDER_SECTION = CONFIG_SECTION + "builder.";
285
286 /** Constant for the builder factory property. */
287 public static final String PROP_BUILDER_FACTORY = BUILDER_SECTION
288 + "factory";
289
290 /** Constant for the bean builder factory property. */
291 public static final String PROP_BEAN_BUILDER_FACTORY = BUILDER_SECTION
292 + "beanBuilderFactory";
293
294 /** Constant for the bean definitions property. */
295 public static final String PROP_BEAN_DEFS = BUILDER_SECTION
296 + "beandefinitions.beandefinition";
297
298 /** Constant for the builder menu icon property. */
299 public static final String PROP_BUILDER_MENU_ICON = BUILDER_SECTION
300 + "menuIcon";
301
302 /** Constant for the builder toolbar text property. */
303 public static final String PROP_BUILDER_TOOLBAR_TEXT = BUILDER_SECTION
304 + "toolbarText";
305
306 /** Constant for the builder main script property. */
307 public static final String PROP_BUILDER_MAIN_SCRIPT = BUILDER_SECTION
308 + "mainScript";
309
310 /** Constant for the frame section in the configuration file. */
311 public static final String FRAME_SECTION = CONFIG_SECTION + "frame.";
312
313 /** Constant for the xpos property in the config file. */
314 public static final String PROP_XPOS = FRAME_SECTION + "xpos";
315
316 /** Constant for the ypos property in the config file. */
317 public static final String PROP_YPOS = FRAME_SECTION + "ypos";
318
319 /** Constant for the width property in the config file. */
320 public static final String PROP_WIDTH = FRAME_SECTION + "width";
321
322 /** Constant for the height property in the config file. */
323 public static final String PROP_HEIGHT = FRAME_SECTION + "height";
324
325 /** Constant for the storeuserconfig property in the config file. */
326 public static final String PROP_USRCONF = CONFIG_SECTION
327 + "storeuserconfig";
328
329 /** Constant for the userconfigname property in the config file. */
330 public static final String PROP_USRCONFNAME = CONFIG_SECTION
331 + "userconfigname";
332
333 /** Constant for the name of the user configuration. */
334 public static final String USRCONF_NAME = "userConfig";
335
336 /**
337 * Constant for the name of the class loader which loaded the application
338 * class. This class loader is set as the default class loader at the
339 * {@code ClassLoaderProvider} created at startup.
340 *
341 * @since 1.2
342 */
343 public static final String CLASS_LOADER = BEAN_PREFIX
344 + "Application.classLoader";
345
346 /** Constant for the default bean builder factory class. */
347 private static final Class<?> DEF_BEAN_BUILDER_FACTORY_CLS = JellyBeanBuilderFactory.class;
348
349 /** Constant for the script with the default bean definitions. */
350 private static final Locator DEFAULT_BEANS = ClassPathLocator.getInstance(
351 "defaultbeans.jelly");
352
353 /**
354 * Constant for the locator to the script with platform-specific bean
355 * definitions.
356 */
357 private static final Locator PLATFORM_BEANS = ClassPathLocator
358 .getInstance("platformbeans.jelly");
359
360 /** The logger to use. */
361 protected final Log log = LogFactory.getLog(Application.class);
362
363 /** Stores the URL for the configuration file. */
364 private String configURL;
365
366 /** Stores the resource name for the configuration file. */
367 private String configResourceName;
368
369 /** Stores a reference to the application context. */
370 private ApplicationContext applicationContext;
371
372 /** Stores the bean builder factory. */
373 private BeanBuilderFactory beanBuilderFactory;
374
375 /** The root bean store. */
376 private MutableBeanStore rootBeanStore;
377
378 /** Stores a reference to the command queue. */
379 private CommandQueue cmdQueue;
380
381 /** Stores the registered shutdown listeners. */
382 private final EventListenerList shutdownListeners;
383
384 /** A list with the bean builder results created during initialization.*/
385 private final Collection<BeanBuilderResult> beanBuilderResults;
386
387 /**
388 * The default exit handler. This instance is returned if no specific exit
389 * handler has been set. It calls {@code System.exit()} with the current exit
390 * code.
391 */
392 private final Runnable defaultExitHandler = new Runnable()
393 {
394 public void run()
395 {
396 System.exit(getExitCode());
397 }
398 };
399
400 /** The current exit handler of this application. */
401 private final AtomicReference<Runnable> exitHandler;
402
403 /** Stores the bean context of the main window. */
404 private BeanContext mainWindowBeanContext;
405
406 /** The exit code of this application. */
407 private int exitCode;
408
409 /**
410 * Creates a new instance of {@code Application}.
411 */
412 public Application()
413 {
414 shutdownListeners = new EventListenerList();
415 beanBuilderResults = new ArrayList<BeanBuilderResult>();
416 exitHandler = new AtomicReference<Runnable>();
417 }
418
419 /**
420 * Returns the URL from which the configuration file is to be loaded.
421 *
422 * @return the URL to the configuration file
423 */
424 public String getConfigURL()
425 {
426 return configURL;
427 }
428
429 /**
430 * Sets the URL to the configuration file. The configuration file can either
431 * be loaded directly from a URL or as a resource from the class path. If
432 * this property is defined, it is loaded directly from the URL specified
433 * here.
434 *
435 * @param configURL the URL to the configuration file
436 */
437 public void setConfigURL(String configURL)
438 {
439 this.configURL = configURL;
440 }
441
442 /**
443 * Returns the resource name of the configuration file.
444 *
445 * @return the resource name of the configuration file
446 */
447 public String getConfigResourceName()
448 {
449 return configResourceName;
450 }
451
452 /**
453 * Sets the resource name under which the configuration file can be loaded.
454 * If no configuration URL is provided, this property is used to look up the
455 * configuration file from the class path.
456 *
457 * @param configResourceName the resource name of the configuration file
458 */
459 public void setConfigResourceName(String configResourceName)
460 {
461 this.configResourceName = configResourceName;
462 }
463
464 /**
465 * Returns a reference to the actual application context.
466 *
467 * @return the {@code ApplicationContext}
468 */
469 public ApplicationContext getApplicationContext()
470 {
471 return applicationContext;
472 }
473
474 /**
475 * Sets the application context.
476 *
477 * @param context the new context
478 */
479 public void setApplicationContext(ApplicationContext context)
480 {
481 applicationContext = context;
482 }
483
484 /**
485 * Returns the {@code BeanBuilderFactory} for obtaining a bean
486 * builder. This method can be used when a bean definition file is to be
487 * processed.
488 *
489 * @return the {@code BeanBuilderFactory}
490 */
491 public BeanBuilderFactory getBeanBuilderFactory()
492 {
493 return beanBuilderFactory;
494 }
495
496 /**
497 * Allows to set the {@code BeanBuilderFactory}. Normally it is not
498 * necessary to set this property. When the application is initialized it
499 * creates a default factory.
500 *
501 * @param beanBuilderFactory the new {@code BeanBuilderFactory}
502 */
503 public void setBeanBuilderFactory(BeanBuilderFactory beanBuilderFactory)
504 {
505 this.beanBuilderFactory = beanBuilderFactory;
506 }
507
508 /**
509 * Registers the specified object as a shutdown listeners.
510 *
511 * @param l the listener to register
512 */
513 public void addShutdownListener(ApplicationShutdownListener l)
514 {
515 shutdownListeners.add(ApplicationShutdownListener.class, l);
516 }
517
518 /**
519 * Removes the specified shutdown listener.
520 *
521 * @param l the listener to remove
522 */
523 public void removeShutdownListener(ApplicationShutdownListener l)
524 {
525 shutdownListeners.remove(ApplicationShutdownListener.class, l);
526 }
527
528 /**
529 * Helper method for locating a resource. The resource can either be
530 * specified by a full URL, in which case it is directly loaded, or by a
531 * resource name. In the latter case the class loader is used to find the
532 * resource.
533 *
534 * @param url a URL to the resource; this can be a full qualified URL or the
535 * name of a file (either relative or absolute)
536 * @param name the resource name
537 * @return the URL to the resource or <b>null </b> if it cannot be found
538 * @deprecated This method does not make sense in the public interface of
539 * this class. It will be removed in later versions. Use corresponding
540 * functionality from the {@code LocatorUtils} class instead.
541 */
542 @Deprecated
543 public static URL resolveResourceURL(String url, String name)
544 {
545 return LocatorUtils.locate(url, name);
546 }
547
548 /**
549 * Tries to the set a reference to the global {@code Application}
550 * object in the target object. If the target object implements the
551 * {@code ApplicationClient} interface, the reference can be set.
552 *
553 * @param target the target object
554 * @param ref the application reference to set
555 */
556 public static void setApplicationReference(Object target, Application ref)
557 {
558 if (target instanceof ApplicationClient)
559 {
560 ((ApplicationClient) target).setApplication(ref);
561 }
562 }
563
564 /**
565 * <p>
566 * Returns the configuration object with user specific settings. This
567 * configuration can be used to store personalization settings that override
568 * the default configuration. For this method to work the system's main
569 * configuration definition file must include an entry for the user
570 * configuration that is identified by its name. Per default the name is
571 * "userConfig", but can be altered with the
572 * {@code userconfigname} property. The following example fragment
573 * from the application's configuration definition file demonstrates how the
574 * user configuration should be declared:
575 * </p>
576 * <p>
577 *
578 * <pre>
579 * <configuration>
580 * ...
581 * <xml fileName="${user.home}/myAppConfig.xml" config-name="userConfig"
582 * config-optional="true" config-forceCreate="true"/>
583 * <!-- Further configuration sources to load -->
584 * ...
585 * </pre>
586 *
587 * </p>
588 * <p>
589 * In this example the attributes starting with {@code config-} are
590 * of special importance. With {@code config-name} the
591 * configuration's name is specified, which is necessary for the framework
592 * to retrieve the correct user configuration. {@code config-optional}
593 * declares this configuration source as optional. This means that it won't
594 * cause an error when this source cannot be loaded (which will probably be
595 * the case when a user starts this application for the first time). The
596 * {@code config-forceCreate} attribute finally tells the
597 * configuration framework to create an empty configuration when loading of
598 * the configuration file fails. This will cause the configuration to be
599 * automatically created for the new user. If the user customizes the
600 * application, these settings can be stored in this configuration. When the
601 * application terminates it checks the value of the
602 * {@code storeuserconfig} property. If this is set to <b>true</b>,
603 * the user configuration will be stored. So the next time the application
604 * starts it will be found there and override the values in all other
605 * configuration sources.
606 * </p>
607 *
608 * @return the configuration with user specific settings
609 * @throws ApplicationRuntimeException if the user configuration cannot be
610 * obtained
611 */
612 public Configuration getUserConfiguration()
613 throws ApplicationRuntimeException
614 {
615 Configuration userConfig = null;
616
617 if (getApplicationContext().getConfiguration() instanceof CombinedConfiguration)
618 {
619 CombinedConfiguration cconf = (CombinedConfiguration) getApplicationContext()
620 .getConfiguration();
621 String userConfigName = cconf.getString(PROP_USRCONFNAME,
622 USRCONF_NAME);
623 userConfig = cconf.getConfiguration(userConfigName);
624 }
625
626 if (userConfig == null)
627 {
628 throw new ApplicationRuntimeException(
629 "Cannot obtain user configuration!");
630 }
631 return userConfig;
632 }
633
634 /**
635 * Stores the configuration with user specific settings. This method obtains
636 * the user configuration by calling the
637 * {@link #getUserConfiguration()}
638 * method. It expects that the user configuration implements the
639 * {@code FileConfiguration} interface. If a different configuration
640 * type is used as user configuration, this method should also be adapted.
641 *
642 * @throws ApplicationException if an error occurs
643 */
644 public void saveUserConfiguration() throws ApplicationException
645 {
646 Configuration conf = getUserConfiguration();
647 if (conf instanceof FileConfiguration)
648 {
649 FileConfiguration fconf = (FileConfiguration) conf;
650 try
651 {
652 fconf.save();
653 if (log.isInfoEnabled())
654 {
655 log.info("Saved user configuration to " + fconf.getURL());
656 }
657 }
658 catch (ConfigurationException cex)
659 {
660 throw new ApplicationException(
661 "Could not save user configuration!", cex);
662 }
663 }
664
665 else
666 {
667 throw new ApplicationException(
668 "User configuration is no file-based configuration!");
669 }
670 }
671
672 /**
673 * Creates and initializes the application context. Loads the configuration,
674 * too.
675 *
676 * @return the application context
677 * @throws ApplicationRuntimeException if an error occurs during
678 * initialization
679 */
680 protected ApplicationContext createApplicationContext()
681 {
682 HierarchicalConfiguration config = createConfiguration();
683 setBeanBuilderFactory(createBeanBuilderFactory(config));
684 BeanContext beanContext = initBeans(config);
685 return (ApplicationContext) beanContext
686 .getBean(BEAN_APPLICATION_CONTEXT);
687 }
688
689 /**
690 * Helper method for extracting the startup locale from the application's
691 * configuration data.
692 *
693 * @param config the configuration data
694 * @return the locale to use; if not defined in the configuration, the
695 * system's default locale will be returned
696 * @throws ApplicationRuntimeException if the locale is invalid
697 */
698 Locale parseLocale(Configuration config)
699 {
700 if (config.containsKey(PROP_LOCALE))
701 {
702 try
703 {
704 return PropertyConverter.toLocale(config
705 .getProperty(PROP_LOCALE));
706 }
707 catch (ConversionException cex)
708 {
709 throw new ApplicationRuntimeException(
710 "Error when parsing locale", cex);
711 }
712 }
713
714 return Locale.getDefault();
715 }
716
717 /**
718 * Returns the URL to the configuration file. This URL is determined by the
719 * properties {@code configURL} and {@code configResourceName}.
720 *
721 * @return the URL to the application's main configuration file
722 * @throws ApplicationRuntimeException if the configuration file cannot be
723 * located
724 */
725 protected URL fetchConfigURL()
726 {
727 URL url =
728 LocatorUtils.locate(getConfigURL(), getConfigResourceName(),
729 getClass().getClassLoader());
730 if (url == null)
731 {
732 throw new ApplicationRuntimeException(
733 "Cannot find configuration file!");
734 }
735 return url;
736 }
737
738 /**
739 * Creates the configuration for this application. This method calls
740 * {@code fetchConfigURL()} to determine the URL to the main
741 * configuration file. Then this file is loaded with commons-configuration.
742 *
743 * @return the configuration to use
744 * @throws ApplicationRuntimeException if the configuration file cannot be
745 * located
746 */
747 protected HierarchicalConfiguration createConfiguration()
748 {
749 return createConfiguration(fetchConfigURL());
750 }
751
752 /**
753 * Reads the application's configuration from the specified URL. The
754 * {@code DefaultConfigurationBuilder} of
755 * <em>Commons Configuration</em> is used for reading the configuration.
756 * Occurring exceptions are re-thrown as runtime exceptions.
757 *
758 * @param configURL the configuration URL
759 * @return the application's main configuration
760 * @throws ApplicationRuntimeException if the configuration cannot be loaded
761 */
762 protected HierarchicalConfiguration createConfiguration(URL configURL)
763 {
764 log.info("Loading configuration from " + configURL);
765 try
766 {
767 DefaultConfigurationBuilder factory = new DefaultConfigurationBuilder();
768 factory.setURL(configURL);
769 return factory.getConfiguration(true);
770 }
771 catch (ConfigurationException cex)
772 {
773 throw new ApplicationRuntimeException(
774 "Error when loading configuration!", cex);
775 }
776 }
777
778 /**
779 * Creates the root bean store. This bean store will contain the fundamental
780 * bean definitions required by the framework. Because it is at the top
781 * level the contained bean definitions can be used or even overridden by
782 * child stores. This implementation creates a default store and populates
783 * it with the configuration and the application instance itself.
784 *
785 * @param config the configuration object
786 * @return the initialized root bean store
787 */
788 protected MutableBeanStore createRootStore(Configuration config)
789 {
790 DefaultBeanStore store = new DefaultBeanStore();
791 addBean(store, BEAN_CONFIGURATION, config);
792 addBean(store, BEAN_APPLICATION, this);
793 addBean(store, BEAN_LOCALE, parseLocale(config));
794 addBean(store, BEAN_DEF_RES_GROUP, config.getString(PROP_DEFRESGROUP));
795 return store;
796 }
797
798 /**
799 * Creates the factory for the bean builder. This method is called during
800 * initialization phase. It tries to obtain the factory implementation from
801 * the main configuration file. If this fails, a default factory instance
802 * will be returned.
803 *
804 * @param config the main configuration
805 * @return the {@code BeanBuilderFactory} to be used
806 */
807 protected BeanBuilderFactory createBeanBuilderFactory(Configuration config)
808 {
809 BeanDeclaration decl = new XMLBeanDeclaration(
810 (HierarchicalConfiguration) config, PROP_BEAN_BUILDER_FACTORY,
811 true);
812 return (BeanBuilderFactory) BeanHelper.createBean(decl,
813 DEF_BEAN_BUILDER_FACTORY_CLS);
814 }
815
816 /**
817 * Initializes the application's bean definitions. This implementation will
818 * first process the framework-internal bean definition file, which defines
819 * the standard beans. After that {@code findBeanDefinitions()} is
820 * called for obtaining a list of additional definition files to be
821 * evaluated. Finally a bean context is created allowing access to all beans
822 * defined this way. This algorithm allows concrete applications to define
823 * their own beans in an easy way and also to override standard beans used
824 * by the framework.
825 *
826 * @param config the main configuration source
827 * @return the global bean context
828 */
829 protected BeanContext initBeans(Configuration config)
830 {
831 // Initialize global bean context
832 BeanContext context = new DefaultBeanContext();
833 rootBeanStore = createRootStore(config);
834 addBeanDuringApplicationStartup(BEAN_GLOBAL_CONTEXT, context);
835 context.setDefaultBeanStore(rootBeanStore);
836
837 // Read default bean definitions
838 beanBuilderResults.add(readBeanDefinition(DEFAULT_BEANS, rootBeanStore,
839 null));
840
841 // Initialize the class loader provider
842 ClassLoaderProvider clp = (ClassLoaderProvider) context
843 .getBean(BEAN_CLASS_LOADER_PROVIDER);
844 ClassLoaderProvider clpInit = initClassLoaderProvider(clp);
845 if (clp != clpInit)
846 {
847 // the init method replaces the class loader provider
848 addBeanDuringApplicationStartup(BEAN_CLASS_LOADER_PROVIDER, clpInit);
849 }
850
851 // Now process custom bean definitions
852 processBeanDefinitions(findBeanDefinitions(config, context), context,
853 clpInit);
854
855 return context;
856 }
857
858 /**
859 * Adds a bean to the application's global root bean store while the
860 * application starts up. This method can be called by derived classes that
861 * need to create beans dynamically Sometimes it is not sufficient to define
862 * beans in additional definition scripts. For instance, objects may have to
863 * be looked up from different sources, or complex logic is required for
864 * creating the beans. In such cases, this method can be used to make the
865 * beans created by a derived application class globally available via the
866 * bean context. As the name implies, this method can be called only during
867 * application startup phase - after the invocation of
868 * {@link #initBeans(Configuration)} and before the global bean context is
869 * accessed. Calling this method to a later point of time is not guaranteed
870 * to have the desired effect!
871 *
872 * @param name the name of the bean
873 * @param bean the bean itself
874 * @since 1.3.1
875 */
876 protected void addBeanDuringApplicationStartup(String name, Object bean)
877 {
878 addBean(rootBeanStore, name, bean);
879 }
880
881 /**
882 * Initializes the global {@code ClassLoaderProvider}. This method is called
883 * by {@link #initBeans(Configuration)} after the default beans have been
884 * loaded. The {@code ClassLoaderProvider} passed to this method was
885 * obtained from the default beans. A derived class can override this method
886 * to perform specific initialization of the passed in {@code
887 * ClassLoaderProvider}. It can even create a completely new object (the
888 * {@code ClassLoaderProvider} returned by this method will become the
889 * global {@code ClassLoaderProvider}; it need not be the same object as was
890 * passed to this method). This base implementation registers the
891 * class loader which has loaded the concrete {@code Application} sub
892 * class and makes it the default class loader.
893 *
894 * @param clp the {@code ClassLoaderProvider} as obtained from the default
895 * beans
896 * @return the new global {@code ClassLoaderProvider} (must never be
897 * <b>null</b>)
898 */
899 protected ClassLoaderProvider initClassLoaderProvider(
900 ClassLoaderProvider clp)
901 {
902 clp.registerClassLoader(CLASS_LOADER, getClass().getClassLoader());
903 clp.setDefaultClassLoaderName(CLASS_LOADER);
904 return clp;
905 }
906
907 /**
908 * A convenience method for processing a file with bean definitions. A new
909 * bean builder will be created, which processes the passed in script. The
910 * defined beans are stored in the specified root store. Occurring builder
911 * exceptions are re-thrown as runtime exceptions.
912 *
913 * @param script defines the script with the bean definitions
914 * @param rootStore the root store for storing the results
915 * @param loaderProvider the optional class loader provider
916 * @return the result object returned by the builder
917 * @throws IllegalArgumentException if required parameters are missing
918 * @throws ApplicationRuntimeException if an error occurs
919 */
920 protected BeanBuilderResult readBeanDefinition(Locator script,
921 MutableBeanStore rootStore, ClassLoaderProvider loaderProvider)
922 {
923 try
924 {
925 return getBeanBuilderFactory().getBeanBuilder().build(script,
926 rootStore, loaderProvider);
927 }
928 catch (BuilderException bex)
929 {
930 throw new ApplicationRuntimeException(
931 "Error when processing script " + script, bex);
932 }
933 }
934
935 /**
936 * Returns a collection with additional bean definition files to process.
937 *
938 * @param config the main configuration source
939 * @return a list with locators to bean definition files to be processed
940 * (can be <b>null</b>)
941 * @deprecated This method is replaced by
942 * {@link #findBeanDefinitions(Configuration, BeanContext)}. It
943 * is still called during application initialization to keep
944 * backwards compatibility, but this base implementation simply
945 * returns an empty collection.
946 */
947 @Deprecated
948 protected Collection<Locator> findBeanDefinitions(Configuration config)
949 {
950 return new ArrayList<Locator>(0);
951 }
952
953 /**
954 * Returns a collection with additional bean definition files to process.
955 * This method is called when the application context is created. All files
956 * contained in the returned list will be processed by the bean builder.
957 * This base implementation obtains the value(s) of the
958 * {@code framework.builder.beandefinitions.beandefinition} configuration
959 * property. The values are interpreted as textual representations of
960 * {@link Locator} objects which can be converted using the
961 * {@link LocatorConverter} class. Strings that do not contain a locator
962 * type prefix (e.g. {@code classpath:} or {@code url:} are expected to be
963 * names of bean definition files, which can be read from the class path. In
964 * addition, the {@link #getPlatformBeansLocator()} method is called to
965 * obtain a {@code Locator} for a file with platform-specific bean
966 * declarations; if this method returns a non-<b>null</b> value, this
967 * {@code Locator} is added to the list, too. If an application has
968 * different requirements for specifying additional bean definition files,
969 * this method can be overridden.
970 *
971 * @param config the main configuration source
972 * @param beanCtx the current {@code BeanContext}
973 * @return a list with locators to bean definition files to be processed
974 * (can be <b>null</b>)
975 * @since 1.2
976 */
977 protected Collection<Locator> findBeanDefinitions(Configuration config,
978 BeanContext beanCtx)
979 {
980 List<?> defLocators = config.getList(PROP_BEAN_DEFS);
981 // for backwards compatibility reasons call old method
982 Collection<Locator> locs = findBeanDefinitions(config);
983 if (locs == null)
984 {
985 locs = Collections.emptyList();
986 }
987
988 Collection<Locator> result =
989 new ArrayList<Locator>(defLocators.size() + locs.size() + 1);
990 Locator platformLocator = getPlatformBeansLocator();
991 if (platformLocator != null)
992 {
993 result.add(platformLocator);
994 }
995 result.addAll(locs);
996 LocatorConverter converter = null;
997
998 for (Iterator<?> it = defLocators.iterator(); it.hasNext();)
999 {
1000 Object locatorRep = it.next();
1001 String strLocatorRep = String.valueOf(locatorRep);
1002 if (strLocatorRep.indexOf(LocatorConverter.PREFIX_SEPARATOR) >= 0)
1003 {
1004 if (converter == null)
1005 {
1006 converter =
1007 new LocatorConverter(
1008 beanCtx.getBean(ClassLoaderProvider.class));
1009 }
1010 result.add((Locator) converter.convert(Locator.class,
1011 locatorRep));
1012 }
1013 else
1014 {
1015 result.add(cpLocator(strLocatorRep));
1016 }
1017 }
1018 return result;
1019 }
1020
1021 /**
1022 * Returns a {@code Locator} object pointing to a file with bean
1023 * declarations related to the platform or UI toolkit. This method is called
1024 * when additional bean declaration files to be loaded during application
1025 * initialization are detected. The base implementation returns a locator
1026 * pointing to a class path resource with the name
1027 * {@code platformbeans.jelly}. This file contains declarations for beans
1028 * like the platform-specific component manager, window manager, etc. In a
1029 * standard JGUIraffe application a single file with this name exists which
1030 * contains definitions compatible to the supported platform. A derived
1031 * class may override this method and return a different {@code Locator}.
1032 * Result can be <b>null</b>, then no additional bean declaration file is
1033 * loaded.
1034 *
1035 * @return a {@code Locator} pointing to platform-specific bean declarations
1036 * @since 1.3
1037 */
1038 protected Locator getPlatformBeansLocator()
1039 {
1040 return PLATFORM_BEANS;
1041 }
1042
1043 /**
1044 * Processes the given bean definition file. A new bean store is created as
1045 * a child of the bean context's default store. This store is passed to the
1046 * bean builder for being populated. Finally it is set as the new default
1047 * store in the bean context. Occurring exceptions will be re-directed as
1048 * runtime exceptions.
1049 *
1050 * @param script the script with the bean definitions
1051 * @param context the global context
1052 * @param clp the class loader provider
1053 * @throws ApplicationRuntimeException if an error occurs
1054 */
1055 void processBeanDefinition(Locator script, BeanContext context,
1056 ClassLoaderProvider clp)
1057 {
1058 DefaultBeanStore store = new DefaultBeanStore();
1059 beanBuilderResults.add(readBeanDefinition(script, store, clp));
1060 ApplicationContextImpl.installBeanStore(context, store);
1061 }
1062
1063 /**
1064 * Processes a list of bean definitions.
1065 *
1066 * @param defs the list with the bean definitions (can be <b>null</b>)
1067 * @param context the global context
1068 * @param clp the class loader provider
1069 */
1070 void processBeanDefinitions(Collection<Locator> defs, BeanContext context,
1071 ClassLoaderProvider clp)
1072 {
1073 if (defs != null)
1074 {
1075 for (Locator l : defs)
1076 {
1077 processBeanDefinition(l, context, clp);
1078 }
1079 }
1080 }
1081
1082 /**
1083 * Returns a collection with the {@code BeanBuilderResult} objects that were
1084 * created during initialization of the application. These objects must be
1085 * released on shutdown.
1086 *
1087 * @return a collection with the builder results to release on shutdown
1088 */
1089 Collection<BeanBuilderResult> getIninitializedBuilderResults()
1090 {
1091 return beanBuilderResults;
1092 }
1093
1094 /**
1095 * Initializes the application's main GUI. This method checks whether a
1096 * script for the main GUI is defined in the application's configuration. If
1097 * this is the case, the script is executed, and the resulting main window
1098 * is stored.
1099 *
1100 * @param appCtx the application context
1101 * @throws ApplicationRuntimeException if an error occurs
1102 */
1103 protected void initGUI(ApplicationContext appCtx)
1104 {
1105 Configuration config = appCtx.getConfiguration();
1106 Locator scriptLocator = locatorForMainScript(config);
1107 if (scriptLocator != null)
1108 {
1109 try
1110 {
1111 Builder builder = appCtx.newBuilder();
1112 ApplicationBuilderData builderData = appCtx.initBuilderData();
1113 Window mainWindow = builder.buildWindow(scriptLocator, builderData);
1114 if (mainWindow != null)
1115 {
1116 appCtx.setMainWindow(mainWindow);
1117 initMainWindow(mainWindow, config);
1118 }
1119 mainWindowBeanContext = builderData.getBuilderContext();
1120 }
1121 catch (BuilderException bex)
1122 {
1123 throw new ApplicationRuntimeException(bex);
1124 }
1125 }
1126 }
1127
1128 /**
1129 * Returns the locator object for the application's main build script. This
1130 * implementation checks if a script is specified in the configuration. If
1131 * this is the case, a class path locator will be returned. Derived classes
1132 * can use different mechanisms to determine the build script. A return
1133 * value of <b>null</b> means that no script should be executed.
1134 *
1135 * @param config the configuration
1136 * @return the locator for the main builder script
1137 */
1138 protected Locator locatorForMainScript(Configuration config)
1139 {
1140 if (config.containsKey(PROP_BUILDER_MAIN_SCRIPT))
1141 {
1142 return cpLocator(config.getString(PROP_BUILDER_MAIN_SCRIPT));
1143 }
1144 else
1145 {
1146 return null;
1147 }
1148 }
1149
1150 /**
1151 * Initializes the application's main window object. This method is called
1152 * after the main window has been created by the main build script. Here the
1153 * passed in configuration object can be used to initialize properties on
1154 * this object. This implementation deals with the window's bounds, which
1155 * can be initialized from the configuration.
1156 *
1157 * @param window the new main window object
1158 * @param config the actual configuration
1159 */
1160 protected void initMainWindow(Window window, Configuration config)
1161 {
1162 if (config.containsKey(PROP_XPOS) || config.containsKey(PROP_YPOS)
1163 || config.containsKey(PROP_WIDTH)
1164 || config.containsKey(PROP_HEIGHT))
1165 {
1166 // only do something if necessary
1167 Rectangle bnds = new Rectangle(window.getXPos(), window.getYPos(),
1168 window.getWidth(), window.getHeight());
1169 bnds.setLocation(config.getInt(PROP_XPOS, (int) bnds.getX()),
1170 config.getInt(PROP_YPOS, (int) bnds.getY()));
1171 bnds.setSize(config.getInt(PROP_WIDTH, (int) bnds.getWidth()),
1172 config.getInt(PROP_HEIGHT, (int) bnds.getHeight()));
1173 window.setBounds(bnds.x, bnds.y, bnds.width, bnds.height);
1174 }
1175 }
1176
1177 /**
1178 * Shows the application's main window. This method is called after the main
1179 * window has been fully initialized. All other parts of the application have
1180 * also been initialized.
1181 *
1182 * @param window the main window
1183 */
1184 protected void showMainWindow(Window window)
1185 {
1186 window.open();
1187 }
1188
1189 /**
1190 * Creates and initializes the application's command queue. This
1191 * implementation obtains the command queue from the global bean context
1192 * managed by the application context.
1193 *
1194 * @param appCtx the application context
1195 * @return the new command queue
1196 * @throws net.sf.jguiraffe.di.InjectionException if the command queue
1197 * cannot be created
1198 */
1199 protected CommandQueue createCommandQueue(ApplicationContext appCtx)
1200 {
1201 CommandQueue q = (CommandQueue) appCtx.getBeanContext().getBean(
1202 BEAN_COMMAND_QUEUE);
1203 return q;
1204 }
1205
1206 /**
1207 * Returns a reference to the command queue that is used for executing
1208 * commands in another thread.
1209 *
1210 * @return the command queue
1211 */
1212 public CommandQueue getCommandQueue()
1213 {
1214 return cmdQueue;
1215 }
1216
1217 /**
1218 * Sets the command queue that is used for executing commands.
1219 *
1220 * @param q the new command queue (must not be <b>null</b>)
1221 * @throws IllegalArgumentException if the command queue is <b>null</b>
1222 */
1223 public void setCommandQueue(CommandQueue q)
1224 {
1225 if (q == null)
1226 {
1227 throw new IllegalArgumentException(
1228 "Command queue must not be null!");
1229 }
1230 cmdQueue = q;
1231 }
1232
1233 /**
1234 * Returns the {@code GUISynchronizer} object used by this
1235 * application. This object can be used to deal with the event dispatch
1236 * thread.
1237 *
1238 * @return the GUI synchronizer
1239 */
1240 public GUISynchronizer getGUISynchronizer()
1241 {
1242 return getCommandQueue().getGUISynchronizer();
1243 }
1244
1245 /**
1246 * Sets the {@code GUISynchronizer} object to be used by this
1247 * application. This object can be used for safe GUI updates that need to
1248 * take place at the event dispatch thread. It will also set at the
1249 * application's command queue, so that always the same synchronizer is
1250 * used.
1251 *
1252 * @param sync the GUI synchronizer
1253 */
1254 public void setGUISynchronizer(GUISynchronizer sync)
1255 {
1256 getCommandQueue().setGUISynchronizer(sync);
1257 }
1258
1259 /**
1260 * Executes the given command. The command is put into the internal command
1261 * queue. It is then executed in another thread. If the passed in command
1262 * implements the {@code ApplicationClient} interface, a reference to
1263 * this application will be automatically set before execution.
1264 *
1265 * @param cmd the command to be executed
1266 */
1267 public void execute(Command cmd)
1268 {
1269 setApplicationReference(cmd, this);
1270 getCommandQueue().execute(cmd);
1271 }
1272
1273 /**
1274 * Shuts down this application. This method should always be called by the
1275 * main window class when the application is to be terminated. This
1276 * implementation checks if commands are still running. If this is the case,
1277 * a message is displayed (using the message output object) to the user
1278 * asking if the application should be ended anyway. The resource for this
1279 * message is defined by the passed in parameter, which can be a resource ID
1280 * or an {@code ApplicationResourceDef} object. Only if the user
1281 * confirms this, the application will be ended.
1282 *
1283 * @param msgres defines the resource of the message to be displayed
1284 * @param titleres defines the resource of the title of the message
1285 */
1286 public void shutdown(Object msgres, Object titleres)
1287 {
1288 log.info("shutdown() called.");
1289 if (titleres != null && getCommandQueue().isPending())
1290 {
1291 if (getApplicationContext().messageBox(msgres, titleres,
1292 MessageOutput.MESSAGE_QUESTION, MessageOutput.BTN_YES_NO)
1293 != MessageOutput.RET_YES)
1294 {
1295 return;
1296 }
1297 }
1298
1299 log.debug("Calling shutdown listeners.");
1300 if (fireCanShutdown())
1301 {
1302 fireShutdown();
1303 onShutdown();
1304 getCommandQueue().shutdown(true);
1305 releaseBeanBuilderResults(getIninitializedBuilderResults());
1306 exitApplication(0);
1307 }
1308 else
1309 {
1310 log.debug("Veto of shutdown listener!");
1311 }
1312 }
1313
1314 /**
1315 * Shuts down this application unconditionally. This version of the {@code
1316 * shutdown()} method does not check for tasks still running in the
1317 * background. It directly invokes the registered shutdown listeners and
1318 * exits the application if none vetos.
1319 */
1320 public void shutdown()
1321 {
1322 shutdown(null, null);
1323 }
1324
1325 /**
1326 * Returns the current <em>exit handler</em> of this application. This is
1327 * the object called during a {@code shutdown()} operation. This method
1328 * never returns <b>null</b>. If no exit handler has been set, a default one
1329 * is returned. The default exit handler terminates this application by
1330 * calling {@code System.exit()} with the current exit code.
1331 *
1332 * @return the exit handler of this application
1333 * @since 1.2
1334 */
1335 public Runnable getExitHandler()
1336 {
1337 Runnable eh = exitHandler.get();
1338 return (eh != null) ? eh : defaultExitHandler;
1339 }
1340
1341 /**
1342 * Sets the <em>exit handler</em> for this application. The exit handler is
1343 * called eventually by {@code shutdown()}. Its task is to ultimately
1344 * terminate this application, e.g. by calling {@code System.exit()}.
1345 *
1346 * @param handler the exit handler for this application (may be <b>null</b>)
1347 * @since 1.2
1348 */
1349 public void setExitHandler(Runnable handler)
1350 {
1351 exitHandler.set(handler);
1352 }
1353
1354 /**
1355 * Returns the current exit code for this application. This value is only
1356 * defined during a {@code shutdown()} operation. This method is intended to
1357 * be called by an <em>exit handler</em> to find out the exit status of the
1358 * application.
1359 *
1360 * @return this application's exit status
1361 * @see #setExitHandler(Runnable)
1362 * @since 1.2
1363 */
1364 public int getExitCode()
1365 {
1366 return exitCode;
1367 }
1368
1369 /**
1370 * Returns the {@code BeanContext} that was created when processing the
1371 * builder script for the main window. This context may be useful because it
1372 * contains some central objects defined by the builder script for the main
1373 * window, e.g. global actions. If this application does not define a
1374 * builder script for the main window, this method returns <b>null</b>.
1375 *
1376 * @return the {@code BeanContext} created when the main window was
1377 * constructed
1378 * @since 1.3.1
1379 */
1380 public BeanContext getMainWindowBeanContext()
1381 {
1382 return mainWindowBeanContext;
1383 }
1384
1385 /**
1386 * A hook for shutdown. This method is called by the default implementation
1387 * of the {@code shutdown()} method. Here application specific
1388 * cleanup can be placed. Note: if this method is overloaded in a derived
1389 * class, the inherited method should be called. This implementation cares
1390 * for storing the user specific configuration if the
1391 * {@code storeuserconfig} property is <b>true</b>. Before that the
1392 * {@link #updateUserConfiguration()} method is called.
1393 */
1394 protected void onShutdown()
1395 {
1396 if (getApplicationContext().getConfiguration().getBoolean(PROP_USRCONF,
1397 false))
1398 {
1399 try
1400 {
1401 updateUserConfiguration();
1402 saveUserConfiguration();
1403 }
1404 catch (ApplicationException cex)
1405 {
1406 log.warn("Error when saving user configuration!", cex);
1407 }
1408 }
1409 }
1410
1411 /**
1412 * Updates the user configuration. This method is called during shutdown if
1413 * the {@code storeuserconfig} configuration property is set. Here
1414 * actual settings can be written in the user configuration object so that
1415 * they can be restored the next time the application is started again. This
1416 * implementation stores the actual bounds of the main frame in the user
1417 * config.
1418 */
1419 protected void updateUserConfiguration()
1420 {
1421 Window wnd = getApplicationContext().getMainWindow();
1422 if (wnd != null)
1423 {
1424 Configuration uc = getUserConfiguration();
1425 uc.setProperty(PROP_XPOS, Integer.valueOf(wnd.getXPos()));
1426 uc.setProperty(PROP_YPOS, Integer.valueOf(wnd.getYPos()));
1427 uc.setProperty(PROP_WIDTH, Integer.valueOf(wnd.getWidth()));
1428 uc.setProperty(PROP_HEIGHT, Integer.valueOf(wnd.getHeight()));
1429 }
1430 }
1431
1432 /**
1433 * Calls the {@code canShutdown()} method on all registered shutdown
1434 * listeners. If one of them returns <b>false </b>, the remaining listeners
1435 * are not invoked and shutdown process is canceled.
1436 *
1437 * @return a flag whether to proceed with the shutdown process
1438 */
1439 protected boolean fireCanShutdown()
1440 {
1441 Object[] listeners = shutdownListeners.getListenerList();
1442 boolean result = true;
1443
1444 for (int i = listeners.length - 2; i >= 0 && result; i -= 2)
1445 {
1446 if (listeners[i] == ApplicationShutdownListener.class)
1447 {
1448 result = ((ApplicationShutdownListener) listeners[i + 1])
1449 .canShutdown(this);
1450 }
1451 }
1452
1453 return result;
1454 }
1455
1456 /**
1457 * Notifies all registered shutdown listeners about the shutdown of this
1458 * application.
1459 */
1460 protected void fireShutdown()
1461 {
1462 Object[] listeners = shutdownListeners.getListenerList();
1463 for (int i = listeners.length - 2; i >= 0; i -= 2)
1464 {
1465 if (listeners[i] == ApplicationShutdownListener.class)
1466 {
1467 ((ApplicationShutdownListener) listeners[i + 1]).shutdown(this);
1468 }
1469 }
1470 }
1471
1472 /**
1473 * Releases the results of bean builder operations that were created during
1474 * initialization of this application. The results of all bean builder
1475 * operations performed by {@link #initBeans(Configuration)} are stored so
1476 * that they can be released when the application shuts down. This is
1477 * exactly the task of this method. It is called by {@code shutdown()}.
1478 *
1479 * @param results the collection with the builder results to be released
1480 */
1481 protected void releaseBeanBuilderResults(
1482 Collection<BeanBuilderResult> results)
1483 {
1484 try
1485 {
1486 BeanBuilder builder = getBeanBuilderFactory().getBeanBuilder();
1487
1488 for (BeanBuilderResult result : getIninitializedBuilderResults())
1489 {
1490 builder.release(result);
1491 }
1492 }
1493 catch (BuilderException bex)
1494 {
1495 log.warn("Error when releasing bean builder results", bex);
1496 }
1497 }
1498
1499 /**
1500 * Terminates this application. This method is called by
1501 * {@link #shutdown(Object, Object)} at the very end. It calls the
1502 * <em>exit handler</em>. This object is responsible for actually
1503 * terminating this application.
1504 *
1505 * @param exitCode the exit code
1506 * @see #setExitHandler(Runnable)
1507 */
1508 protected void exitApplication(int exitCode)
1509 {
1510 if (log.isInfoEnabled())
1511 {
1512 log.info("Application ends with exit code " + exitCode);
1513 log.info("Calling exit handler.");
1514 }
1515
1516 getExitHandler().run();
1517 }
1518
1519 /**
1520 * The main execute method of this application. Performs all initialization
1521 * steps and displays the application's main window. This method starts it
1522 * all.
1523 *
1524 * @throws ApplicationException if an error occurs during initialization
1525 */
1526 public void run() throws ApplicationException
1527 {
1528 applicationContext = createApplicationContext();
1529 setCommandQueue(createCommandQueue(applicationContext));
1530 initGUI(applicationContext);
1531 if (applicationContext.getMainWindow() != null)
1532 {
1533 showMainWindow(applicationContext.getMainWindow());
1534 }
1535 }
1536
1537 /**
1538 * Hook method for processing the command line arguments. Here a derived
1539 * class can place some logic to evaluate passed in parameters. This base
1540 * implementation is empty.
1541 *
1542 * @param args the command line arguments
1543 * @throws ApplicationException if a command line error occurs
1544 */
1545 public void processCommandLine(String[] args) throws ApplicationException
1546 {
1547 // empty method, may be defined in derived classes
1548 }
1549
1550 /**
1551 * Starts an application. This method performs all steps to initialize and
1552 * startup an {@code Application} object. First the system properties
1553 * are checked if the configuration file is specified. Then the application
1554 * is given the opportunity of processing its command line. Finally its
1555 * {@code run()} method is invoked, which starts the application. A
1556 * typical use case for this method is to create an {@code Application}
1557 * instance (which also can be of a derived class) and pass it to this
1558 * method together with the command line array. The rest is done by this
1559 * method.
1560 *
1561 * @param application the application to start
1562 * @param args the command line arguments
1563 * @throws ApplicationException if an error occurs
1564 */
1565 public static void startup(Application application, String[] args)
1566 throws ApplicationException
1567 {
1568 if (application.getConfigResourceName() == null
1569 && application.getConfigURL() == null)
1570 {
1571 if (System.getProperties().containsKey(PROP_CONFIG_NAME))
1572 {
1573 application.setConfigResourceName(System
1574 .getProperty(PROP_CONFIG_NAME));
1575 }
1576 else if (System.getProperties().containsKey(PROP_CONFIG_URL))
1577 {
1578 application.setConfigURL(System.getProperty(PROP_CONFIG_URL));
1579 }
1580 else
1581 {
1582 application.setConfigResourceName(DEF_CONFIG_NAME);
1583 }
1584 }
1585
1586 application.processCommandLine(args);
1587 application.run();
1588 }
1589
1590 /**
1591 * A main method for applications based on this framework. This method tries
1592 * to determine the name of the configuration file from system properties.
1593 * Then it creates an instance of this class, initializes it, and calls the
1594 * {@code startup()} method.
1595 *
1596 * @param args command line arguments
1597 * @throws ApplicationException if an error occurs
1598 */
1599 public static void main(String[] args) throws ApplicationException
1600 {
1601 startup(new Application(), args);
1602 }
1603
1604 /**
1605 * Obtains the central {@code Application} instance from the specified
1606 * {@code BeanContext}. This method provides an easy way for obtaining
1607 * the {@code Application} when only the {@code ApplicationContext} is
1608 * known: just call
1609 * <pre>
1610 * Application myApp = Application.getInstance(appCtx.getBeanContext());
1611 * </pre>
1612 * If the application cannot be found in the given bean context, an
1613 * exception is thrown.
1614 *
1615 * @param context the bean context
1616 * @return the {@code Application} object defined in this bean context
1617 * @throws net.sf.jguiraffe.di.InjectionException if the application bean
1618 * cannot be found
1619 * @throws IllegalArgumentException if the passed in context is <b>null</b>
1620 */
1621 public static Application getInstance(BeanContext context)
1622 {
1623 if (context == null)
1624 {
1625 throw new IllegalArgumentException("Bean context must not be null!");
1626 }
1627 return (Application) context.getBean(BEAN_APPLICATION);
1628 }
1629
1630 /**
1631 * Helper method for creating a {@code ClassPathLocator} which is configured
1632 * with this application's class loader.
1633 *
1634 * @param resource the name of the resource to be loaded
1635 * @return the newly created {@code ClassPathLocator}
1636 */
1637 private ClassPathLocator cpLocator(String resource)
1638 {
1639 return ClassPathLocator.getInstance(resource, getClass()
1640 .getClassLoader());
1641 }
1642
1643 /**
1644 * Adds a bean to a bean store. This implementation creates a constant bean
1645 * provider for the specified bean.
1646 *
1647 * @param store the bean store
1648 * @param name the name of the bean
1649 * @param bean the bean itself
1650 */
1651 private static void addBean(MutableBeanStore store, String name, Object bean)
1652 {
1653 store.addBeanProvider(name, ConstantBeanProvider.getInstance(bean));
1654 }
1655 }