org.eclipse.ptp.gig.util.GIGUtilities.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ptp.gig.util.GIGUtilities.java

Source

/*******************************************************************************
 * Copyright (c) 2012 Brandon Gibson
 * 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:
 *    Brandon Gibson - initial API and implementation and/or initial documentation
 *******************************************************************************/
package org.eclipse.ptp.gig.util;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.swing.JOptionPane;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.ptp.gig.GIGPlugin;
import org.eclipse.ptp.gig.log.GkleeLog;
import org.eclipse.ptp.gig.log.LogException;
import org.eclipse.ptp.gig.messages.Messages;
import org.eclipse.ptp.gig.preferences.GIGPreferencePage;
import org.eclipse.ptp.gig.views.GIGView;
import org.eclipse.ptp.gig.views.ServerTreeItem;
import org.eclipse.ptp.gig.views.ServerView;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.console.ConsolePlugin;
import org.eclipse.ui.console.IConsole;
import org.eclipse.ui.console.IConsoleManager;
import org.eclipse.ui.console.MessageConsole;
import org.eclipse.ui.console.MessageConsoleStream;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.progress.UIJob;
import org.eclipse.ui.statushandlers.StatusManager;

/*
 * Contains most of the logical and generic utility code of the plug-in
 */
public class GIGUtilities {

    /*
     * Indicates what state the job is in to prevent multiple jobs from being accidentally started.
     */
    public enum JobState {
        None, Running, Canceled
    }

    // The current non-GUI job being run
    private static Job job;
    // lock is needed to protect critical sections
    private static Lock jobsLock = new ReentrantLock();
    // This keeps track of what part of the life of a job we are in
    private static volatile JobState jobState = JobState.None;

    // the port to communicate with the server
    static final int port = 8883;
    /*
     * The socket, outputStream and inputStream are all together. Used for communication to server
     */
    private static Socket socket;
    private static OutputStream socketOutputStream;
    private static InputStream socketInputStream;
    // Used for cancelling jobs
    private static IProgressMonitor progressMonitor;

    /*
     * Builds the paths needed to run klee and gklee based on values from the Preferences
     */
    private static void buildEnvPath(Map<String, String> env) {
        final IPreferenceStore preferenceStore = GIGPlugin.getDefault().getPreferenceStore();
        env.put(GIGPreferencePage.GKLEE_HOME, preferenceStore.getString(GIGPreferencePage.GKLEE_HOME));
        env.put(GIGPreferencePage.FLA_KLEE_HOME_DIR,
                preferenceStore.getString(GIGPreferencePage.FLA_KLEE_HOME_DIR));
        final StringBuilder sBuilder = new StringBuilder(env.get("PATH") + ':'); //$NON-NLS-1$
        sBuilder.append(preferenceStore.getString(GIGPreferencePage.GKLEE_DEBUG_PLUS_ASSERTS_BIN) + ':');
        sBuilder.append(preferenceStore.getString(GIGPreferencePage.LLVM_DEBUG_PLUS_ASSERTS_BIN) + ':');
        sBuilder.append(preferenceStore.getString(GIGPreferencePage.LLVM_GCC_LINUX_BIN) + ':');
        sBuilder.append(preferenceStore.getString(GIGPreferencePage.BIN) + ':');
        sBuilder.append(preferenceStore.getString(GIGPreferencePage.ADDITIONAL_PATH));
        final String path = sBuilder.toString();
        env.put("PATH", path); //$NON-NLS-1$
    }

    /*
     * Gklee leaves a few files and folders when it runs, and we don't need to keep those around. This detects and deletes them.
     */
    private static void cleanUpGklee(IContainer gigFolder) throws CoreException {
        final IResource[] resources = gigFolder.members();
        for (final IResource res : resources) {
            if (res.getName().startsWith("klee-")) { //$NON-NLS-1$
                res.delete(true, progressMonitor);
            }
        }
    }

    /*
     * Closes the connection.
     */
    private static void closeConnection() throws IOException {
        /*
         * the int received should always be 0. This is actually to prevent a race. We ensure that the server is done with the
         * current operation before requesting an additional operation. For example, we might send over a file and ask for updated
         * server file structure information; we want the first command completed before a second command is sent.
         */
        recvInt();
        socketOutputStream.close();
        socketInputStream.close();
        socket.close();
        socketOutputStream = null;
        socketInputStream = null;
        socket = null;
    }

    /*
     * Send the command to delete these files on the server
     */
    public static void deleteRemoteFiles(Object[] objects)
            throws IOException, IncorrectPasswordException, IllegalCommandException {
        initializeConnection(4);

        sendInt(objects.length);
        for (final Object o : objects) {
            final ServerTreeItem item = (ServerTreeItem) o;
            sendString(item.getFullName());
        }

        closeConnection();
    }

    /*
     * Cancels the job
     */
    public static void doCancel() {
        if (progressMonitor != null) {
            progressMonitor.setCanceled(true);
        }
    }

    /*
     * Code from eclipse.org to get a console to print to
     */
    private static MessageConsole findConsole(String name) {
        final ConsolePlugin plugin = ConsolePlugin.getDefault();
        final IConsoleManager conMan = plugin.getConsoleManager();
        final IConsole[] existing = conMan.getConsoles();
        for (final IConsole element : existing) {
            if (name.equals(element.getName())) {
                return (MessageConsole) element;
            }
        }
        // no console found, so create a new one
        final MessageConsole myConsole = new MessageConsole(name, null);
        conMan.addConsoles(new IConsole[] { myConsole });
        return myConsole;
    }

    public static JobState getJobState() {
        return jobState;
    }

    /*
     * Requests the file structure from the server
     */
    public static ServerTreeItem getServerFoldersAndFilesRoot()
            throws IOException, IncorrectPasswordException, IllegalCommandException {
        final ServerTreeItem root = new ServerTreeItem("root"); //$NON-NLS-1$

        initializeConnection(1);
        final int numFolders = recvInt();
        for (int i = 0; i < numFolders; i++) {
            recvFolderInfo(root);
        }
        final int numFiles = recvInt();
        for (int i = 0; i < numFiles; i++) {
            recvFileInfo(root);
        }
        closeConnection();

        return root;
    }

    /*
     * This is a way to try and get the IProject that import and log files should go to.
     */
    public static IProject getTargetProject() throws ProjectNotFoundException {
        final IWorkspace workspace = ResourcesPlugin.getWorkspace();
        final IWorkspaceRoot root = workspace.getRoot();
        final IPreferenceStore preferenceStore = GIGPlugin.getDefault().getPreferenceStore();
        final String projectName = preferenceStore.getString(Messages.TARGET_PROJECT);
        final IProject project = root.getProject(projectName);
        if (!project.exists()) {
            throw new ProjectNotFoundException(projectName);
        }
        return project;
    }

    /*
     * This organizes the objects into a ProjectToRecv object, then sends the request and receives the data into the given project.
     */
    public static void importFoldersAndFiles(IProject project, Object[] objects)
            throws IOException, IncorrectPasswordException, CoreException, IllegalCommandException {
        final ProjectToRecv projectToRecv = new ProjectToRecv();
        for (final Object o : objects) {
            final ServerTreeItem item = (ServerTreeItem) o;
            projectToRecv.add(item, true);
        }
        projectToRecv.sendNamesRecvData(project);
    }

    /*
     * This initializes the connection, opening the socket, output streams, sending the instruction, and handling error codes that
     * may be sent.
     */
    private static void initializeConnection(int instructionType)
            throws IOException, IncorrectPasswordException, IllegalCommandException {
        final IPreferenceStore preferenceStore = GIGPlugin.getDefault().getPreferenceStore();
        socket = new Socket(preferenceStore.getString(Messages.SERVER_NAME), port);
        socketOutputStream = socket.getOutputStream();
        socketInputStream = socket.getInputStream();

        // login
        String username, password;
        username = preferenceStore.getString(GIGPreferencePage.USERNAME);
        password = preferenceStore.getString(GIGPreferencePage.PASSWORD);
        sendString(username);
        sendString(password);
        int i = recvInt();
        if (i == -1) {
            throw new IncorrectPasswordException();
        }
        sendInt(instructionType);
        if (instructionType == 0 || instructionType == 4) {
            i = recvInt();
            if (i != 0) {
                throw new IllegalCommandException();
            }
        }
    }

    /*
     * Jumps to the specified line in the specified file in the main editor
     */
    public static void jumpToLine(IFile file, int line) throws CoreException {
        final IMarker marker = file.createMarker(IMarker.MARKER);
        marker.setAttribute(IMarker.LINE_NUMBER, line);
        final IEditorPart editor = IDE
                .openEditor(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), file, true);
        IDE.gotoMarker(editor, marker);
    }

    /*
     * Makes the specified folder and all of its parent folders similar to java's Folder.mkdirs()
     */
    private static void makeFolder(IFolder iFolder) throws CoreException {
        final IContainer parent = iFolder.getParent();
        if (!parent.exists()) {
            makeFolder((IFolder) parent);
        }
        iFolder.create(true, true, progressMonitor);
    }

    private static void printToConsole(InputStream inputStream) {
        final BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
        final Scanner scan = new Scanner(br);
        final MessageConsole myConsole = findConsole(GIGPlugin.PLUGIN_ID);
        final MessageConsoleStream out = myConsole.newMessageStream();
        while (scan.hasNextLine()) {
            final String line = scan.nextLine();
            out.println(line);
            if (progressMonitor.isCanceled()) {
                throw new OperationCanceledException();
            }
        }
    }

    /*
     * Processes the binary created by klee
     */
    private static IStatus processBinary(IPath binaryPath) throws IOException, CoreException, InterruptedException {
        final IWorkspace workspace = ResourcesPlugin.getWorkspace();
        final IWorkspaceRoot workspaceRoot = workspace.getRoot();
        final IFile binaryFile = workspaceRoot.getFile(binaryPath);

        final IPreferenceStore preferenceStore = GIGPlugin.getDefault().getPreferenceStore();
        final boolean local = preferenceStore.getBoolean(GIGPreferencePage.LOCAL);
        if (!local) {
            try {
                // First send the file over
                final List<IFile> fileList = new ArrayList<IFile>();
                fileList.add(binaryFile);
                sendFoldersAndFiles(new ArrayList<IFolder>(), fileList);

                requestVerification(binaryFile.getProject(), binaryFile.getProjectRelativePath());

                final UIJob job = new UIJob(Messages.RESET_SERVER_VIEW) {

                    @Override
                    public IStatus runInUIThread(IProgressMonitor monitor) {
                        ServerView.getDefault().reset();
                        return Status.OK_STATUS;
                    }

                };
                startJob(job);
                return Status.OK_STATUS;
            } catch (final IncorrectPasswordException ipe) {
                showErrorDialog(Messages.INCORRECT_PASSWORD, Messages.INCORRECT_PASSWORD_MESSAGE);
                StatusManager.getManager()
                        .handle(new Status(IStatus.ERROR, GIGPlugin.PLUGIN_ID, Messages.INCORRECT_PASSWORD, ipe));
            } catch (final IllegalCommandException e) {
                showErrorDialog(Messages.ILLEGAL_COMMAND, Messages.ILLEGAL_COMMAND_MESSAGE);
                StatusManager.getManager()
                        .handle(new Status(IStatus.ERROR, GIGPlugin.PLUGIN_ID, Messages.ILLEGAL_COMMAND, e));
            }
            return Status.CANCEL_STATUS;
        }

        // setup the log file path
        final IPath logPath = binaryPath.removeFileExtension().addFileExtension("log"); //$NON-NLS-1$
        final String binaryOSPath = binaryFile.getLocation().toOSString();
        final String gkleeOSPath = preferenceStore.getString(GIGPreferencePage.BIN) + "/gklee"; //$NON-NLS-1$
        final ProcessBuilder processBuilder = new ProcessBuilder(gkleeOSPath, "-emacs", binaryOSPath); //$NON-NLS-1$
        buildEnvPath(processBuilder.environment());

        final Process process = processBuilder.start();
        final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        final Scanner scan = new Scanner(bufferedReader);
        final StringBuilder stringBuilder = new StringBuilder();
        while (scan.hasNextLine()) {
            final String line = scan.nextLine();
            stringBuilder.append(line);
            stringBuilder.append('\n');
            if (progressMonitor.isCanceled()) {
                throw new OperationCanceledException();
            }
        }
        process.waitFor();
        if (process.exitValue() != 0) {
            showErrorDialog(Messages.RUNTIME_ERROR, Messages.SEE_CONSOLE);
            printToConsole(process.getErrorStream());
            return Status.CANCEL_STATUS;
        }
        final InputStream logStringInputStream = new ByteArrayInputStream(stringBuilder.toString().getBytes());
        final IFile logFile = workspaceRoot.getFile(logPath);
        if (logFile.exists()) {
            logFile.setContents(logStringInputStream, true, false, progressMonitor);
        } else {
            logFile.create(logStringInputStream, true, progressMonitor);
        }
        logStringInputStream.close();

        // refresh eclipse environment to make it aware of the new log file
        final IContainer gigFolder = logFile.getParent();
        gigFolder.refreshLocal(1, progressMonitor);
        // also be sure to get rid of temporary files that gklee created
        cleanUpGklee(gigFolder);
        gigFolder.refreshLocal(1, progressMonitor);

        return processLog(logPath);
    }

    /*
     * Processes a log file generated by gklee
     */
    private static IStatus processLog(IPath logPath) throws IOException, CoreException {
        final IWorkspace workspace = ResourcesPlugin.getWorkspace();
        final IWorkspaceRoot workspaceRoot = workspace.getRoot();
        final IFile logFile = workspaceRoot.getFile(logPath);
        final IProject project = logFile.getProject();
        final InputStream logInputStream = logFile.getContents();
        try {
            final GkleeLog gkleeLog = new GkleeLog(logInputStream, logFile);
            logInputStream.close();
            final UIJob job = new UIJob(Messages.UPDATE_GIG) {
                @Override
                public IStatus runInUIThread(IProgressMonitor monitor) {
                    GIGView.getDefault().update(gkleeLog, project);
                    return Status.OK_STATUS;
                }
            };
            startJob(job);
            return Status.OK_STATUS;
        } catch (final IllegalStateException e) {
            StatusManager.getManager()
                    .handle(new Status(IStatus.ERROR, GIGPlugin.PLUGIN_ID, Messages.PARSE_EXCEPTION, e));
        } catch (final LogException e) {
            showErrorDialog(Messages.EXCEPTION_WHILE_PARSING, Messages.BAD_LOG_FILE);
            StatusManager.getManager()
                    .handle(new Status(IStatus.ERROR, GIGPlugin.PLUGIN_ID, Messages.BAD_LOG_FILE, e));
        } finally {
            logInputStream.close();
        }
        return Status.CANCEL_STATUS;
    }

    /*
     * This will process the source, binary or log file passed to it
     */
    public static IStatus processSource(IPath filePath) throws IOException, CoreException, InterruptedException {
        // enforce that the file is of the right type, and switch to something else if needed
        final String fileExtension = filePath.getFileExtension();
        if (fileExtension.equals("gig")) { //$NON-NLS-1$
            return processBinary(filePath);
        } else if (fileExtension.equals("log")) { //$NON-NLS-1$
            return processLog(filePath);
        } else if (!fileExtension.equals("cu") && !fileExtension.equals("C") && !fileExtension.equals("c")) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            showErrorDialog(Messages.INCORRECT_FILE_EXTENSION, Messages.CHANGE_FILE_EXTENSION);
            return Status.CANCEL_STATUS;
        }

        final IWorkspace workspace = ResourcesPlugin.getWorkspace();
        final IWorkspaceRoot workspaceRoot = workspace.getRoot();

        // If we are doing a remote execution, now is the time to go over to it
        final IFile origFile = workspaceRoot.getFile(filePath);
        final IPreferenceStore preferenceStore = GIGPlugin.getDefault().getPreferenceStore();
        final boolean local = preferenceStore.getBoolean(GIGPreferencePage.LOCAL);
        if (!local) {
            try {
                // First send the file over
                final List<IFile> fileList = new ArrayList<IFile>();
                fileList.add(origFile);

                if (!preferenceStore.getString(Messages.USERNAME)
                        .equals(preferenceStore.getDefaultString(Messages.USERNAME))) {
                    sendFoldersAndFiles(new ArrayList<IFolder>(), fileList);
                }

                requestVerification(origFile.getProject(), origFile.getProjectRelativePath());

                final UIJob job = new UIJob(Messages.RESET_SERVER_VIEW) {

                    @Override
                    public IStatus runInUIThread(IProgressMonitor monitor) {
                        ServerView.getDefault().reset();
                        return Status.OK_STATUS;
                    }

                };
                startJob(job);
                return Status.OK_STATUS;
            } catch (final IncorrectPasswordException ipe) {
                showErrorDialog(Messages.INCORRECT_PASSWORD, Messages.INCORRECT_PASSWORD_MESSAGE);
                StatusManager.getManager()
                        .handle(new Status(IStatus.ERROR, GIGPlugin.PLUGIN_ID, Messages.INCORRECT_PASSWORD, ipe));
            } catch (final IllegalCommandException e) {
                showErrorDialog(Messages.ILLEGAL_COMMAND, Messages.ILLEGAL_COMMAND_MESSAGE);
                StatusManager.getManager()
                        .handle(new Status(IStatus.ERROR, GIGPlugin.PLUGIN_ID, Messages.ILLEGAL_COMMAND, e));
            }
            return Status.CANCEL_STATUS;
        }

        // klee-l++ can't handle the .cu extension, so make a link to it
        IFile currFile;
        final IContainer gigContainer = origFile.getParent();
        if (fileExtension.equals("cu")) { //$NON-NLS-1$
            final IPath newPath = origFile.getProjectRelativePath().removeFileExtension().addFileExtension("C"); //$NON-NLS-1$
            currFile = origFile.getProject().getFile(newPath);
            origFile.getParent().refreshLocal(1, progressMonitor);
            if (currFile.exists()) {
                currFile.delete(true, progressMonitor);
            }
            origFile.copy(currFile.getFullPath(), 0, progressMonitor);
            gigContainer.refreshLocal(1, progressMonitor);
        } else {
            currFile = origFile;
        }
        final IPath binaryPath = filePath.removeFileExtension().addFileExtension("gig"); //$NON-NLS-1$

        // begin building the command line with absolute paths, especially from the preferenceStore
        String sourceOSPath, binaryOSPath;
        final IPath sourceAbsoluteIPath = currFile.getLocation();
        sourceOSPath = sourceAbsoluteIPath.toOSString();
        binaryOSPath = sourceAbsoluteIPath.removeFileExtension().addFileExtension("gig").toOSString(); //$NON-NLS-1$
        final String kleeOSPath = preferenceStore.getString(GIGPreferencePage.BIN) + "/klee-l++"; //$NON-NLS-1$
        final ProcessBuilder processBuilder = new ProcessBuilder(kleeOSPath, sourceOSPath, "-o", binaryOSPath); //$NON-NLS-1$

        // klee depends on a lot of path variables, so set these in the environment
        final Map<String, String> environment = processBuilder.environment();
        buildEnvPath(environment);

        final Process process = processBuilder.start();
        printToConsole(process.getInputStream());
        process.waitFor();

        if (process.exitValue() != 0) {
            showErrorDialog(Messages.COMPILATION_ERROR, Messages.SEE_CONSOLE);
            printToConsole(process.getErrorStream());
            return Status.CANCEL_STATUS;
        }

        // refresh environment so that eclipse is aware of the new binary
        gigContainer.refreshLocal(1, progressMonitor);
        return processBinary(binaryPath);
    }

    /*
     * Receives the filename and adds it to the tree. Assumes connection is already established.
     */
    private static void recvFileInfo(ServerTreeItem root) throws IOException {
        final String name = recvString();
        new ServerTreeItem(name, root, false);
    }

    /*
     * Receives the folder information and recursively its contents. Assumes connection is already established.
     */
    private static void recvFolderInfo(ServerTreeItem root) throws IOException {
        final String name = recvString();
        final ServerTreeItem folder = new ServerTreeItem(name, root, true);
        final int numFolders = recvInt();
        for (int i = 0; i < numFolders; i++) {
            recvFolderInfo(folder);
        }
        final int numFiles = recvInt();
        for (int i = 0; i < numFiles; i++) {
            recvFileInfo(folder);
        }
    }

    /*
     * Receives an integer. Assumes connection is already established.
     */
    protected static int recvInt() throws IOException {
        final int len = 4;
        int off = 0;
        final byte[] buffer = new byte[4];
        while (off < len) {
            off += socketInputStream.read(buffer, off, len - off);
        }
        int ret = 0;
        ret |= (buffer[0] << 24) & 0xff000000;
        ret |= (buffer[1] << 16) & 0x00ff0000;
        ret |= (buffer[2] << 8) & 0x0000ff00;
        ret |= (buffer[3] << 0) & 0x000000ff;
        return ret;
    }

    /*
     * Receives a String. Assumes connection is already established.
     */
    protected static String recvString() throws IOException {
        final int len = recvInt();
        int off = 0;
        final byte[] buffer = new byte[len];
        while (off < len) {
            off += socketInputStream.read(buffer, off, len - off);
        }
        return new String(buffer);
    }

    /*
     * Requests a remote verification of the file from the given project specified in the item.
     * This also receives the log file, processes it, and displays it.
     */
    public static void remoteVerifyFile(IProject project, ServerTreeItem item)
            throws IOException, IncorrectPasswordException, CoreException, IllegalCommandException {
        final String filePathString = item.getFullName();
        final IFile file = project.getFile(filePathString);
        final IPath filePath = file.getProjectRelativePath();

        requestVerification(project, filePath);
    }

    /*
     * Requests remote verification of the file in the project. The filePath needs to be relative to the project.
     * This also receives the log file, processes it, and displays it.
     */
    private static void requestVerification(IProject project, IPath filePath)
            throws IOException, IncorrectPasswordException, CoreException, IllegalCommandException {
        initializeConnection(2);

        sendString(filePath.toString());
        final String logString = recvString();

        final IPath logPath = filePath.removeFileExtension().addFileExtension("log"); //$NON-NLS-1$
        final IFile logFile = project.getFile(logPath);
        final InputStream logInputStream = new ByteArrayInputStream(logString.getBytes());
        if (logFile.exists()) {
            logFile.setContents(logInputStream, true, false, progressMonitor);
        } else {
            final IContainer parentToLog = logFile.getParent();
            if (!parentToLog.exists()) {
                makeFolder((IFolder) (parentToLog));
            }
            logFile.create(logInputStream, true, progressMonitor);
        }

        closeConnection();

        logFile.getProject().refreshLocal(IResource.DEPTH_INFINITE, progressMonitor);

        processLog(logFile.getFullPath());
    }

    /*
     * Sends the file. Assumes connection is already established.
     */
    public static void sendFile(IFile file) throws IOException, CoreException {
        final String filename = file.getName();
        sendString(filename);
        final InputStream is = file.getContents();
        final int len = is.available();
        sendInt(len);
        int off = 0;
        final byte[] buffer = new byte[len];
        while (off < len) {
            final int rec = is.read(buffer, off, len - off);
            socketOutputStream.write(buffer, off, rec);
            off += rec;
        }
        is.close();
    }

    /*
     * This sends the folders and files to the server including their containing parent directories up to but not including the
     * project.
     */
    public static void sendFoldersAndFiles(List<IFolder> folders, List<IFile> files)
            throws CoreException, IOException, IncorrectPasswordException, IllegalCommandException {
        initializeConnection(0);
        final ProjectToSend projectToSend = new ProjectToSend();
        for (final IFolder folder : folders) {
            final FolderToSend folderToSend = new FolderToSend(folder);
            projectToSend.add(folderToSend);
        }
        for (final IFile file : files) {
            projectToSend.add(file);
        }
        projectToSend.send();
        closeConnection();
    }

    /*
     * Sends the int. Assumes connection is already established.
     */
    protected static void sendInt(int i) throws IOException {
        final byte[] buffer = new byte[4];
        buffer[0] = (byte) (i >> 24 & 0xff);
        buffer[1] = (byte) (i >> 16 & 0xff);
        buffer[2] = (byte) (i >> 8 & 0xff);
        buffer[3] = (byte) (i >> 0 & 0xff);
        socketOutputStream.write(buffer);
    }

    /*
     * Sends the names of folders and files and receives the data to create the files. Assumes connection is already established.
     */
    public static void sendNamesRecvData(IContainer container, String name, List<FolderToRecv> folders,
            List<String> files) throws IOException, CoreException {
        sendString(name);
        IFolder folder;
        if (container instanceof IFolder) {
            folder = ((IFolder) container).getFolder(name);
        } else {
            folder = ((IProject) container).getFolder(name);
        }
        if (!folder.exists()) {
            folder.create(true, true, progressMonitor);
        }
        final int numFolders = folders.size();
        sendInt(numFolders);
        for (int i = 0; i < numFolders; i++) {
            folders.get(i).sendNamesRecvData(folder);
        }
        final int numFiles = files.size();
        sendInt(numFiles);
        for (int i = 0; i < numFiles; i++) {
            final String filename = files.get(i);
            sendString(filename);
            final IFile file = folder.getFile(filename);
            final int len = recvInt();
            final byte[] buffer = new byte[len];
            int off = 0;
            while (off < len) {
                off += socketInputStream.read(buffer, off, len - off);
            }
            final InputStream inputStream = new ByteArrayInputStream(buffer);
            if (file.exists()) {
                file.setContents(inputStream, true, true, progressMonitor);
                inputStream.close();
            } else {
                file.create(inputStream, true, progressMonitor);
                inputStream.close();
            }
        }
    }

    /*
     * Sends the names of the folders and files and receives their data in the specified project.
     */
    public static void sendNamesRecvData(IProject project, List<FolderToRecv> folders, List<String> files)
            throws IOException, IncorrectPasswordException, CoreException, IllegalCommandException {
        initializeConnection(3);

        sendInt(folders.size());
        for (final FolderToRecv folder : folders) {
            folder.sendNamesRecvData(project);
        }
        sendInt(files.size());
        for (final String filename : files) {
            sendString(filename);
            final IFile file = project.getFile(filename);
            final int len = recvInt();
            final byte[] buffer = new byte[len];
            int off = 0;
            while (off < len) {
                off += socketInputStream.read(buffer, off, len - off);
            }
            final InputStream inputStream = new ByteArrayInputStream(buffer);
            if (file.exists()) {
                file.setContents(inputStream, true, true, progressMonitor);
                inputStream.close();
            } else {
                file.create(inputStream, true, progressMonitor);
                inputStream.close();
            }
        }

        closeConnection();
        project.refreshLocal(IResource.DEPTH_INFINITE, progressMonitor);
    }

    /*
     * Sends the String. Assumes connection is already established.
     */
    protected static void sendString(String string) throws IOException {
        sendInt(string.length());
        socketOutputStream.write(string.getBytes());
    }

    /*
     * Thread safe way to change the job state.
     */
    public static void setJobState(JobState newState) {
        jobsLock.lock();
        jobState = newState;
        jobsLock.unlock();
    }

    /*
     * This creates a popup that displays an error message. The Eclipse ErrorDialog didn't work in Juno after several attempts.
     */
    public static void showErrorDialog(final String title, final String message) {
        JOptionPane.showMessageDialog(null, message, title, JOptionPane.ERROR_MESSAGE);
    }

    /*
     * Thread safe way to start a job. No job will be started if an other job is running. Be sure to call setJobState(JobState.None)
     * after the job is done, or no more jobs can execute via this method.
     */
    public static void startJob(Job job) {
        jobsLock.lock();
        try {
            if (jobState == JobState.None) {
                GIGUtilities.job = job;
                job.setPriority(Job.LONG);
                progressMonitor = new NullProgressMonitor();
                progressMonitor.setCanceled(false);
                job.schedule();
                jobState = JobState.Running;
            }
        } finally {
            jobsLock.unlock();
        }
    }

    /*
     * Starts a UIJob. No need to attach a progress monitor or anything as canceling occurs in the UI thread anyways. As such, these
     * jobs should be relatively short.
     */
    public static void startJob(UIJob job) {
        job.setPriority(Job.INTERACTIVE);
        job.schedule();
    }

    /*
     * Used to correctly stop the job midway through for both canceling and exiting purposes.
     */
    public static void stopJob() {
        jobsLock.lock();
        if (jobState == JobState.Running) {
            progressMonitor.setCanceled(true);
            jobState = JobState.Canceled;
            jobsLock.unlock();
            int i = 1000;
            /*
             * We are waiting and giving the job time to cleanly exit itself, it will signal us by changing the jobState to
             * None.
             * Or we wait for timeout and kill it with a cancel
             */
            while (jobState == JobState.Canceled && i > 0) {
                try {
                    Thread.sleep(1);
                    i--;
                } catch (final InterruptedException e) {
                    StatusManager.getManager().handle(
                            new Status(IStatus.ERROR, GIGPlugin.PLUGIN_ID, Messages.INTERRUPTED_EXCEPTION, e));
                }
            }
            if (i <= 0) {
                job.cancel();
                job = null;
                jobsLock.lock();
                jobState = JobState.None;
                jobsLock.unlock();
            }
        } else {
            jobsLock.unlock();
        }
    }
}