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