| /******************************************************************************* |
| * Copyright (c) 2004, 2015 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Eric Rizzo - added API to set the list of recent workspaces. |
| * Jan-Ove Weichel <ovi.weichel@gmail.com> - Bug 463039 |
| * Jan-Ove Weichel <janove.weichel@vogella.com> - Bug 411578 |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.ide; |
| |
| import java.io.File; |
| import java.io.FileReader; |
| import java.io.IOException; |
| import java.io.Reader; |
| import java.net.URL; |
| import java.util.StringTokenizer; |
| |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.preferences.ConfigurationScope; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.osgi.service.datalocation.Location; |
| import org.eclipse.ui.IMemento; |
| import org.eclipse.ui.WorkbenchException; |
| import org.eclipse.ui.XMLMemento; |
| import org.eclipse.ui.ide.IDE; |
| import org.eclipse.ui.preferences.ScopedPreferenceStore; |
| import org.osgi.service.prefs.BackingStoreException; |
| import org.osgi.service.prefs.Preferences; |
| |
| /** |
| * This class stores the information behind the "Launch Workspace" dialog. The |
| * class is able to read and write itself to a well known configuration file. |
| */ |
| public class ChooseWorkspaceData { |
| /** |
| * The default max length of the recent workspace mru list. |
| */ |
| private static final int RECENT_MAX_LENGTH = 10; |
| |
| /** |
| * The directory within the config area that will be used for the |
| * receiver's persisted data. |
| */ |
| private static final String PERS_FOLDER = "org.eclipse.ui.ide"; //$NON-NLS-1$ |
| |
| /** |
| * The name of the file within the config area that will be used for |
| * the recever's persisted data. |
| * @see PERS_FOLDER |
| */ |
| private static final String PERS_FILENAME = "recentWorkspaces.xml"; //$NON-NLS-1$ |
| |
| /** |
| * In the past a file was used to store persist these values. This file was written |
| * with this value as its protocol identifier. |
| */ |
| private static final int PERS_ENCODING_VERSION = 1; |
| |
| /** |
| * This is the first version of the encode/decode protocol that uses the |
| * configuration area preference store for persistence. The only encoding |
| * done is to convert the recent workspace list into a comma-separated list. |
| */ |
| private static final int PERS_ENCODING_VERSION_CONFIG_PREFS = 2; |
| |
| /** |
| * This is the second version of the encode/decode protocol that uses the |
| * configuration area preferences store for persistence. This version is the |
| * same as the previous version except it uses a \n character to separate |
| * the path entries instead of commas. (see bug 98467) |
| * |
| * @since 3.3.1 |
| */ |
| private static final int PERS_ENCODING_VERSION_CONFIG_PREFS_NO_COMMAS = 3; |
| |
| private boolean showDialog = true; |
| |
| private boolean showRecentWorkspaces; |
| |
| private String initialDefault; |
| |
| private String selection; |
| |
| private String[] recentWorkspaces; |
| |
| // xml tags |
| private static interface XML { |
| public static final String PROTOCOL = "protocol"; //$NON-NLS-1$ |
| |
| public static final String VERSION = "version"; //$NON-NLS-1$ |
| |
| public static final String ALWAYS_ASK = "alwaysAsk"; //$NON-NLS-1$ |
| |
| public static final String SHOW_DIALOG = "showDialog"; //$NON-NLS-1$ |
| |
| public static final String WORKSPACE = "workspace"; //$NON-NLS-1$ |
| |
| public static final String RECENT_WORKSPACES = "recentWorkspaces"; //$NON-NLS-1$ |
| |
| public static final String MAX_LENGTH = "maxLength"; //$NON-NLS-1$ |
| |
| public static final String PATH = "path"; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Creates a new instance, loading persistent data if its found. |
| */ |
| public ChooseWorkspaceData(String initialDefault) { |
| readPersistedData(); |
| setInitialDefault(initialDefault); |
| } |
| |
| /** |
| * Creates a new instance, loading persistent data if its found. |
| */ |
| public ChooseWorkspaceData(URL instanceUrl) { |
| readPersistedData(); |
| if (instanceUrl != null) { |
| setInitialDefault(new File(instanceUrl.getFile()).toString()); |
| } |
| } |
| |
| /** |
| * Return the folder to be used as a default if no other information |
| * exists. Does not return null. |
| */ |
| public String getInitialDefault() { |
| if (initialDefault == null) { |
| setInitialDefault(System.getProperty("user.dir") //$NON-NLS-1$ |
| + File.separator + "workspace"); //$NON-NLS-1$ |
| } |
| return initialDefault; |
| } |
| |
| /** |
| * Set this data's initialDefault parameter to a properly formatted version |
| * of the argument directory string. The proper format is to the platform |
| * appropriate separator character without meaningless leading or trailing |
| * separator characters. |
| */ |
| private void setInitialDefault(String dir) { |
| if (dir == null || dir.length() <= 0) { |
| initialDefault = null; |
| return; |
| } |
| |
| dir = new Path(dir).toOSString(); |
| while (dir.charAt(dir.length() - 1) == File.separatorChar) { |
| dir = dir.substring(0, dir.length() - 1); |
| } |
| initialDefault = dir; |
| } |
| |
| /** |
| * Return the currently selected workspace or null if nothing is selected. |
| */ |
| public String getSelection() { |
| return selection; |
| } |
| |
| /** |
| * Return the currently selected workspace or null if nothing is selected. |
| */ |
| public boolean getShowDialog() { |
| return showDialog; |
| } |
| |
| /** |
| * Returns whether the "Recent Workspaces" should be shown |
| */ |
| public boolean isShowRecentWorkspaces() { |
| return showRecentWorkspaces; |
| } |
| |
| /** |
| * Return an array of recent workspaces sorted with the most recently used at |
| * the start. |
| */ |
| public String[] getRecentWorkspaces() { |
| return recentWorkspaces; |
| } |
| |
| /** |
| * The argument workspace has been selected, update the receiver. Does not |
| * persist the new values. |
| */ |
| public void workspaceSelected(String dir) { |
| // this just stores the selection, it is not inserted and persisted |
| // until the workspace is actually selected |
| selection = dir; |
| } |
| |
| /** |
| * Toggle value of the showDialog persistent setting. |
| */ |
| public void toggleShowDialog() { |
| showDialog = !showDialog; |
| } |
| |
| /** |
| * Set if the "Recent Workspaces" should be shown |
| */ |
| public void setShowRecentWorkspaces(boolean showRecentWorkspaces) { |
| this.showRecentWorkspaces = showRecentWorkspaces; |
| } |
| |
| /** |
| * Sets the list of recent workspaces. |
| */ |
| public void setRecentWorkspaces(String[] workspaces) { |
| if (workspaces == null) { |
| recentWorkspaces = new String[0]; |
| } else { |
| recentWorkspaces = workspaces; |
| } |
| } |
| |
| /** |
| * Update the persistent store. Call this function after the currently |
| * selected value has been found to be ok. |
| */ |
| public void writePersistedData() { |
| // 1. get config pref node |
| Preferences node = ConfigurationScope.INSTANCE.getNode(IDEWorkbenchPlugin.IDE_WORKBENCH); |
| |
| // 2. get value for showDialog |
| node.putBoolean( |
| IDE.Preferences.SHOW_WORKSPACE_SELECTION_DIALOG, |
| showDialog); |
| |
| // 3. use value of numRecent to create proper length array |
| node.putInt(IDE.Preferences.MAX_RECENT_WORKSPACES, |
| recentWorkspaces.length); |
| |
| // move the new selection to the front of the list |
| if (selection != null) { |
| File newFolder = new File(selection); |
| String oldEntry = recentWorkspaces[0]; |
| recentWorkspaces[0] = selection; |
| for (int i = 1; i < recentWorkspaces.length && oldEntry != null; ++i) { |
| File oldFolder = new File (oldEntry); |
| // If selection represents a file location we already have, don't store it |
| if (newFolder.compareTo(oldFolder) == 0){ |
| break; |
| } |
| String tmp = recentWorkspaces[i]; |
| recentWorkspaces[i] = oldEntry; |
| oldEntry = tmp; |
| } |
| } |
| |
| // 4. store values of recent workspaces into array |
| String encodedRecentWorkspaces = encodeStoredWorkspacePaths(recentWorkspaces); |
| node.put(IDE.Preferences.RECENT_WORKSPACES, |
| encodedRecentWorkspaces); |
| |
| // 5. store the protocol version used to encode the list |
| node.putInt(IDE.Preferences.RECENT_WORKSPACES_PROTOCOL, |
| PERS_ENCODING_VERSION_CONFIG_PREFS_NO_COMMAS); |
| |
| // 6. store if the "Recent Workspaces" should be shown |
| node.putBoolean(IDE.Preferences.SHOW_RECENT_WORKSPACES, showRecentWorkspaces); |
| |
| // 7. store the node |
| try { |
| node.flush(); |
| } catch (BackingStoreException e) { |
| // do nothing |
| } |
| } |
| |
| /** |
| * Look for and read data that might have been persisted from some previous |
| * run. Leave the receiver in a default state if no persistent data is |
| * found. |
| * |
| * @return true if a file was successfully read and false otherwise |
| */ |
| private boolean readPersistedData_file() { |
| URL persUrl = null; |
| |
| Location configLoc = Platform.getConfigurationLocation(); |
| if (configLoc != null) { |
| persUrl = getPersistenceUrl(configLoc.getURL(), false); |
| } |
| |
| try { |
| // inside try to get the safe default creation in the finally |
| // clause |
| if (persUrl == null) { |
| return false; |
| } |
| |
| // E.g., |
| // <launchWorkspaceData> |
| // <protocol version="1"/> |
| // <alwaysAsk showDialog="1"/> |
| // <recentWorkspaces maxLength="5"> |
| // <workspace path="C:\eclipse\workspace0"/> |
| // <workspace path="C:\eclipse\workspace1"/> |
| // </recentWorkspaces> |
| // </launchWorkspaceData> |
| |
| Reader reader = new FileReader(persUrl.getFile()); |
| XMLMemento memento = XMLMemento.createReadRoot(reader); |
| if (memento == null || !compatibleFileProtocol(memento)) { |
| return false; |
| } |
| |
| IMemento alwaysAskTag = memento.getChild(XML.ALWAYS_ASK); |
| showDialog = alwaysAskTag == null ? true : alwaysAskTag.getInteger( |
| XML.SHOW_DIALOG).intValue() == 1; |
| |
| IMemento recent = memento.getChild(XML.RECENT_WORKSPACES); |
| if (recent == null) { |
| return false; |
| } |
| |
| Integer maxLength = recent.getInteger(XML.MAX_LENGTH); |
| int max = RECENT_MAX_LENGTH; |
| if (maxLength != null) { |
| max = maxLength.intValue(); |
| } |
| |
| IMemento indices[] = recent.getChildren(XML.WORKSPACE); |
| if (indices == null || indices.length <= 0) { |
| return false; |
| } |
| |
| // if a user has edited maxLength to be shorter than the listed |
| // indices, accept the list (its tougher for them to retype a long |
| // list of paths than to update a max number) |
| max = Math.max(max, indices.length); |
| |
| recentWorkspaces = new String[max]; |
| for (int i = 0; i < indices.length; ++i) { |
| String path = indices[i].getString(XML.PATH); |
| if (path == null) { |
| break; |
| } |
| recentWorkspaces[i] = path; |
| } |
| } catch (IOException e) { |
| // cannot log because instance area has not been set |
| return false; |
| } catch (WorkbenchException e) { |
| // cannot log because instance area has not been set |
| return false; |
| } finally { |
| // create safe default if needed |
| if (recentWorkspaces == null) { |
| recentWorkspaces = new String[RECENT_MAX_LENGTH]; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Return the current (persisted) value of the "showDialog on startup" |
| * preference. Return the global default if the file cannot be accessed. |
| */ |
| public static boolean getShowDialogValue() { |
| // TODO See the long comment in #readPersistedData -- when the |
| // transition time is over this method can be changed to |
| // read the preference directly. |
| |
| ChooseWorkspaceData data = new ChooseWorkspaceData(""); //$NON-NLS-1$ |
| |
| // return either the value in the file or true, which is the global |
| // default |
| return data.readPersistedData() ? data.showDialog : true; |
| } |
| |
| /** |
| * Return the current (persisted) value of the "showDialog on startup" |
| * preference. Return the global default if the file cannot be accessed. |
| */ |
| public static void setShowDialogValue(boolean showDialog) { |
| // TODO See the long comment in #readPersistedData -- when the |
| // transition time is over this method can be changed to |
| // read the preference directly. |
| |
| ChooseWorkspaceData data = new ChooseWorkspaceData(""); //$NON-NLS-1$ |
| |
| // update the value and write the new settings |
| data.showDialog = showDialog; |
| data.writePersistedData(); |
| } |
| |
| /** |
| * Look in the config area preference store for the list of recently used |
| * workspaces. |
| * |
| * NOTE: During the transition phase the file will be checked if no config |
| * preferences are found. |
| * |
| * @return true if the values were successfully retrieved and false |
| * otherwise |
| */ |
| public boolean readPersistedData() { |
| IPreferenceStore store = new ScopedPreferenceStore(ConfigurationScope.INSTANCE, |
| IDEWorkbenchPlugin.IDE_WORKBENCH); |
| |
| // The old way was to store this information in a file, the new is to |
| // use the configuration area preference store. To help users with the |
| // transition, this code always looks for values in the preference |
| // store; they are used if found. If there aren't any related |
| // preferences, then the file method is used instead. This class always |
| // writes to the preference store, so the fall-back should be needed no |
| // more than once per-user, per-configuration. |
| |
| // This code always sets the value of the protocol to a non-zero value |
| // (currently at 2). If the value comes back as the default (0), then |
| // none of the preferences were set, revert to the file method. |
| |
| int protocol = store.getInt(IDE.Preferences.RECENT_WORKSPACES_PROTOCOL); |
| if (protocol == IPreferenceStore.INT_DEFAULT_DEFAULT |
| && readPersistedData_file()) { |
| return true; |
| } |
| |
| // 2. get value for showDialog |
| showDialog = store.getBoolean(IDE.Preferences.SHOW_WORKSPACE_SELECTION_DIALOG); |
| |
| // 3. use value of numRecent to create proper length array |
| int max = store.getInt(IDE.Preferences.MAX_RECENT_WORKSPACES); |
| max = Math.max(max, RECENT_MAX_LENGTH); |
| |
| // 4. load values of recent workspaces into array |
| String workspacePathPref = store.getString(IDE.Preferences.RECENT_WORKSPACES); |
| recentWorkspaces = decodeStoredWorkspacePaths(protocol, max, workspacePathPref); |
| |
| // 5. get value for showRecentWorkspaces |
| showRecentWorkspaces = store.getBoolean(IDE.Preferences.SHOW_RECENT_WORKSPACES); |
| |
| return true; |
| } |
| |
| /** |
| * The the list of recent workspaces must be stored as a string in the preference node. |
| */ |
| private static String encodeStoredWorkspacePaths(String[] recent) { |
| StringBuilder buff = new StringBuilder(); |
| |
| String path = null; |
| for (String recentPath : recent) { |
| if (recentPath == null) { |
| break; |
| } |
| |
| // as of 3.3.1 pump this out using newlines instead of commas |
| if (path != null) { |
| buff.append("\n"); //$NON-NLS-1$ |
| } |
| |
| path = recentPath; |
| buff.append(path); |
| } |
| |
| return buff.toString(); |
| } |
| |
| /** |
| * The the preference for recent workspaces must be converted from the |
| * storage string into an array. |
| */ |
| private static String[] decodeStoredWorkspacePaths(int protocol, int max, String prefValue) { |
| String[] paths = new String[max]; |
| if (prefValue == null || prefValue.length() <= 0) { |
| return paths; |
| } |
| |
| // if we're using the latest version of the protocol use the newline as a |
| // token. Otherwise use the older comma. |
| String tokens = null; |
| switch (protocol) { |
| case PERS_ENCODING_VERSION_CONFIG_PREFS_NO_COMMAS : |
| tokens = "\n"; //$NON-NLS-1$ |
| break; |
| case PERS_ENCODING_VERSION_CONFIG_PREFS : |
| tokens = ","; //$NON-NLS-1$ |
| break; |
| } |
| if (tokens == null) { |
| // Unknown version? corrupt file? we can't log it because we don't |
| // have a workspace yet... |
| return new String[0]; |
| } |
| |
| StringTokenizer tokenizer = new StringTokenizer(prefValue, tokens); |
| for (int i = 0; i < paths.length && tokenizer.hasMoreTokens(); ++i) { |
| paths[i] = tokenizer.nextToken(); |
| } |
| |
| return paths; |
| } |
| |
| /** |
| * Returns true if the protocol used to encode the argument memento is |
| * compatible with the receiver's implementation and false otherwise. |
| */ |
| private static boolean compatibleFileProtocol(IMemento memento) { |
| IMemento protocolMemento = memento.getChild(XML.PROTOCOL); |
| if (protocolMemento == null) { |
| return false; |
| } |
| |
| Integer version = protocolMemento.getInteger(XML.VERSION); |
| return version != null && version.intValue() == PERS_ENCODING_VERSION; |
| } |
| |
| /** |
| * The workspace data is stored in the well known file pointed to by the result |
| * of this method. |
| * @param create If the directory and file does not exist this parameter |
| * controls whether it will be created. |
| * @return An url to the file and null if it does not exist or could not |
| * be created. |
| */ |
| private static URL getPersistenceUrl(URL baseUrl, boolean create) { |
| if (baseUrl == null) { |
| return null; |
| } |
| |
| try { |
| // make sure the directory exists |
| URL url = new URL(baseUrl, PERS_FOLDER); |
| File dir = new File(url.getFile()); |
| if (!dir.exists() && (!create || !dir.mkdir())) { |
| return null; |
| } |
| |
| // make sure the file exists |
| url = new URL(dir.toURL(), PERS_FILENAME); |
| File persFile = new File(url.getFile()); |
| if (!persFile.exists() && (!create || !persFile.createNewFile())) { |
| return null; |
| } |
| |
| return persFile.toURL(); |
| } catch (IOException e) { |
| // cannot log because instance area has not been set |
| return null; |
| } |
| } |
| } |