au.gov.ga.earthsci.eclipse.extras.ide.ChooseWorkspaceData.java Source code

Java tutorial

Introduction

Here is the source code for au.gov.ga.earthsci.eclipse.extras.ide.ChooseWorkspaceData.java

Source

/*******************************************************************************
 * Copyright (c) 2004, 2013 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.
 *******************************************************************************/
package au.gov.ga.earthsci.eclipse.extras.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.preferences.ScopedPreferenceStore;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;

import au.gov.ga.earthsci.eclipse.extras.Activator;

/**
 * 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 = 5;

    /**
     * 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 config 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
    * confi area preferences store for persistence. This version is the same as
    * the previous version except it uses a \n character to seperate 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 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;
    }

    /**
     * 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;
    }

    /**
     * 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(Activator.PLUGIN_ID);

        // 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 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, Activator.PLUGIN_ID);

        // 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);

        return true;
    }

    /**
     * The the list of recent workspaces must be stored as a string in the preference node.
     */
    private static String encodeStoredWorkspacePaths(String[] recent) {
        StringBuffer buff = new StringBuffer();

        String path = null;
        for (int i = 0; i < recent.length; ++i) {
            if (recent[i] == 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 = recent[i];
            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 dont 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;
    }

    /**
    * Return 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.toURI().toURL(), PERS_FILENAME);
            File persFile = new File(url.getFile());
            if (!persFile.exists() && (!create || !persFile.createNewFile())) {
                return null;
            }

            return persFile.toURI().toURL();
        } catch (IOException e) {
            // cannot log because instance area has not been set
            return null;
        }
    }
}