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 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      * &quot;userConfig&quot;, 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      * &lt;configuration&gt;
580      *   ...
581      *   &lt;xml fileName="${user.home}/myAppConfig.xml" config-name="userConfig"
582      *     config-optional="true" config-forceCreate="true"/&gt;
583      *   &lt;!-- Further configuration sources to load --&gt;
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 }