com.mobilesorcery.sdk.core.CoreMoSyncPlugin.java Source code

Java tutorial

Introduction

Here is the source code for com.mobilesorcery.sdk.core.CoreMoSyncPlugin.java

Source

/*  Copyright (C) 2009 Mobile Sorcery AB
    
This program is free software; you can redistribute it and/or modify it
under the terms of the Eclipse Public License v1.0.
    
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the Eclipse Public License v1.0 for
more details.
    
You should have received a copy of the Eclipse Public License v1.0 along
with this program. It is also available at http://www.eclipse.org/legal/epl-v10.html
*/
package com.mobilesorcery.sdk.core;

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.SecureRandom;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.regex.Pattern;

import javax.crypto.spec.PBEKeySpec;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;

import com.mobilesorcery.sdk.core.build.BuildSequence;
import com.mobilesorcery.sdk.core.build.BundleBuildStep;
import com.mobilesorcery.sdk.core.build.CommandLineBuildStep;
import com.mobilesorcery.sdk.core.build.CompileBuildStep;
import com.mobilesorcery.sdk.core.build.CopyBuildResultBuildStep;
import com.mobilesorcery.sdk.core.build.IBuildStepFactory;
import com.mobilesorcery.sdk.core.build.IBuildStepFactoryExtension;
import com.mobilesorcery.sdk.core.build.LinkBuildStep;
import com.mobilesorcery.sdk.core.build.PackBuildStep;
import com.mobilesorcery.sdk.core.build.ResourceBuildStep;
import com.mobilesorcery.sdk.core.launch.AutomaticEmulatorLauncher;
import com.mobilesorcery.sdk.core.launch.IEmulatorLauncher;
import com.mobilesorcery.sdk.core.launch.MoReLauncher;
import com.mobilesorcery.sdk.core.memory.LowMemoryManager;
import com.mobilesorcery.sdk.core.security.IApplicationPermissions;
import com.mobilesorcery.sdk.core.stats.Stats;
import com.mobilesorcery.sdk.internal.ErrorPackager;
import com.mobilesorcery.sdk.internal.HeadlessUpdater;
import com.mobilesorcery.sdk.internal.PID;
import com.mobilesorcery.sdk.internal.PROCESS;
import com.mobilesorcery.sdk.internal.PackagerProxy;
import com.mobilesorcery.sdk.internal.PropertyInitializerProxy;
import com.mobilesorcery.sdk.internal.RebuildListener;
import com.mobilesorcery.sdk.internal.ReindexListener;
import com.mobilesorcery.sdk.internal.SecurePasswordProvider;
import com.mobilesorcery.sdk.internal.SecureProperties;
import com.mobilesorcery.sdk.internal.dependencies.DependencyManager;
import com.mobilesorcery.sdk.internal.launch.EmulatorLauncherProxy;
import com.mobilesorcery.sdk.internal.security.ApplicationPermissions;
import com.mobilesorcery.sdk.lib.JNALibInitializer;
import com.mobilesorcery.sdk.profiles.IProfile;
import com.mobilesorcery.sdk.profiles.filter.DeviceFilterFactoryProxy;
import com.mobilesorcery.sdk.profiles.filter.IDeviceFilterFactory;
import com.mobilesorcery.sdk.profiles.filter.elementfactories.ConstantFilterFactory;
import com.mobilesorcery.sdk.profiles.filter.elementfactories.DeviceCapabilitiesFilterFactory;
import com.mobilesorcery.sdk.profiles.filter.elementfactories.FeatureFilterFactory;
import com.mobilesorcery.sdk.profiles.filter.elementfactories.ProfileFilterFactory;
import com.mobilesorcery.sdk.profiles.filter.elementfactories.VendorFilterFactory;

/**
 * The activator class controls the plug-in life cycle
 */
public class CoreMoSyncPlugin extends AbstractUIPlugin implements IPropertyChangeListener, IResourceChangeListener {

    // The plug-in ID
    public static final String PLUGIN_ID = "com.mobilesorcery.sdk.core";

    /**
     * A non-UI console id.
     */
    public static final String LOG_CONSOLE_NAME = "@@log";

    private static final String WORKSPACE_TOKEN_PREF = PLUGIN_ID + ".w.s.token";

    private static final String PREFERRED_LAUNCER_PREF_PREFIX = PLUGIN_ID + "preferred.launcher.";

    // Days since Oct 15, 1528 until Jan 1, 1970
    private static final long DCE_OFFSET = 141427 * 86400;

    // The shared instance
    private static CoreMoSyncPlugin plugin;

    private static LowMemoryManager lowMemoryManager;

    private static ISavePolicy savePolicy;

    private final ArrayList<Pattern> runtimePatterns = new ArrayList<Pattern>();

    private final ArrayList<Pattern> platformPatterns = new ArrayList<Pattern>();

    private final ArrayList<IPackager> packagers = new ArrayList<IPackager>();

    private Map<String, Map<String, IPropertyInitializer>> propertyInitializers = new HashMap<String, Map<String, IPropertyInitializer>>();

    private final HashMap<String, IEmulatorLauncher> launchers = new HashMap<String, IEmulatorLauncher>();

    private DependencyManager<IProject> projectDependencyManager;

    private Properties panicMessages = new Properties();

    private ReindexListener reindexListener;

    private Integer[] sortedPanicErrorCodes;

    private boolean isHeadless = false;

    private HashMap<String, IDeviceFilterFactory> factories;

    private boolean updaterInitialized;

    private IUpdater updater;

    private IProvider<IProcessConsole, String> ideProcessConsoleProvider;

    private EmulatorProcessManager emulatorProcessManager;

    private String[] buildConfigurationTypes;

    private final HashMap<String, Integer> logCounts = new HashMap<String, Integer>();

    private ISecurePropertyOwner secureProperties;

    private final SecurePasswordProvider passwordProvider = new SecurePasswordProvider();

    private String workspaceToken;

    private SecureRandom secureRnd;

    private HashMap<String, IBuildStepFactoryExtension> buildStepExtensions = null;

    private List<String> buildStepFactoryIds;

    private boolean nativeLibsInited = false;

    /**
     * The constructor
     */
    public CoreMoSyncPlugin() {
    }

    @Override
    public void start(BundleContext context) throws Exception {
        super.start(context);
        plugin = this;
        aboutBoxHack();
        initReIndexerListener();
        initRebuildListener();
        initPackagers();
        initDeviceFilterFactories();
        initPanicErrorMessages();
        initPropertyInitializers();
        initGlobalDependencyManager();
        initEmulatorProcessManager();
        installResourceListener();
        initBuildConfigurationTypes();
        initLaunchers();
        initSecureProperties();
        initWorkspaceToken();
        getPreferenceStore().addPropertyChangeListener(this);
        initializeOnSeparateThread();
    }

    private void aboutBoxHack() {
        // The about box makes use of system properties; let's set a few.
        String mosyncVersion = MoSyncTool.getDefault().getVersionInfo(MoSyncTool.BINARY_VERSION);
        String buildDate = MoSyncTool.getDefault().getVersionInfo(MoSyncTool.BUILD_DATE);
        String mainGitHash = MoSyncTool.getDefault().getVersionInfo(MoSyncTool.MOSYNC_GIT_HASH);
        String eclipseGitHash = MoSyncTool.getDefault().getVersionInfo(MoSyncTool.ECLIPSE_GIT_HASH);
        System.setProperty("MOSYNC_VERSION", mosyncVersion);
        System.setProperty("MOSYNC_BUILD_DATE", "Build date: " + buildDate);
        System.setProperty("MOSYNC_MAIN_GIT_HASH", mainGitHash);
        System.setProperty("MOSYNC_ECLIPSE_GIT_HASH", eclipseGitHash);
    }

    private void initStats() {
        Stats.getStats().start();
    }

    private void initSecureProperties() {
        secureProperties = new SecureProperties(new PreferenceStorePropertyOwner(getPreferenceStore()),
                getPasswordProvider(), null);
    }

    private void initLaunchers() {
        // Default and auto always present
        this.launchers.put(AutomaticEmulatorLauncher.ID, new AutomaticEmulatorLauncher());
        this.launchers.put(MoReLauncher.ID, new MoReLauncher());
        IConfigurationElement[] launchers = Platform.getExtensionRegistry()
                .getConfigurationElementsFor(IEmulatorLauncher.EXTENSION_POINT_ID);
        for (int i = 0; i < launchers.length; i++) {
            IConfigurationElement launcher = launchers[i];
            String id = launcher.getAttribute("id");
            this.launchers.put(id, new EmulatorLauncherProxy(launcher));
        }
    }

    private void initBuildConfigurationTypes() {
        IConfigurationElement[] types = Platform.getExtensionRegistry()
                .getConfigurationElementsFor(BuildConfiguration.TYPE_EXTENSION_POINT);
        ArrayList<String> buildConfigurationTypes = new ArrayList<String>();

        // Add defaults
        buildConfigurationTypes.add(IBuildConfiguration.RELEASE_TYPE);
        buildConfigurationTypes.add(IBuildConfiguration.DEBUG_TYPE);

        // Add extensions
        for (int i = 0; i < types.length; i++) {
            String typeId = types[i].getAttribute("id");
            if (typeId != null) {
                buildConfigurationTypes.add(typeId);
            }
        }

        this.buildConfigurationTypes = buildConfigurationTypes.toArray(new String[0]);
    }

    void initializeOnSeparateThread() {
        // I think we should move this to the UI plugin!
        Thread initializerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                initStats();
            }
        }, "Initializer");

        initializerThread.setDaemon(true);
        initializerThread.start();
    }

    /**
      * Returns whether this app is running in headless mode.
      * @return
      */
    public static boolean isHeadless() {
        return plugin.isHeadless;
    }

    /**
     * Sets this app to headless/non-headless mode.
     * Please note that this will trigger a bundle activation,
     * so if you want to make sure headless is set before that
     * use <code>System.setProperty("com.mobilesorcery.headless", true")</code>
     * @param isHeadless
     */
    public static void setHeadless(boolean isHeadless) {
        plugin.isHeadless = isHeadless;
        if (isHeadless) {
            getDefault().getLog().log(new Status(IStatus.INFO, PLUGIN_ID, "Entering headless mode"));
        }
    }

    private void initGlobalDependencyManager() {
        // Currently, all workspaces share this guy -- fixme later.
        this.projectDependencyManager = new DependencyManager<IProject>();
    }

    private void initRebuildListener() {
        MoSyncProject.addGlobalPropertyChangeListener(new RebuildListener());
    }

    @Override
    public void stop(BundleContext context) throws Exception {
        // Must be here, before nulling the plugin
        Stats.getStats().stop();
        plugin = null;
        projectDependencyManager = null;
        disposeUpdater();
        MoSyncProject.removeGlobalPropertyChangeListener(reindexListener);
        deinstallResourceListener();
        super.stop(context);
    }

    private void initPropertyInitializers() {
        IConfigurationElement[] elements = Platform.getExtensionRegistry()
                .getConfigurationElementsFor(IPropertyInitializerDelegate.EXTENSION_POINT);
        propertyInitializers = new HashMap<String, Map<String, IPropertyInitializer>>();

        for (int i = 0; i < elements.length; i++) {
            String context = PropertyInitializerProxy.getContext(elements[i]);
            String prefix = PropertyInitializerProxy.getPrefix(elements[i]);

            if (context != null && prefix != null) {
                Map<String, IPropertyInitializer> prefixMap = propertyInitializers.get(context);
                if (prefixMap == null) {
                    prefixMap = new HashMap<String, IPropertyInitializer>();
                    propertyInitializers.put(context, prefixMap);
                }

                prefixMap.put(prefix, new PropertyInitializerProxy(elements[i]));
            }
        }
    }

    /**
     * <p>
     * From the registered <code>IPropertyInitializerDelegate</code>s, returns
     * the default value for <code>key</code>, where <code>key</code> has the
     * format <code>prefix:subkey</code>.
     * </p>
     * <p>
     * <code>IPropertyInitializerDelegate</code>s are always registered with
     * context and prefix, which are used for lookup.
     * </p>
     * <p>
     * The context is the same as is returned from the <code>getContext()</code>
     * method in <code>IPropertyOwner</code>.
     *
     * @param owner
     * @param key
     * @return May return <code>null</code>
     */
    public String getDefaultValue(IPropertyOwner owner, String key) {
        Map<String, IPropertyInitializer> prefixMap = propertyInitializers.get(owner.getContext());
        if (prefixMap != null) {
            String[] prefixAndSubkey = key.split(":", 2);
            if (prefixAndSubkey.length == 2) {
                IPropertyInitializer initializer = prefixMap.get(prefixAndSubkey[0]);
                if (initializer != null) {
                    return initializer.getDefaultValue(owner, key);
                }
            }
        }

        return null;
    }

    private void initReIndexerListener() {
        reindexListener = new ReindexListener();
        MoSyncProject.addGlobalPropertyChangeListener(new ReindexListener());
    }

    private void initEmulatorProcessManager() {
        this.emulatorProcessManager = new EmulatorProcessManager();
    }

    private void initPanicErrorMessages() {
        try {
            panicMessages = new Properties();

            InputStream messagesStream = new FileInputStream(
                    MoSyncTool.getDefault().getMoSyncHome().append("eclipse/paniccodes.properties").toFile());

            try {
                panicMessages.load(messagesStream);
            } finally {
                Util.safeClose(messagesStream);
            }
        } catch (Exception e) {
            // Just ignore.
            getLog().log(new Status(IStatus.WARNING, PLUGIN_ID, "Could not initialize panic messages", e));
        }

        TreeSet<Integer> result = new TreeSet<Integer>();
        for (Enumeration errorCodes = panicMessages.keys(); errorCodes.hasMoreElements();) {
            try {
                String errorCode = (String) errorCodes.nextElement();
                int errorCodeValue = Integer.parseInt(errorCode);
                result.add(errorCodeValue);
            } catch (Exception e) {
                // Just ignore.
            }
        }

        sortedPanicErrorCodes = result.toArray(new Integer[result.size()]);
    }

    /**
     * Returns the shared instance
     *
     * @return the shared instance
     */
    public static CoreMoSyncPlugin getDefault() {
        return plugin;
    }

    /**
     * Returns an image descriptor for the image file at the given plug-in
     * relative path
     *
     * @param path
     *            the path
     * @return the image descriptor
     */
    public static ImageDescriptor getImageDescriptor(String path) {
        return imageDescriptorFromPlugin(PLUGIN_ID, path);
    }

    public void log(Throwable e) {
        getLog().log(new Status(IStatus.ERROR, PLUGIN_ID, e.getMessage(), e));
    }

    private synchronized void initNativeLibs() {
        if (nativeLibsInited) {
            return;
        }
        nativeLibsInited = true;
        try {
            JNALibInitializer.init(this.getBundle(), "libpipe");
            @SuppressWarnings("unused")
            PROCESS dummy = PROCESS.INSTANCE; // Just to execute the .clinit.

            JNALibInitializer.init(this.getBundle(), "libpid2");

            if (isDebugging()) {
                trace("Process id: " + getPid());
            }
        } catch (Throwable t) {
            log(t);
            t.printStackTrace();
        }
    }

    private void initPackagers() {
        IConfigurationElement[] elements = Platform.getExtensionRegistry()
                .getConfigurationElementsFor(IPackager.EXTENSION_POINT);
        for (int i = 0; i < elements.length; i++) {
            try {
                String runtime = elements[i].getAttribute(PackagerProxy.RUNTIME_PATTERN);
                String platform = elements[i].getAttribute(PackagerProxy.PLATFORM_PATTERN);
                Pattern runtimePattern = runtime == null ? null : Pattern.compile(runtime);
                Pattern platformPattern = platform == null ? null : Pattern.compile(platform);
                runtimePatterns.add(runtimePattern);
                platformPatterns.add(platformPattern);
                packagers.add(new PackagerProxy(elements[i]));
            } catch (Exception e) {
                CoreMoSyncPlugin.getDefault().log(e);
            }
        }
    }

    /**
     * Returns the packager for a specific project/platform.
     * @param project The profile type
     * @param profile The profile to package for
     * @return A non-null packager. If no packager is found,
     * a default <code>ErrorPackager</code> is returned
     * @see ErrorPackager
     */
    public IPackager getPackager(int profileType, IProfile profile) {
        String runtime = profile.getRuntime();
        String platform = profile.getVendor().getName();
        IPackager packager = null;
        if (profileType == MoSyncTool.DEFAULT_PROFILE_TYPE) {
            packager = matchPackager(platformPatterns, platform);
        }
        if (packager == null) {
            packager = matchPackager(runtimePatterns, runtime);
        }
        if (packager == null) {
            packager = ErrorPackager.getDefault();
        }
        return packager;
    }

    private IPackager matchPackager(List<Pattern> patterns, String matchingCriteria) {
        for (int i = 0; i < patterns.size(); i++) {
            Pattern pattern = patterns.get(i);
            if (pattern != null && pattern.matcher(matchingCriteria).matches()) {
                return packagers.get(i);
            }
        }
        return null;
    }

    /**
     * Returns an (unmodifiable) list of all available packagers
     * @return
     */
    public List<IPackager> getPackagers() {
        return Collections.unmodifiableList(packagers);
    }

    /**
     * Returns a packager with a specific id.
     * @param id
     * @return
     */
    public IPackager getPackagerById(String id) {
        for (IPackager packager : packagers) {
            if (Util.equals(packager.getId(), id)) {
                return packager;
            }
        }
        return null;
    }

    /**
      * Returns a sorted list of all panic error codes.
      * @return
      */
    public Integer[] getAllPanicErrorCodes() {
        return sortedPanicErrorCodes;
    }

    /**
     * Returns the panic message corresponding to <code>errcode</code>
     * @param errcode
     * @return
     */
    public String getPanicMessage(int errcode) {
        return panicMessages.getProperty(Integer.toString(errcode));
    }

    /**
     * @deprecated Do we really need this? It is never used outside
     * the builder + recalculated every time...
     * @return
     */
    @Deprecated
    public DependencyManager<IProject> getProjectDependencyManager() {
        return getProjectDependencyManager(ResourcesPlugin.getWorkspace());
    }

    /**
     * @deprecated Do we really need this? It is never used outside
     * the builder + recalculated every time...
     * @param ws
     * @return
     */
    @Deprecated
    public DependencyManager<IProject> getProjectDependencyManager(IWorkspace ws) {
        return projectDependencyManager;
    }

    /**
     * Returns the Eclipse OS Process ID.
     * @return
     */
    public String getPid() {
        initNativeLibs();
        return "" + PID.INSTANCE.pid();
    }

    public IProcessUtil getProcessUtil() {
        initNativeLibs();
        return PROCESS.INSTANCE;
    }

    /**
     * <p>Outputs a trace message.</p>
     * <p>Please use this pattern:
     * <blockquote><code>
     *     if (CoreMoSyncPlugin.getDefault().isDebugging()) {
     *         trace("A trace message");
     *     }
     * </code></blockquote>
     * </p>
     * <p>Long messages will be truncated.</p>
     * @param msg
     */
    public static void trace(Object msg) {
        System.out.println(Util.truncate("" + msg, null, 1024));
    }

    /**
     * <p>Outputs a trace message.</p>
     * <p>The arguments match those of <code>MessageFormat.format</code>.</p>
     * @see {@link CoreMoSyncPlugin#trace(Object)};
     */
    public static void trace(String msg, Object... args) {
        trace(MessageFormat.format(msg, args));
    }

    private void initDeviceFilterFactories() {
        factories = new HashMap<String, IDeviceFilterFactory>();
        // We'll just add some of them explicitly
        factories.put(ConstantFilterFactory.ID, new ConstantFilterFactory());
        factories.put(VendorFilterFactory.ID, new VendorFilterFactory());
        factories.put(FeatureFilterFactory.ID, new FeatureFilterFactory());
        factories.put(ProfileFilterFactory.ID, new ProfileFilterFactory());
        factories.put(DeviceCapabilitiesFilterFactory.ID, new DeviceCapabilitiesFilterFactory());

        IConfigurationElement[] factoryCEs = Platform.getExtensionRegistry()
                .getConfigurationElementsFor(PLUGIN_ID + ".filter.factories");
        for (int i = 0; i < factoryCEs.length; i++) {
            IConfigurationElement factoryCE = factoryCEs[i];
            String id = factoryCE.getAttribute("id");
            DeviceFilterFactoryProxy factory = new DeviceFilterFactoryProxy(factoryCE);
            registerDeviceFilterFactory(id, factory);
        }
    }

    private void registerDeviceFilterFactory(String id, IDeviceFilterFactory factory) {
        if (factories.containsKey(id)) {
            throw new IllegalStateException("Id already used");
        }
        factories.put(id, factory);
    }

    private void installResourceListener() {
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this,
                IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.PRE_BUILD | IResourceChangeEvent.PRE_CLOSE
                        | IResourceChangeEvent.POST_CHANGE);
    }

    private void deinstallResourceListener() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

    /**
     * <p>Returns an <code>IDeviceFilterFactory</code>.</p>
     * <p>Examples of <code>IDeviceFilterFactories</code> are
     * <code>ConstantFilterFactory</code> and <code>VenderFilterFactory</code>.
     * @param factoryId
     * @return
     */
    public IDeviceFilterFactory getDeviceFilterFactory(String factoryId) {
        if (factoryId == null) {
            return null;
        }

        // Kind of an IElementFactory, but without the UI deps.
        return factories.get(factoryId);
    }

    /**
     * Creates an {@link IProcessConsole} to be used for output of command line tools,
     * build plugins, etc.
     * @param consoleName
     * @return An {@link IProcessConsole}. If running in headless mode or if the
     * {@code consoleName} is set to {@link #LOG_CONSOLE_NAME}, a non-UI console is
     * returned.
     */
    public IProcessConsole createConsole(String consoleName) {
        if (isHeadless || LOG_CONSOLE_NAME.equals(consoleName)) {
            return new LogProcessConsole(consoleName);
        } else {
            return ideProcessConsoleProvider == null ? new LogProcessConsole(consoleName)
                    : ideProcessConsoleProvider.get(consoleName);
        }
    }

    /**
     * For a given launcher id, return the corresponding {@link IEmulatorLauncher}.
     * @param launcherId
     * @return
     */
    public IEmulatorLauncher getEmulatorLauncher(String launcherId) {
        return launchers.get(launcherId);
    }

    /**
     * Returns the preferred launcher for a given packager.
     * @param packager
     * @return {@code null} if no preferred launcher
     */
    public IEmulatorLauncher getPreferredLauncher(String packager) {
        IPreferenceStore store = getPreferenceStore();
        String launcherId = store.getString(PREFERRED_LAUNCER_PREF_PREFIX + packager);
        return getEmulatorLauncher(launcherId);
    }

    /**
     * Sets the preferred launcher for a given packager.
     * @param packager
     * @param launcherId {@code null} if no preferred launcher should be set
     */
    public void setPreferredLauncher(String packager, String launcherId) {
        IPreferenceStore store = getPreferenceStore();
        String pref = PREFERRED_LAUNCER_PREF_PREFIX + packager;
        if (launcherId == null) {
            store.setToDefault(pref);
        } else {
            store.setValue(pref, launcherId);
        }
    }

    public Set<String> getEmulatorLauncherIds() {
        return Collections.unmodifiableSet(launchers.keySet());
    }

    public IBuildStepFactory createBuildStepFactory(String id) {
        // The default ones.
        if (CompileBuildStep.ID.equals(id)) {
            return new CompileBuildStep.Factory();
        } else if (ResourceBuildStep.ID.equals(id)) {
            return new ResourceBuildStep.Factory();
        } else if (LinkBuildStep.ID.equals(id)) {
            return new LinkBuildStep.Factory();
        } else if (PackBuildStep.ID.equals(id)) {
            return new PackBuildStep.Factory();
        } else if (CommandLineBuildStep.ID.equals(id)) {
            return new CommandLineBuildStep.Factory();
        } else if (BundleBuildStep.ID.equals(id)) {
            return new BundleBuildStep.Factory();
        } else if (CopyBuildResultBuildStep.ID.equals(id)) {
            return new CopyBuildResultBuildStep.Factory();
        }

        IBuildStepFactoryExtension extension = getBuildStepFactoryExtension(id);
        if (extension != null) {
            return extension.createFactory();
        }
        return null;
    }

    public IBuildStepFactoryExtension getBuildStepFactoryExtension(String id) {
        if (buildStepExtensions == null) {
            buildStepExtensions = new HashMap<String, IBuildStepFactoryExtension>();
            IExtensionRegistry registry = Platform.getExtensionRegistry();
            IConfigurationElement[] elements = registry
                    .getConfigurationElementsFor(IBuildStepFactoryExtension.EXTENSION_ID);
            for (IConfigurationElement element : elements) {
                try {
                    String extId = element.getAttribute("id");
                    IBuildStepFactoryExtension ext = (IBuildStepFactoryExtension) element
                            .createExecutableExtension("implementation");
                    buildStepExtensions.put(extId, ext);
                } catch (CoreException e) {
                    CoreMoSyncPlugin.getDefault().log(e);
                }
            }
        }
        return buildStepExtensions.get(id);
    }

    /**
     * Returns a mutable list of all build step factories.
     * @return
     */
    public List<String> getBuildStepFactories() {
        if (buildStepFactoryIds == null) {
            ArrayList<String> result = new ArrayList<String>();
            result.add(CompileBuildStep.ID);
            result.add(ResourceBuildStep.ID);
            result.add(LinkBuildStep.ID);
            result.add(PackBuildStep.ID);
            result.add(CommandLineBuildStep.ID);
            result.add(BundleBuildStep.ID);
            result.add(CopyBuildResultBuildStep.ID);
            getBuildStepFactoryExtension(""); // Just to init.
            result.addAll(buildStepExtensions.keySet());
            buildStepFactoryIds = Collections.unmodifiableList(result);
        }
        return buildStepFactoryIds;
    }

    public IUpdater getUpdater() {
        if (isHeadless) {
            if (isDebugging()) {
                trace("Headless build: update checks suppressed");
            }
            return HeadlessUpdater.getInstance();
        } else if (!updaterInitialized) {
            IExtensionRegistry registry = Platform.getExtensionRegistry();
            IConfigurationElement[] elements = registry
                    .getConfigurationElementsFor("com.mobilesorcery.sdk.updater");
            if (elements.length > 0) {
                try {
                    updater = (IUpdater) elements[0].createExecutableExtension("implementation");
                } catch (CoreException e) {
                    getLog().log(e.getStatus());
                }
            }

            updaterInitialized = true;
        }

        return updater;
    }

    private void disposeUpdater() {
        if (updater != null) {
            updater.dispose();
        }
    }

    public void checkAutoUpdate() {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                internalCheckAutoUpdate();
            }
        });
        t.setName("Auto update");
        t.start();
    }

    private synchronized void internalCheckAutoUpdate() {
        if (isHeadless) {
            return;
        }

        String[] args = Platform.getApplicationArgs();
        if (suppressUpdating(args)) {
            return;
        }

        IUpdater updater = getUpdater();
        if (updater != null) {
            updater.update(false);
        }
    }

    private boolean suppressUpdating(String[] args) {
        for (int i = 0; i < args.length; i++) {
            if ("-suppress-updates".equals(args[i])) {
                return true;
            }
        }

        return false;
    }

    /**
     * <p>Returns the (single) emulator process manager</p>
     * @return
     */
    public EmulatorProcessManager getEmulatorProcessManager() {
        return emulatorProcessManager;
    }

    /**
     * INTERNAL: Clients should not call this method.
     */
    public void setIDEProcessConsoleProvider(IProvider<IProcessConsole, String> ideProcessConsoleProvider) {
        // I'm lazy - Instead of extension points...
        this.ideProcessConsoleProvider = ideProcessConsoleProvider;
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        if (MoSyncTool.MOSYNC_HOME_PREF.equals(event.getProperty())
                || MoSyncTool.MO_SYNC_HOME_FROM_ENV_PREF.equals(event.getProperty())) {
            initPanicErrorMessages();
        }

    }

    /**
     * Tries to derive a mosync project from whatever object is passed
     * as the <code>receiver</code>; this method will accept <code>List</code>s,
     * <code>IAdaptable</code>s, <code>IResource</code>s, as well as <code>IStructuredSelection</code>s
     * and then if the project
     * associated with these is compatible with a MoSyncProject, return that project.
     */
    // Should it be here?
    public MoSyncProject extractProject(Object receiver) {
        if (receiver instanceof IStructuredSelection) {
            return extractProject(((IStructuredSelection) receiver).toList());
        }

        if (receiver instanceof IAdaptable) {
            receiver = ((IAdaptable) receiver).getAdapter(IResource.class);
        }

        if (receiver instanceof Collection) {
            if (((Collection) (receiver)).size() == 0) {
                return null;
            }

            return extractProject(((Collection) receiver).iterator().next());
        }

        if (receiver == null) {
            return null;
        }

        if (receiver instanceof IResource) {
            IProject project = ((IResource) receiver).getProject();

            try {
                return MoSyncNature.isCompatible(project) ? MoSyncProject.create(project) : null;
            } catch (CoreException e) {
                return null;
            }
        }

        return null;
    }

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        int eventType = event.getType();
        if (eventType == IResourceChangeEvent.PRE_DELETE || eventType == IResourceChangeEvent.PRE_CLOSE) {
            IResource resource = event.getResource();
            IProject project = (resource != null && resource.getType() == IResource.PROJECT) ? (IProject) resource
                    : null;

            MoSyncProject mosyncProject = MoSyncProject.create(project);
            if (mosyncProject != null) {
                // So we do not keep any old references to this project
                mosyncProject.dispose();
            }
        } else if (eventType == IResourceChangeEvent.PRE_BUILD
                && event.getBuildKind() != IncrementalProjectBuilder.CLEAN_BUILD
                && event.getBuildKind() != IncrementalProjectBuilder.AUTO_BUILD) {
            Object source = event.getSource();
            ArrayList<IResource> mosyncProjects = new ArrayList<IResource>();
            IProject[] projects = null;
            if (source instanceof IWorkspace) {
                projects = ((IWorkspace) source).getRoot().getProjects();
            } else if (source instanceof IProject) {
                projects = new IProject[] { (IProject) source };
            }

            for (int i = 0; projects != null && i < projects.length; i++) {
                IProject project = projects[i];
                try {
                    if (MoSyncNature.isCompatible(project)) {
                        mosyncProjects.add(projects[i]);
                    }
                } catch (CoreException e) {
                    CoreMoSyncPlugin.getDefault().log(e);
                }
            }

            IJobManager jm = Job.getJobManager();
            Job currentJob = jm.currentJob();
            if (!MoSyncBuilder.saveAllEditors(mosyncProjects)) {
                // If this thread is a build job, then cancel.
                // TODO: Could this somewhere or some day cease to work!
                if (currentJob != null) {
                    currentJob.cancel();
                }
            }
        } else {
            Collection<MoSyncProject> projects = extractProjectsToReinit(event);
            for (MoSyncProject project : projects) {
                project.reinit(true);
            }
        }
    }

    public Collection<MoSyncProject> extractProjectsToReinit(IResourceChangeEvent event) {
        final HashSet<MoSyncProject> result = new HashSet<MoSyncProject>();
        boolean isContentChange = event.getDelta() != null
                && (event.getDelta().getFlags() & IResourceDelta.CONTENT) != 0;
        boolean isFileResource = event.getResource() != null && event.getResource().getType() == IResource.FILE;
        if (event.getType() == IResourceChangeEvent.POST_CHANGE && isContentChange && isFileResource) {
            try {
                event.getDelta().accept(new IResourceDeltaVisitor() {
                    @Override
                    public boolean visit(IResourceDelta delta) throws CoreException {
                        IResource resource = delta.getResource();
                        if (resource != null) {
                            String name = resource.getName();
                            if (MoSyncProject.MOSYNC_PROJECT_META_DATA_FILENAME.equals(name)
                                    || MoSyncProject.MOSYNC_PROJECT_META_DATA_FILENAME.equals(name)) {
                                MoSyncProject mosyncProject = MoSyncProject.create(resource.getProject());
                                if (mosyncProject != null) {
                                    result.add(mosyncProject);
                                    // And for good measure...
                                    BuildSequence.clearCache(mosyncProject);
                                }
                            }
                        }
                        return true;
                    }
                });
            } catch (CoreException e) {
                CoreMoSyncPlugin.getDefault().log(e);
            }
        }
        return result;
    }

    public String[] getBuildConfigurationTypes() {
        return buildConfigurationTypes;
    }

    /**
     * <p>Returns a working copy of an <code>IApplicationPermissions</code>
     * with default permissions.</p>
     * @param project
     * @return
     */
    public IApplicationPermissions getDefaultPermissions(MoSyncProject project) {
        return ApplicationPermissions.getDefaultPermissions(project);
    }

    /**
     * Logs a specified method ONCE
     * @param e
     * @param token Used to distinguish the source of log messages
     */
    public void logOnce(Exception e, String token) {
        Integer logCount = logCounts.get(token);
        if (logCount == null) {
            logCount = 0;
        }

        if (logCount < 1) {
            log(e);
        }

        logCount++;
        logCounts.put(token, logCount);
    }

    public ISecurePropertyOwner getSecureProperties() {
        return secureProperties;
    }

    public IProvider<PBEKeySpec, String> getPasswordProvider() {
        return passwordProvider;
    }

    public boolean usesEclipseSecureStorage() {
        return passwordProvider.usesEclipseSecureStorage();
    }

    public void doUseEclipseSecureStorage(boolean useEclipseSecureStorage) throws CoreException {
        passwordProvider.doUseEclipseSecureStorage(useEclipseSecureStorage);
    }

    /**
     * Returns a 'workspace' token, that has a very large probability
     * of being unique per workspace. (It is randomly generated).
     * It is designed be used in filenames for workspace specific file lookup.
     * @return
     */
    public String getWorkspaceToken() {
        return workspaceToken;
    }

    private void initWorkspaceToken() {
        if (getPreferenceStore().isDefault(WORKSPACE_TOKEN_PREF)) {
            byte[] random = new byte[6];
            new Random(System.currentTimeMillis()).nextBytes(random);
            String workspaceToken = Util.toBase16(random);
            getPreferenceStore().setValue(WORKSPACE_TOKEN_PREF, workspaceToken);
        }
        this.workspaceToken = getPreferenceStore().getString(WORKSPACE_TOKEN_PREF);
    }

    /**
     * Generates a time-based 128-bit UUID.
     * @return
     */
    public UUID generateUUID() {
        if (secureRnd == null) {
            secureRnd = new SecureRandom();
        }
        // Generate a 48-bit random node id; set the multicast bit to 1
        long nodeId = (secureRnd.nextLong() & 0xffffffffffffL) | 0x010000000000L;
        long clockSeq = secureRnd.nextInt(0x4fff);
        long clockSeq_lo = clockSeq & 0xff;
        long clockSeq_hi_and_res = ((clockSeq >> 16) & 0x4f) | 0x80;
        long lsb = (clockSeq_hi_and_res << 56) | (clockSeq_lo << 48) | nodeId;

        long utcTimestamp = System.currentTimeMillis();
        long timestamp = DCE_OFFSET + 10000 * utcTimestamp;
        long timestamp_lo = timestamp & 0xffffffff;
        long timestamp_mid = (timestamp >> 32) & 0xffff;
        long timestamp_hi_and_version = ((timestamp >> 48) & 0x0fff) | 0x1000;
        long msb = (timestamp_lo << 32) | (timestamp_mid << 16) | timestamp_hi_and_version;
        return new UUID(msb, lsb);
    }

    /**
     * Returns the 'low memory manager' for notifications on low memory
     * @return
     */
    public static synchronized LowMemoryManager getLowMemoryManager() {
        if (lowMemoryManager == null) {
            lowMemoryManager = new LowMemoryManager(0.85);
        }
        return lowMemoryManager;
    }

    public static ISavePolicy getSavePolicy() {
        if (savePolicy == null) {
            IConfigurationElement[] elements = Platform.getExtensionRegistry()
                    .getConfigurationElementsFor(ISavePolicy.EXTENSION_POINT);
            try {
                savePolicy = ISavePolicy.NULL;
                if (!isHeadless() && elements.length == 1) {
                    savePolicy = (ISavePolicy) elements[0].createExecutableExtension("implementation");
                }
            } catch (Exception e) {
                // Ignore
            }
        }
        return savePolicy;
    }

}