]> gerrit.simantics Code Review - simantics/platform.git/blob
d31914fd00351c59d1c42f3f5e735f18cdefc712
[simantics/platform.git] /
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.workbench.internal;
13
14 import java.io.File;
15 import java.io.FileInputStream;
16 import java.io.FileOutputStream;
17 import java.io.IOException;
18 import java.io.OutputStream;
19 import java.net.MalformedURLException;
20 import java.net.URL;
21 import java.util.Map;
22 import java.util.Properties;
23
24 import org.eclipse.core.runtime.IConfigurationElement;
25 import org.eclipse.core.runtime.IExecutableExtension;
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.Platform;
28 import org.eclipse.core.runtime.Status;
29 import org.eclipse.equinox.app.IApplication;
30 import org.eclipse.equinox.app.IApplicationContext;
31 import org.eclipse.jface.dialogs.Dialog;
32 import org.eclipse.jface.dialogs.MessageDialog;
33 import org.eclipse.osgi.service.datalocation.Location;
34 import org.eclipse.osgi.util.NLS;
35 import org.eclipse.swt.SWT;
36 import org.eclipse.swt.widgets.Composite;
37 import org.eclipse.swt.widgets.Control;
38 import org.eclipse.swt.widgets.Display;
39 import org.eclipse.swt.widgets.MessageBox;
40 import org.eclipse.swt.widgets.Shell;
41 import org.eclipse.ui.IWorkbench;
42 import org.eclipse.ui.PlatformUI;
43 import org.eclipse.ui.application.WorkbenchAdvisor;
44 import org.eclipse.ui.internal.WorkbenchPlugin;
45 import org.eclipse.ui.internal.ide.ChooseWorkspaceData;
46 import org.eclipse.ui.internal.ide.ChooseWorkspaceDialog;
47 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
48 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
49 import org.eclipse.ui.internal.ide.StatusUtil;
50 import org.simantics.application.arguments.ApplicationUtils;
51 import org.simantics.application.arguments.Arguments;
52 import org.simantics.application.arguments.IArgumentFactory;
53 import org.simantics.application.arguments.IArguments;
54 import org.simantics.application.arguments.SimanticsArguments;
55 import org.simantics.db.management.ISessionContextProvider;
56 import org.simantics.db.management.ISessionContextProviderSource;
57 import org.simantics.db.management.SessionContextProvider;
58 import org.simantics.db.management.SingleSessionContextProviderSource;
59 import org.simantics.ui.SimanticsUI;
60 import org.simantics.utils.ui.BundleUtils;
61
62
63 /**
64  * The "main program" for the Eclipse IDE.
65  * 
66  * @since 3.0
67  */
68 public class SimanticsWorkbenchApplication implements IApplication, IExecutableExtension {
69
70     /**
71      * The name of the folder containing metadata information for the workspace.
72      */
73     public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$
74
75     private static final String VERSION_FILENAME = "version.ini"; //$NON-NLS-1$
76
77     private static final String WORKSPACE_VERSION_KEY = "org.eclipse.core.runtime"; //$NON-NLS-1$
78
79     private static final String WORKSPACE_VERSION_VALUE = "1"; //$NON-NLS-1$
80
81     private static final String PROP_EXIT_CODE = "eclipse.exitcode"; //$NON-NLS-1$
82
83     /**
84      * A special return code that will be recognized by the launcher and used to
85      * restart the workbench.
86      */
87     private static final Integer EXIT_RELAUNCH = new Integer(24);
88
89     /**
90      * A special return code that will be recognized by the PDE launcher and used to
91      * show an error dialog if the workspace is locked.
92      */
93     private static final Integer EXIT_WORKSPACE_LOCKED = new Integer(15);
94
95     /**
96      * Creates a new IDE application.
97      */
98     public SimanticsWorkbenchApplication() {
99         // There is nothing to do for WorkbenchApplication
100     }
101
102     public WorkbenchAdvisor createWorkbenchAdvisor(IArguments args, DelayedEventsProcessor processor) {
103         return new SimanticsWorkbenchAdvisor(args, processor);
104     }
105
106     /* (non-Javadoc)
107      * @see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.IApplicationContext context)
108      */
109     @Override
110     public Object start(IApplicationContext appContext) throws Exception {
111         ApplicationUtils.loadSystemProperties(BundleUtils.find(Activator.PLUGIN_ID, "system.properties"));
112         IArguments args = parseArguments((String[]) appContext.getArguments().get(IApplicationContext.APPLICATION_ARGS));
113
114         Display display = createDisplay();
115         // processor must be created before we start event loop
116         DelayedEventsProcessor processor = new DelayedEventsProcessor(display);
117
118         try {
119             Object argCheck = verifyArguments(args);
120             if (argCheck != null)
121                 return argCheck;
122
123             // look and see if there's a splash shell we can parent off of
124             Shell shell = WorkbenchPlugin.getSplashShell(display);
125             if (shell != null) {
126                 // should should set the icon and message for this shell to be the 
127                 // same as the chooser dialog - this will be the guy that lives in
128                 // the task bar and without these calls you'd have the default icon 
129                 // with no message.
130                 shell.setText(ChooseWorkspaceDialog.getWindowTitle());
131                 shell.setImages(Dialog.getDefaultImages());
132             }
133
134             Object instanceLocationCheck = checkInstanceLocation(shell, appContext.getArguments(), args);
135             if (instanceLocationCheck != null) {
136                 WorkbenchPlugin.unsetSplashShell(display);
137                 Platform.endSplash();
138                 return instanceLocationCheck;
139             }
140
141             final ISessionContextProvider provider = new SessionContextProvider(null);
142             final ISessionContextProviderSource contextProviderSource = new SingleSessionContextProviderSource(provider);
143             //final ISessionContextProviderSource contextProviderSource = new WorkbenchWindowSessionContextProviderSource(PlatformUI.getWorkbench());
144             SimanticsUI.setSessionContextProviderSource(contextProviderSource);
145             org.simantics.db.layer0.internal.SimanticsInternal.setSessionContextProviderSource(contextProviderSource);
146             org.simantics.Simantics.setSessionContextProviderSource(contextProviderSource);
147             
148             // create the workbench with this advisor and run it until it exits
149             // N.B. createWorkbench remembers the advisor, and also registers
150             // the workbench globally so that all UI plug-ins can find it using
151             // PlatformUI.getWorkbench() or AbstractUIPlugin.getWorkbench()
152             int returnCode = PlatformUI.createAndRunWorkbench(display,
153                     createWorkbenchAdvisor(args, processor));
154
155             // the workbench doesn't support relaunch yet (bug 61809) so
156             // for now restart is used, and exit data properties are checked
157             // here to substitute in the relaunch return code if needed
158             if (returnCode != PlatformUI.RETURN_RESTART) {
159                 return EXIT_OK;
160             }
161
162             // if the exit code property has been set to the relaunch code, then
163             // return that code now, otherwise this is a normal restart
164             return EXIT_RELAUNCH.equals(Integer.getInteger(PROP_EXIT_CODE)) ? EXIT_RELAUNCH
165                     : EXIT_RESTART;
166         } finally {
167             if (display != null) {
168                 display.dispose();
169             }
170             Location instanceLoc = Platform.getInstanceLocation();
171             if (instanceLoc != null)
172                 instanceLoc.release();
173         }
174     }
175
176     /*************************************************************************/
177
178     private IArguments parseArguments(String[] args) {
179         IArgumentFactory<?>[] accepted = {
180                 SimanticsArguments.RECOVERY_POLICY_FIX_ERRORS,
181                 SimanticsArguments.ONTOLOGY_RECOVERY_POLICY_REINSTALL,
182                 SimanticsArguments.DEFAULT_WORKSPACE_LOCATION,
183                 SimanticsArguments.WORKSPACE_CHOOSER,
184                 SimanticsArguments.WORKSPACE_NO_REMEMBER,
185                 SimanticsArguments.PERSPECTIVE,
186                 SimanticsArguments.SERVER,
187                 SimanticsArguments.NEW_MODEL,
188                 SimanticsArguments.EXPERIMENT,
189                 SimanticsArguments.DISABLE_INDEX,
190                 SimanticsArguments.DATABASE_ID,
191         };
192         IArguments result = Arguments.parse(args, accepted);
193         return result;
194     }
195
196     private Object verifyArguments(IArguments args) {
197         StringBuilder report = new StringBuilder();
198
199 //        if (args.contains(SimanticsArguments.NEW_PROJECT)) {
200 //            if (args.contains(SimanticsArguments.PROJECT)) {
201 //                exclusiveArguments(report, SimanticsArguments.PROJECT, SimanticsArguments.NEW_PROJECT);
202 //            }
203 //            // Must have a server to checkout from when creating a new
204 //            // project right from the beginning.
205 //            if (!args.contains(SimanticsArguments.SERVER)) {
206 //                missingArgument(report, SimanticsArguments.SERVER);
207 //            }
208 //        } else if (args.contains(SimanticsArguments.PROJECT)) {
209 //            // To load a project, a server must be defined to checkout from
210 //            if (!args.contains(SimanticsArguments.SERVER)) {
211 //                missingArgument(report, SimanticsArguments.SERVER);
212 //            }
213 //        }
214
215         // NEW_MODEL and MODEL arguments are optional
216         // EXPERIMENT argument is optional
217
218         String result = report.toString();
219         boolean valid = result.length() == 0;
220
221         if (!valid) {
222             String msg = NLS.bind(Messages.Application_1, result);
223             MessageDialog.openInformation(null, Messages.Application_2, msg);
224         }
225         return valid ? null : EXIT_OK;
226     }
227
228 //    private void exclusiveArguments(StringBuilder sb, IArgumentFactory<?> arg1, IArgumentFactory<?> arg2) {
229 //        sb.append(NLS.bind(Messages.Application_3, arg1.getArgument(), arg2.getArgument()));
230 //        sb.append('\n');
231 //    }
232 //
233 //    private void missingArgument(StringBuilder sb, IArgumentFactory<?> arg) {
234 //        sb.append(NLS.bind(Messages.Application_0, arg.getArgument()));
235 //        sb.append('\n');
236 //    }
237
238     /*************************************************************************/
239
240     /**
241      * Creates the display used by the application.
242      * 
243      * @return the display used by the application
244      */
245     protected Display createDisplay() {
246         return PlatformUI.createDisplay();
247     }
248
249     /* (non-Javadoc)
250      * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object)
251      */
252     @Override
253     public void setInitializationData(IConfigurationElement config,
254             String propertyName, Object data) {
255         // There is nothing to do for ProConfApplication
256     }
257
258     /**
259      * Return true if a valid workspace path has been set and false otherwise.
260      * Prompt for and set the path if possible and required.
261      * @param applicationArguments 
262      * 
263      * @return true if a valid instance location has been set and false
264      *         otherwise
265      */
266     private Object checkInstanceLocation(Shell shell, Map<?,?> applicationArguments, IArguments args) {
267         // -data @none was specified but an ide requires workspace
268         Location instanceLoc = Platform.getInstanceLocation();
269         if (instanceLoc == null) {
270             MessageDialog
271             .openError(
272                     shell,
273                     IDEWorkbenchMessages.IDEApplication_workspaceMandatoryTitle,
274                     IDEWorkbenchMessages.IDEApplication_workspaceMandatoryMessage);
275             return EXIT_OK;
276         }
277
278         // -data "/valid/path", workspace already set
279         // This information is stored in configuration/.settings/org.eclipse.ui.ide.prefs
280         if (instanceLoc.isSet()) {
281             // make sure the meta data version is compatible (or the user has
282             // chosen to overwrite it).
283             if (!checkValidWorkspace(shell, instanceLoc.getURL())) {
284                 return EXIT_OK;
285             }
286
287             // at this point its valid, so try to lock it and update the
288             // metadata version information if successful
289             try {
290                 if (instanceLoc.lock()) {
291                     writeWorkspaceVersion();
292                     return null;
293                 }
294
295                 // we failed to create the directory.
296                 // Two possibilities:
297                 // 1. directory is already in use
298                 // 2. directory could not be created
299                 File workspaceDirectory = new File(instanceLoc.getURL().getFile());
300                 if (workspaceDirectory.exists()) {
301                     if (isDevLaunchMode(applicationArguments)) {
302                         return EXIT_WORKSPACE_LOCKED;
303                     }
304                     MessageDialog.openError(
305                             shell,
306                             IDEWorkbenchMessages.IDEApplication_workspaceCannotLockTitle,
307                             IDEWorkbenchMessages.IDEApplication_workspaceCannotLockMessage);
308                 } else {
309                     MessageDialog.openError(
310                             shell,
311                             IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle,
312                             IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage);
313                 }
314             } catch (IOException e) {
315                 IDEWorkbenchPlugin.log("Could not obtain lock for workspace location", //$NON-NLS-1$
316                         e);
317                 MessageDialog
318                 .openError(
319                         shell,
320                         IDEWorkbenchMessages.InternalError,
321                         e.getMessage());
322             }
323             return EXIT_OK;
324         }
325
326         // -data @noDefault or -data not specified, prompt and set
327         ChooseWorkspaceData launchData = null;
328         if (args.contains(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION)) {
329             launchData = new ChooseWorkspaceData(args.get(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION));
330         } else {
331             launchData = new ChooseWorkspaceData(instanceLoc.getDefault());
332         }
333
334         boolean force = args.contains(SimanticsArguments.WORKSPACE_CHOOSER);
335         boolean suppressAskAgain = args.contains(SimanticsArguments.WORKSPACE_NO_REMEMBER);
336
337         while (true) {
338             URL workspaceUrl = promptForWorkspace(shell, launchData, force, suppressAskAgain);
339             if (workspaceUrl == null) {
340                 return EXIT_OK;
341             }
342
343             // if there is an error with the first selection, then force the
344             // dialog to open to give the user a chance to correct
345             force = true;
346
347             try {
348                 // the operation will fail if the url is not a valid
349                 // instance data area, so other checking is unneeded
350                 if (instanceLoc.setURL(workspaceUrl, true)) {
351                     launchData.writePersistedData();
352                     writeWorkspaceVersion();
353                     return null;
354                 }
355             } catch (IllegalStateException e) {
356                 MessageDialog
357                 .openError(
358                         shell,
359                         IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle,
360                         IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage);
361                 return EXIT_OK;
362             }
363
364             // by this point it has been determined that the workspace is
365             // already in use -- force the user to choose again
366             MessageDialog.openError(shell, IDEWorkbenchMessages.IDEApplication_workspaceInUseTitle,
367                     IDEWorkbenchMessages.IDEApplication_workspaceInUseMessage);
368         }
369     }
370
371     private static boolean isDevLaunchMode(Map<?,?> args) {
372         // see org.eclipse.pde.internal.core.PluginPathFinder.isDevLaunchMode()
373         if (Boolean.getBoolean("eclipse.pde.launch")) //$NON-NLS-1$
374             return true;
375         return args.containsKey("-pdelaunch"); //$NON-NLS-1$
376     }
377
378     private static class ChooseSimanticsWorkspaceDialog extends ChooseWorkspaceDialog {
379
380         public ChooseSimanticsWorkspaceDialog(Shell parentShell, ChooseWorkspaceData launchData, boolean suppressAskAgain, boolean centerOnMonitor) {
381             super(parentShell, launchData, suppressAskAgain, centerOnMonitor);
382         }
383         
384         @Override
385         protected void configureShell(Shell shell) {
386             super.configureShell(shell);
387             // Use product name in shell title instead of generic "Eclipse Launcher"
388             shell.setText(getWindowTitle());
389         }
390     }
391
392     /**
393      * Open a workspace selection dialog on the argument shell, populating the
394      * argument data with the user's selection. Perform first level validation
395      * on the selection by comparing the version information. This method does
396      * not examine the runtime state (e.g., is the workspace already locked?).
397      * 
398      * @param shell
399      * @param launchData
400      * @param force
401      *            setting to true makes the dialog open regardless of the
402      *            showDialog value
403      * @return An URL storing the selected workspace or null if the user has
404      *         canceled the launch operation.
405      */
406     private URL promptForWorkspace(Shell shell, ChooseWorkspaceData launchData,
407             boolean force, boolean suppressAskAgain) {
408         URL url = null;
409         do {
410             // okay to use the shell now - this is the splash shell
411             new ChooseSimanticsWorkspaceDialog(shell, launchData, suppressAskAgain, true).prompt(force); 
412             
413             String instancePath = launchData.getSelection();
414             if (instancePath == null) {
415                 return null;
416             }
417
418             // the dialog is not forced on the first iteration, but is on every
419             // subsequent one -- if there was an error then the user needs to be
420             // allowed to fix it
421             force = true;
422
423             // 70576: don't accept empty input
424             if (instancePath.length() <= 0) {
425                 MessageDialog
426                 .openError(
427                         shell,
428                         IDEWorkbenchMessages.IDEApplication_workspaceEmptyTitle,
429                         IDEWorkbenchMessages.IDEApplication_workspaceEmptyMessage);
430                 continue;
431             }
432
433             // create the workspace if it does not already exist
434             File workspace = new File(instancePath);
435             if (!workspace.exists()) {
436                 workspace.mkdir();
437             }
438
439             try {
440                 // Don't use File.toURL() since it adds a leading slash that Platform does not
441                 // handle properly.  See bug 54081 for more details.
442                 String path = workspace.getAbsolutePath().replace(
443                         File.separatorChar, '/');
444                 url = new URL("file", null, path); //$NON-NLS-1$
445             } catch (MalformedURLException e) {
446                 MessageDialog
447                 .openError(
448                         shell,
449                         IDEWorkbenchMessages.IDEApplication_workspaceInvalidTitle,
450                         IDEWorkbenchMessages.IDEApplication_workspaceInvalidMessage);
451                 continue;
452             }
453         } while (!checkValidWorkspace(shell, url));
454
455         return url;
456     }
457
458     /**
459      * Return true if the argument directory is ok to use as a workspace and
460      * false otherwise. A version check will be performed, and a confirmation
461      * box may be displayed on the argument shell if an older version is
462      * detected.
463      * 
464      * @return true if the argument URL is ok to use as a workspace and false
465      *         otherwise.
466      */
467     private boolean checkValidWorkspace(Shell shell, URL url) {
468         // a null url is not a valid workspace
469         if (url == null) {
470             return false;
471         }
472
473         String version = readWorkspaceVersion(url);
474
475         // if the version could not be read, then there is not any existing
476         // workspace data to trample, e.g., perhaps its a new directory that
477         // is just starting to be used as a workspace
478         if (version == null) {
479             return true;
480         }
481
482         final int ide_version = Integer.parseInt(WORKSPACE_VERSION_VALUE);
483         int workspace_version = Integer.parseInt(version);
484
485         // equality test is required since any version difference (newer
486         // or older) may result in data being trampled
487         if (workspace_version == ide_version) {
488             return true;
489         }
490
491         // At this point workspace has been detected to be from a version
492         // other than the current ide version -- find out if the user wants
493         // to use it anyhow.
494                 int severity;
495                 String title;
496                 String message;
497                 if (workspace_version < ide_version) {
498                         // Workspace < IDE. Update must be possible without issues,
499                         // so only inform user about it.
500                         severity = MessageDialog.INFORMATION;
501                         title = IDEWorkbenchMessages.IDEApplication_versionTitle_olderWorkspace;
502                         message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_olderWorkspace, url.getFile());
503                 } else {
504                         // Workspace > IDE. It must have been opened with a newer IDE version.
505                         // Downgrade might be problematic, so warn user about it.
506                         severity = MessageDialog.WARNING;
507                         title = IDEWorkbenchMessages.IDEApplication_versionTitle_newerWorkspace;
508                         message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_newerWorkspace, url.getFile());
509                 }
510
511         MessageBox mbox = new MessageBox(shell, SWT.OK | SWT.CANCEL
512                 | SWT.ICON_WARNING | SWT.APPLICATION_MODAL);
513         mbox.setText(title);
514         mbox.setMessage(message);
515         return mbox.open() == SWT.OK;
516     }
517
518     /**
519      * Look at the argument URL for the workspace's version information. Return
520      * that version if found and null otherwise.
521      */
522     private static String readWorkspaceVersion(URL workspace) {
523         File versionFile = getVersionFile(workspace, false);
524         if (versionFile == null || !versionFile.exists()) {
525             return null;
526         }
527
528         try {
529             // Although the version file is not spec'ed to be a Java properties
530             // file, it happens to follow the same format currently, so using
531             // Properties to read it is convenient.
532             Properties props = new Properties();
533             FileInputStream is = new FileInputStream(versionFile);
534             try {
535                 props.load(is);
536             } finally {
537                 is.close();
538             }
539
540             return props.getProperty(WORKSPACE_VERSION_KEY);
541         } catch (IOException e) {
542             IDEWorkbenchPlugin.log("Could not read version file", new Status( //$NON-NLS-1$
543                     IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH,
544                     IStatus.ERROR,
545                     e.getMessage() == null ? "" : e.getMessage(), //$NON-NLS-1$,
546                             e));
547             return null;
548         }
549     }
550
551     /**
552      * Write the version of the metadata into a known file overwriting any
553      * existing file contents. Writing the version file isn't really crucial,
554      * so the function is silent about failure
555      */
556     private static void writeWorkspaceVersion() {
557         Location instanceLoc = Platform.getInstanceLocation();
558         if (instanceLoc == null || instanceLoc.isReadOnly()) {
559             return;
560         }
561
562         File versionFile = getVersionFile(instanceLoc.getURL(), true);
563         if (versionFile == null) {
564             return;
565         }
566
567         OutputStream output = null;
568         try {
569             String versionLine = WORKSPACE_VERSION_KEY + '='
570             + WORKSPACE_VERSION_VALUE;
571
572             output = new FileOutputStream(versionFile);
573             output.write(versionLine.getBytes("UTF-8")); //$NON-NLS-1$
574         } catch (IOException e) {
575             IDEWorkbenchPlugin.log("Could not write version file", //$NON-NLS-1$
576                     StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e));
577         } finally {
578             try {
579                 if (output != null) {
580                     output.close();
581                 }
582             } catch (IOException e) {
583                 // do nothing
584             }
585         }
586     }
587
588     /**
589      * The version file is stored in the metadata area of the workspace. This
590      * method returns an URL to the file or null if the directory or file does
591      * not exist (and the create parameter is false).
592      * 
593      * @param create
594      *            If the directory and file does not exist this parameter
595      *            controls whether it will be created.
596      * @return An url to the file or null if the version file does not exist or
597      *         could not be created.
598      */
599     private static File getVersionFile(URL workspaceUrl, boolean create) {
600         if (workspaceUrl == null) {
601             return null;
602         }
603
604         try {
605             // make sure the directory exists
606             File metaDir = new File(workspaceUrl.getPath(), METADATA_FOLDER);
607             if (!metaDir.exists() && (!create || !metaDir.mkdir())) {
608                 return null;
609             }
610
611             // make sure the file exists
612             File versionFile = new File(metaDir, VERSION_FILENAME);
613             if (!versionFile.exists()
614                     && (!create || !versionFile.createNewFile())) {
615                 return null;
616             }
617
618             return versionFile;
619         } catch (IOException e) {
620             // cannot log because instance area has not been set
621             return null;
622         }
623     }
624
625     /* (non-Javadoc)
626      * @see org.eclipse.equinox.app.IApplication#stop()
627      */
628     @Override
629     public void stop() {
630         final IWorkbench workbench = PlatformUI.getWorkbench();
631         if (workbench == null)
632             return;
633         final Display display = workbench.getDisplay();
634         display.syncExec(new Runnable() {
635             @Override
636             public void run() {
637                 if (!display.isDisposed())
638                     workbench.close();
639             }
640         });
641     }
642
643 }