001: /*******************************************************************************
002: * Copyright (c) 2003, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.ide.application;
011:
012: import java.io.File;
013: import java.io.FileInputStream;
014: import java.io.FileOutputStream;
015: import java.io.IOException;
016: import java.io.OutputStream;
017: import java.net.MalformedURLException;
018: import java.net.URL;
019: import java.util.Properties;
020:
021: import org.eclipse.core.runtime.IConfigurationElement;
022: import org.eclipse.core.runtime.IExecutableExtension;
023: import org.eclipse.core.runtime.IStatus;
024: import org.eclipse.core.runtime.Platform;
025: import org.eclipse.core.runtime.Status;
026: import org.eclipse.equinox.app.IApplication;
027: import org.eclipse.equinox.app.IApplicationContext;
028: import org.eclipse.jface.dialogs.MessageDialog;
029: import org.eclipse.osgi.service.datalocation.Location;
030: import org.eclipse.osgi.util.NLS;
031: import org.eclipse.swt.SWT;
032: import org.eclipse.swt.widgets.Display;
033: import org.eclipse.swt.widgets.MessageBox;
034: import org.eclipse.swt.widgets.Shell;
035: import org.eclipse.ui.IWorkbench;
036: import org.eclipse.ui.PlatformUI;
037: import org.eclipse.ui.internal.ide.ChooseWorkspaceData;
038: import org.eclipse.ui.internal.ide.ChooseWorkspaceDialog;
039: import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
040: import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
041: import org.eclipse.ui.internal.ide.StatusUtil;
042:
043: /**
044: * The "main program" for the Eclipse IDE.
045: *
046: * @since 3.0
047: */
048: public class IDEApplication implements IApplication,
049: IExecutableExtension {
050:
051: /**
052: * The name of the folder containing metadata information for the workspace.
053: */
054: public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$
055:
056: private static final String VERSION_FILENAME = "version.ini"; //$NON-NLS-1$
057:
058: private static final String WORKSPACE_VERSION_KEY = "org.eclipse.core.runtime"; //$NON-NLS-1$
059:
060: private static final String WORKSPACE_VERSION_VALUE = "1"; //$NON-NLS-1$
061:
062: private static final String PROP_EXIT_CODE = "eclipse.exitcode"; //$NON-NLS-1$
063:
064: /**
065: * A special return code that will be recognized by the launcher and used to
066: * restart the workbench.
067: */
068: private static final Integer EXIT_RELAUNCH = new Integer(24);
069:
070: /**
071: * The ID of the application plug-in
072: */
073: public static final String PLUGIN_ID = "org.eclipse.ui.ide.application"; //$NON-NLS-1$
074:
075: /**
076: * Creates a new IDE application.
077: */
078: public IDEApplication() {
079: // There is nothing to do for IDEApplication
080: }
081:
082: /* (non-Javadoc)
083: * @see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.IApplicationContext context)
084: */
085: public Object start(IApplicationContext appContext)
086: throws Exception {
087: Display display = createDisplay();
088:
089: try {
090: Shell shell = new Shell(display, SWT.ON_TOP);
091:
092: try {
093: if (!checkInstanceLocation(shell)) {
094: Platform.endSplash();
095: return EXIT_OK;
096: }
097: } finally {
098: if (shell != null) {
099: shell.dispose();
100: }
101: }
102:
103: // create the workbench with this advisor and run it until it exits
104: // N.B. createWorkbench remembers the advisor, and also registers
105: // the workbench globally so that all UI plug-ins can find it using
106: // PlatformUI.getWorkbench() or AbstractUIPlugin.getWorkbench()
107: int returnCode = PlatformUI.createAndRunWorkbench(display,
108: new IDEWorkbenchAdvisor());
109:
110: // the workbench doesn't support relaunch yet (bug 61809) so
111: // for now restart is used, and exit data properties are checked
112: // here to substitute in the relaunch return code if needed
113: if (returnCode != PlatformUI.RETURN_RESTART) {
114: return EXIT_OK;
115: }
116:
117: // if the exit code property has been set to the relaunch code, then
118: // return that code now, otherwise this is a normal restart
119: return EXIT_RELAUNCH.equals(Integer
120: .getInteger(PROP_EXIT_CODE)) ? EXIT_RELAUNCH
121: : EXIT_RESTART;
122: } finally {
123: if (display != null) {
124: display.dispose();
125: }
126: }
127: }
128:
129: /**
130: * Creates the display used by the application.
131: *
132: * @return the display used by the application
133: */
134: protected Display createDisplay() {
135: return PlatformUI.createDisplay();
136: }
137:
138: /* (non-Javadoc)
139: * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object)
140: */
141: public void setInitializationData(IConfigurationElement config,
142: String propertyName, Object data) {
143: // There is nothing to do for IDEApplication
144: }
145:
146: /**
147: * Return true if a valid workspace path has been set and false otherwise.
148: * Prompt for and set the path if possible and required.
149: *
150: * @return true if a valid instance location has been set and false
151: * otherwise
152: */
153: private boolean checkInstanceLocation(Shell shell) {
154: // -data @none was specified but an ide requires workspace
155: Location instanceLoc = Platform.getInstanceLocation();
156: if (instanceLoc == null) {
157: MessageDialog
158: .openError(
159: shell,
160: IDEWorkbenchMessages.IDEApplication_workspaceMandatoryTitle,
161: IDEWorkbenchMessages.IDEApplication_workspaceMandatoryMessage);
162: return false;
163: }
164:
165: // -data "/valid/path", workspace already set
166: if (instanceLoc.isSet()) {
167: // make sure the meta data version is compatible (or the user has
168: // chosen to overwrite it).
169: if (!checkValidWorkspace(shell, instanceLoc.getURL())) {
170: return false;
171: }
172:
173: // at this point its valid, so try to lock it and update the
174: // metadata version information if successful
175: try {
176: if (instanceLoc.lock()) {
177: writeWorkspaceVersion();
178: return true;
179: }
180:
181: // we failed to create the directory.
182: // Two possibilities:
183: // 1. directory is already in use
184: // 2. directory could not be created
185: File workspaceDirectory = new File(instanceLoc.getURL()
186: .getFile());
187: if (workspaceDirectory.exists()) {
188: MessageDialog
189: .openError(
190: shell,
191: IDEWorkbenchMessages.IDEApplication_workspaceCannotLockTitle,
192: IDEWorkbenchMessages.IDEApplication_workspaceCannotLockMessage);
193: } else {
194: MessageDialog
195: .openError(
196: shell,
197: IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle,
198: IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage);
199: }
200: } catch (IOException e) {
201: IDEWorkbenchPlugin.log(
202: "Could not obtain lock for workspace location", //$NON-NLS-1$
203: e);
204: MessageDialog.openError(shell,
205: IDEWorkbenchMessages.InternalError, e
206: .getMessage());
207: }
208: return false;
209: }
210:
211: // -data @noDefault or -data not specified, prompt and set
212: ChooseWorkspaceData launchData = new ChooseWorkspaceData(
213: instanceLoc.getDefault());
214:
215: boolean force = false;
216: while (true) {
217: URL workspaceUrl = promptForWorkspace(shell, launchData,
218: force);
219: if (workspaceUrl == null) {
220: return false;
221: }
222:
223: // if there is an error with the first selection, then force the
224: // dialog to open to give the user a chance to correct
225: force = true;
226:
227: try {
228: // the operation will fail if the url is not a valid
229: // instance data area, so other checking is unneeded
230: if (instanceLoc.setURL(workspaceUrl, true)) {
231: launchData.writePersistedData();
232: writeWorkspaceVersion();
233: return true;
234: }
235: } catch (IllegalStateException e) {
236: MessageDialog
237: .openError(
238: shell,
239: IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle,
240: IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage);
241: return false;
242: }
243:
244: // by this point it has been determined that the workspace is
245: // already in use -- force the user to choose again
246: MessageDialog
247: .openError(
248: shell,
249: IDEWorkbenchMessages.IDEApplication_workspaceInUseTitle,
250: IDEWorkbenchMessages.IDEApplication_workspaceInUseMessage);
251: }
252: }
253:
254: /**
255: * Open a workspace selection dialog on the argument shell, populating the
256: * argument data with the user's selection. Perform first level validation
257: * on the selection by comparing the version information. This method does
258: * not examine the runtime state (e.g., is the workspace already locked?).
259: *
260: * @param shell
261: * @param launchData
262: * @param force
263: * setting to true makes the dialog open regardless of the
264: * showDialog value
265: * @return An URL storing the selected workspace or null if the user has
266: * canceled the launch operation.
267: */
268: private URL promptForWorkspace(Shell shell,
269: ChooseWorkspaceData launchData, boolean force) {
270: URL url = null;
271: do {
272: // don't use the parent shell to make the dialog a top-level
273: // shell. See bug 84881.
274: new ChooseWorkspaceDialog(null, launchData, false, true)
275: .prompt(force);
276: String instancePath = launchData.getSelection();
277: if (instancePath == null) {
278: return null;
279: }
280:
281: // the dialog is not forced on the first iteration, but is on every
282: // subsequent one -- if there was an error then the user needs to be
283: // allowed to fix it
284: force = true;
285:
286: // 70576: don't accept empty input
287: if (instancePath.length() <= 0) {
288: MessageDialog
289: .openError(
290: shell,
291: IDEWorkbenchMessages.IDEApplication_workspaceEmptyTitle,
292: IDEWorkbenchMessages.IDEApplication_workspaceEmptyMessage);
293: continue;
294: }
295:
296: // create the workspace if it does not already exist
297: File workspace = new File(instancePath);
298: if (!workspace.exists()) {
299: workspace.mkdir();
300: }
301:
302: try {
303: // Don't use File.toURL() since it adds a leading slash that Platform does not
304: // handle properly. See bug 54081 for more details.
305: String path = workspace.getAbsolutePath().replace(
306: File.separatorChar, '/');
307: url = new URL("file", null, path); //$NON-NLS-1$
308: } catch (MalformedURLException e) {
309: MessageDialog
310: .openError(
311: shell,
312: IDEWorkbenchMessages.IDEApplication_workspaceInvalidTitle,
313: IDEWorkbenchMessages.IDEApplication_workspaceInvalidMessage);
314: continue;
315: }
316: } while (!checkValidWorkspace(shell, url));
317:
318: return url;
319: }
320:
321: /**
322: * Return true if the argument directory is ok to use as a workspace and
323: * false otherwise. A version check will be performed, and a confirmation
324: * box may be displayed on the argument shell if an older version is
325: * detected.
326: *
327: * @return true if the argument URL is ok to use as a workspace and false
328: * otherwise.
329: */
330: private boolean checkValidWorkspace(Shell shell, URL url) {
331: // a null url is not a valid workspace
332: if (url == null) {
333: return false;
334: }
335:
336: String version = readWorkspaceVersion(url);
337:
338: // if the version could not be read, then there is not any existing
339: // workspace data to trample, e.g., perhaps its a new directory that
340: // is just starting to be used as a workspace
341: if (version == null) {
342: return true;
343: }
344:
345: final int ide_version = Integer
346: .parseInt(WORKSPACE_VERSION_VALUE);
347: int workspace_version = Integer.parseInt(version);
348:
349: // equality test is required since any version difference (newer
350: // or older) may result in data being trampled
351: if (workspace_version == ide_version) {
352: return true;
353: }
354:
355: // At this point workspace has been detected to be from a version
356: // other than the current ide version -- find out if the user wants
357: // to use it anyhow.
358: String title = IDEWorkbenchMessages.IDEApplication_versionTitle;
359: String message = NLS.bind(
360: IDEWorkbenchMessages.IDEApplication_versionMessage, url
361: .getFile());
362:
363: MessageBox mbox = new MessageBox(shell, SWT.OK | SWT.CANCEL
364: | SWT.ICON_WARNING | SWT.APPLICATION_MODAL);
365: mbox.setText(title);
366: mbox.setMessage(message);
367: return mbox.open() == SWT.OK;
368: }
369:
370: /**
371: * Look at the argument URL for the workspace's version information. Return
372: * that version if found and null otherwise.
373: */
374: private static String readWorkspaceVersion(URL workspace) {
375: File versionFile = getVersionFile(workspace, false);
376: if (versionFile == null || !versionFile.exists()) {
377: return null;
378: }
379:
380: try {
381: // Although the version file is not spec'ed to be a Java properties
382: // file, it happens to follow the same format currently, so using
383: // Properties to read it is convenient.
384: Properties props = new Properties();
385: FileInputStream is = new FileInputStream(versionFile);
386: try {
387: props.load(is);
388: } finally {
389: is.close();
390: }
391:
392: return props.getProperty(WORKSPACE_VERSION_KEY);
393: } catch (IOException e) {
394: IDEWorkbenchPlugin
395: .log(
396: "Could not read version file", new Status( //$NON-NLS-1$
397: IStatus.ERROR,
398: IDEWorkbenchPlugin.IDE_WORKBENCH,
399: IStatus.ERROR,
400: e.getMessage() == null ? "" : e.getMessage(), //$NON-NLS-1$,
401: e));
402: return null;
403: }
404: }
405:
406: /**
407: * Write the version of the metadata into a known file overwriting any
408: * existing file contents. Writing the version file isn't really crucial,
409: * so the function is silent about failure
410: */
411: private static void writeWorkspaceVersion() {
412: Location instanceLoc = Platform.getInstanceLocation();
413: if (instanceLoc == null || instanceLoc.isReadOnly()) {
414: return;
415: }
416:
417: File versionFile = getVersionFile(instanceLoc.getURL(), true);
418: if (versionFile == null) {
419: return;
420: }
421:
422: OutputStream output = null;
423: try {
424: String versionLine = WORKSPACE_VERSION_KEY + '='
425: + WORKSPACE_VERSION_VALUE;
426:
427: output = new FileOutputStream(versionFile);
428: output.write(versionLine.getBytes("UTF-8")); //$NON-NLS-1$
429: } catch (IOException e) {
430: IDEWorkbenchPlugin.log("Could not write version file", //$NON-NLS-1$
431: StatusUtil.newStatus(IStatus.ERROR, e.getMessage(),
432: e));
433: } finally {
434: try {
435: if (output != null) {
436: output.close();
437: }
438: } catch (IOException e) {
439: // do nothing
440: }
441: }
442: }
443:
444: /**
445: * The version file is stored in the metadata area of the workspace. This
446: * method returns an URL to the file or null if the directory or file does
447: * not exist (and the create parameter is false).
448: *
449: * @param create
450: * If the directory and file does not exist this parameter
451: * controls whether it will be created.
452: * @return An url to the file or null if the version file does not exist or
453: * could not be created.
454: */
455: private static File getVersionFile(URL workspaceUrl, boolean create) {
456: if (workspaceUrl == null) {
457: return null;
458: }
459:
460: try {
461: // make sure the directory exists
462: File metaDir = new File(workspaceUrl.getPath(),
463: METADATA_FOLDER);
464: if (!metaDir.exists() && (!create || !metaDir.mkdir())) {
465: return null;
466: }
467:
468: // make sure the file exists
469: File versionFile = new File(metaDir, VERSION_FILENAME);
470: if (!versionFile.exists()
471: && (!create || !versionFile.createNewFile())) {
472: return null;
473: }
474:
475: return versionFile;
476: } catch (IOException e) {
477: // cannot log because instance area has not been set
478: return null;
479: }
480: }
481:
482: /* (non-Javadoc)
483: * @see org.eclipse.equinox.app.IApplication#stop()
484: */
485: public void stop() {
486: final IWorkbench workbench = PlatformUI.getWorkbench();
487: if (workbench == null)
488: return;
489: final Display display = workbench.getDisplay();
490: display.syncExec(new Runnable() {
491: public void run() {
492: if (!display.isDisposed())
493: workbench.close();
494: }
495: });
496: }
497: }
|