msi.gama.application.workspace.WorkspaceModelsManager.java Source code

Java tutorial

Introduction

Here is the source code for msi.gama.application.workspace.WorkspaceModelsManager.java

Source

/*********************************************************************************************
 *
 * 'WorkspaceModelsManager.java, in plugin msi.gama.application, is part of the source code of the
 * GAMA modeling and simulation platform.
 * (c) 2007-2016 UMI 209 UMMISCO IRD/UPMC & Partners
 *
 * Visit https://github.com/gama-platform/gama for license information and developers contact.
 * 
 *
 **********************************************************************************************/
package msi.gama.application.workspace;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang.StringUtils;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.FileLocator;
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.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.equinox.internal.app.CommandLineArgs;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.ContainerSelectionDialog;
import org.eclipse.ui.internal.ide.application.DelayedEventsProcessor;
import msi.gama.runtime.GAMA;
import msi.gaml.compilation.kernel.GamaBundleLoader;

/**
 * Class InitialModelOpener.
 *
 * @author drogoul
 * @since 16 nov. 2013
 *
 */
public class WorkspaceModelsManager {

    public final static String GAMA_NATURE = "msi.gama.application.gamaNature";
    public final static String XTEXT_NATURE = "org.eclipse.xtext.ui.shared.xtextNature";
    public final static String PLUGIN_NATURE = "msi.gama.application.pluginNature";
    public final static String BUILTIN_NATURE = "msi.gama.application.builtinNature";

    public final static WorkspaceModelsManager instance = new WorkspaceModelsManager();
    public static OpenDocumentEventProcessor processor;

    public static void createProcessor() {
        final Display display = Display.getDefault();
        if (display == null)
            return;
        processor = new OpenDocumentEventProcessor(display);
    }

    public static class OpenDocumentEventProcessor extends DelayedEventsProcessor {

        private OpenDocumentEventProcessor(final Display display) {
            super(display);
        }

        private final ArrayList<String> filesToOpen = new ArrayList<String>(1);

        @Override
        public void handleEvent(final Event event) {
            if (event.text != null) {
                filesToOpen.add(event.text);
                // System.out.println("RECEIVED FILE TO OPEN: " + event.text);
            }
        }

        @Override
        public void catchUp(final Display display) {
            if (filesToOpen.isEmpty()) {
                return;
            }

            final String[] filePaths = filesToOpen.toArray(new String[filesToOpen.size()]);
            filesToOpen.clear();

            for (final String path : filePaths) {
                instance.openModelPassedAsArgument(path);
            }
        }
    }

    public static QualifiedName BUILTIN_PROPERTY = new QualifiedName("gama.builtin", "models");
    public static String BUILTIN_VERSION = Platform.getProduct().getDefiningBundle().getVersion().toString();

    public void openModelPassedAsArgument(final String modelPath) {

        // printAllGuaranteedProperties();

        String filePath = modelPath;
        String expName = null;
        if (filePath.contains("#")) {
            final String[] segments = filePath.split("#");
            if (segments.length != 2) {
                System.out.println("Wrong definition of model and experiment in argument '" + filePath + "'");
                return;
            }
            filePath = segments[0];
            expName = segments[1];
        }
        final IFile file = findAndLoadIFile(filePath);
        if (file != null) {
            final String fp = filePath;
            final String en = expName;
            final Runnable run = () -> {
                try {
                    // System.out.println(Thread.currentThread().getName() + ": Rebuilding the model " + fp);
                    // Force the project to rebuild itself in order to load the various XText plugins.
                    file.touch(null);
                    file.getProject().build(IncrementalProjectBuilder.FULL_BUILD, null);
                } catch (final CoreException e1) {
                    System.out.println(
                            Thread.currentThread().getName() + ": File " + file.getFullPath() + " cannot be built");
                    return;
                }
                while (GAMA.getRegularGui() == null) {
                    try {
                        Thread.sleep(100);
                        // System.out.println(Thread.currentThread().getName() +
                        // ": waiting for the modeling and simulation environments to be available");
                    } catch (final InterruptedException e2) {
                        // TODO Auto-generated catch block
                        e2.printStackTrace();
                    }
                }
                if (en == null) {
                    // System.out
                    // .println(Thread.currentThread().getName() + ": Opening the model " + fp + " in the editor");
                    GAMA.getGui().editModel(null, file);
                } else {
                    // System.out.println(Thread.currentThread().getName() + ": Trying to run experiment " + en);
                    GAMA.getGui().runModel(file, en);
                }

            };
            new Thread(run, "Automatic opening of " + filePath).start();

        }
    }

    /**
     * @param filePath
     * @return
     */
    private IFile findAndLoadIFile(final String filePath) {
        // GAMA.getGui().debug("WorkspaceModelsManager.findAndLoadIFile " + filePath);
        // No error in case of an empty argument
        if (filePath == null || filePath.isEmpty() || StringUtils.isWhitespace(filePath)) {
            return null;
        }
        final IPath path = new Path(filePath);

        // 1st case: the path can be identified as a file residing in the workspace
        IFile result = findInWorkspace(path);
        if (result != null) {
            return result;
        }
        // 2nd case: the path is outside the workspace
        result = findOutsideWorkspace(path);
        if (result != null) {
            return result;
        }
        System.out.println("File " + filePath
                + " cannot be located. Please check its name and location. Arguments provided were : "
                + Arrays.toString(CommandLineArgs.getApplicationArgs()));
        return null;
    }

    /**
     * @param filePath
     * @return
     */
    private IFile findInWorkspace(final IPath originalPath) {
        // GAMA.getGui().debug("WorkspaceModelsManager.findInWorkspace " + originalPath);
        final IWorkspace workspace = ResourcesPlugin.getWorkspace();
        final IPath workspacePath = new Path(Platform.getInstanceLocation().getURL().getPath());
        final IPath filePath = originalPath.makeRelativeTo(workspacePath);
        IFile file = null;
        try {
            file = workspace.getRoot().getFile(filePath);
        } catch (final Exception e) {
            return null;
        }
        if (!file.exists()) {
            return null;
        }
        return file;
    }

    private IFile findOutsideWorkspace(final IPath originalPath) {
        // GAMA.getGui().debug("WorkspaceModelsManager.findOutsideWorkspace " + originalPath);
        final File modelFile = new File(originalPath.toOSString());
        // TODO If the file does not exist we return null (might be a good idea to check other locations)
        if (!modelFile.exists()) {
            return null;
        }

        // We try to find a folder containing the model file which can be considered as a project
        File projectFileBean = new File(modelFile.getPath());
        File dotFile = null;
        while (projectFileBean != null && dotFile == null) {
            projectFileBean = projectFileBean.getParentFile();
            if (projectFileBean != null) {
                /* parcours des fils pour trouver le dot file et creer le lien vers le projet */
                final File[] children = projectFileBean.listFiles();
                if (children != null)
                    for (int i = 0; i < children.length; i++) {
                        if (children[i].getName().equals(".project")) {
                            dotFile = children[i];
                            break;
                        }
                    }
            }
        }

        if (dotFile == null || projectFileBean == null) {
            MessageDialog.openInformation(Display.getDefault().getActiveShell(), "No project", "The model '"
                    + modelFile.getAbsolutePath()
                    + "' does not seem to belong to an existing GAML project. You can import it in an existing project or in the 'Unclassified models' project.");
            return createUnclassifiedModelsProjectAndAdd(originalPath);
        }

        final IWorkspace workspace = ResourcesPlugin.getWorkspace();
        final IPath location = new Path(dotFile.getAbsolutePath());
        final String pathToProject = projectFileBean.getName();

        try {
            // We load the project description.
            final IProjectDescription description = workspace.loadProjectDescription(location);
            if (description != null) {
                final WorkspaceModifyOperation operation = new WorkspaceModifyOperation() {

                    @Override
                    protected void execute(final IProgressMonitor monitor)
                            throws CoreException, InvocationTargetException, InterruptedException {
                        // We try to get the project in the workspace
                        IProject proj = workspace.getRoot().getProject(pathToProject);
                        // If it does not exist, we create it
                        if (!proj.exists()) {
                            // If a project with the same name exists
                            final IProject[] projects = workspace.getRoot().getProjects();
                            final String name = description.getName();
                            for (final IProject p : projects) {
                                if (p.getName().equals(name)) {
                                    MessageDialog.openInformation(Display.getDefault().getActiveShell(),
                                            "Existing project",
                                            "A project with the same name already exists in the workspace. The model '"
                                                    + modelFile.getAbsolutePath()
                                                    + " will be imported as part of the 'Unclassified models' project.");
                                    createUnclassifiedModelsProjectAndAdd(originalPath);
                                    return;
                                }
                            }

                            proj.create(description, monitor);
                        } else {
                            // project exists but is not accessible, so we delete it and recreate it
                            if (!proj.isAccessible()) {
                                proj.delete(true, null);
                                proj = workspace.getRoot().getProject(pathToProject);
                                proj.create(description, monitor);
                            }
                        }
                        // We open the project
                        proj.open(IResource.NONE, monitor);
                        // And we set some properties to it
                        setValuesProjectDescription(proj, false, false, null);
                    }
                };
                operation.run(new NullProgressMonitor() {

                    // @Override
                    // public void done() {
                    // RefreshHandler.run();
                    // // scope.getGui().tell("Project " + workspace.getRoot().getProject(pathToProject).getName() +
                    // // " has been imported");
                    // }

                });
            }
        } catch (final InterruptedException e) {
            return null;
        } catch (final InvocationTargetException e) {
            return null;
        } catch (final CoreException e) {
            GAMA.getGui().error("Error wien importing project: " + e.getMessage());
        }
        final IProject project = workspace.getRoot().getProject(pathToProject);
        final String relativePathToModel = project.getName()
                + modelFile.getAbsolutePath().replace(projectFileBean.getPath(), "");
        return findInWorkspace(new Path(relativePathToModel));
    }

    /**
     *
     */

    public static String UNCLASSIFIED_MODELS = "Unclassified Models";

    public IFolder createUnclassifiedModelsProject(final IPath location) throws CoreException {
        // First allow to select a parent folder
        final ContainerSelectionDialog dialog = new ContainerSelectionDialog(Display.getDefault().getActiveShell(),
                null, false, "Select a parent project or cancel to create a new project:");
        dialog.setTitle("Project selection");
        dialog.showClosedProjects(false);

        final int result = dialog.open();
        IProject project;
        IFolder modelFolder;

        if (result == MessageDialog.CANCEL) {
            project = createOrUpdateProject(UNCLASSIFIED_MODELS);
            modelFolder = project.getFolder(new Path("models"));
            if (!modelFolder.exists()) {
                modelFolder.create(true, true, null);
            }
        } else {
            final IContainer container = (IContainer) ResourcesPlugin.getWorkspace().getRoot()
                    .findMember((IPath) dialog.getResult()[0]);
            if (container instanceof IProject) {
                project = (IProject) container;
                modelFolder = project.getFolder(new Path("models"));
                if (!modelFolder.exists()) {
                    modelFolder.create(true, true, null);
                }
            } else {
                project = container.getProject();
                modelFolder = (IFolder) container;
            }

        }

        return modelFolder;
    }

    private IFile createUnclassifiedModelsProjectAndAdd(final IPath location) {
        IFile iFile = null;
        try {
            final IFolder modelFolder = createUnclassifiedModelsProject(location);
            iFile = modelFolder.getFile(location.lastSegment());
            if (iFile.exists()) {
                if (iFile.isLinked()) {
                    final IPath path = iFile.getLocation();
                    if (path.equals(location)) {
                        // First case, this is a linked resource to the same location. In that case, we simply return
                        // its name.
                        return iFile;
                    } else {
                        // Second case, this resource is a link to another location. We create a filename that is
                        // guaranteed not to exist and change iFile accordingly.
                        iFile = createUniqueFileFrom(iFile, modelFolder);
                    }
                } else {
                    // Third case, this resource is local and we do not want to overwrite it. We create a filename that
                    // is guaranteed not to exist and change iFile accordingly.
                    iFile = createUniqueFileFrom(iFile, modelFolder);
                }
            }
            iFile.createLink(location, IResource.NONE, null);
            // RefreshHandler.run();
            return iFile;
        } catch (final CoreException e) {
            e.printStackTrace();
            MessageDialog.openInformation(Display.getDefault().getActiveShell(), "Error in creation",
                    "The file " + (iFile == null ? location.lastSegment() : iFile.getFullPath().lastSegment())
                            + " cannot be created because of the following exception " + e.getMessage());
            return null;
        }
    }

    /**
     * @param lastSegment
     * @param modelFolder
     * @return
     */
    private IFile createUniqueFileFrom(final IFile originalFile, final IFolder modelFolder) {
        IFile file = originalFile;
        while (file.exists()) {
            final IPath path = file.getLocation();
            String fName = path.lastSegment();
            final Pattern p = Pattern.compile("(.*?)(\\d+)?(\\..*)?");
            final Matcher m = p.matcher(fName);
            if (m.matches()) {// group 1 is the prefix, group 2 is the number, group 3 is the suffix
                fName = m.group(1) + (m.group(2) == null ? 1 : Integer.parseInt(m.group(2)) + 1)
                        + (m.group(3) == null ? "" : m.group(3));
            }
            file = modelFolder.getFile(fName);
        }
        return file;

    }

    private static void linkPluginsModelsToWorkspace() {
        for (final String plugin : GamaBundleLoader.getPluginsWithModels().keySet()) {
            linkModelsToWorkspace(plugin, GamaBundleLoader.getPluginsWithModels().get(plugin), false);
        }
    }

    public static void linkSampleModelsToWorkspace() {
        final Job job = new Job("Updating the Built-in Models Library") {

            @Override
            protected IStatus run(final IProgressMonitor monitor) {
                // Nothing to do really. Maybe a later version will remove this
                // command. See Issue 669
                while (!GamaBundleLoader.LOADED) {
                    try {
                        Thread.sleep(100);
                    } catch (final InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                linkModelsToWorkspace("msi.gama.models", "models", true);
                linkPluginsModelsToWorkspace();
                return Status.OK_STATUS;
            }

        };
        job.setUser(true);
        job.schedule();

    }

    /**
     * @param plugin
     */

    private static void linkModelsToWorkspace(final String plugin, final String path, final boolean core) {
        final IWorkspace workspace = ResourcesPlugin.getWorkspace();
        final URL urlRep = null;
        File modelsRep = null;
        try {
            final String ext = path == "." ? "/" : "/" + path + "/";
            // urlRep = FileLocator.toFileURL(new URL("platform:/plugin/" + plugin + ext));
            // urlRep = urlRep.toURI().normalize().toURL();

            // urlRep = FileLocator.resolve(new URL("platform:/plugin/" + plugin + ext));

            final URL new_url = FileLocator.resolve(new URL("platform:/plugin/" + plugin + ext));
            final String path_s = new_url.getPath().replaceFirst("^/(.:/)", "$1");
            final java.nio.file.Path normalizedPath = Paths.get(path_s).normalize();
            // urlRep = normalizedPath.toUri().toURL();
            modelsRep = normalizedPath.toFile();

        } catch (final IOException e) {
            e.printStackTrace();
            return;
        } /*
            * catch (URISyntaxException e) {
            * e.printStackTrace();
            * }
            */

        // File modelsRep = new File(urlRep.getPath());
        // System.out.println("chargemen" + modelsRep.getAbsolutePath());
        final Map<File, IPath> foundProjects = new HashMap<>();
        findProjects(modelsRep, foundProjects);
        importBuiltInProjects(plugin, core, workspace, foundProjects);

        if (core)
            stampWorkspaceFromModels();

    }

    private static final FilenameFilter isDotFile = (dir, name) -> name.equals(".project");

    private static void findProjects(final File folder, final Map<File, IPath> found) {
        if (folder == null) {
            return;
        }
        final File[] dotFile = folder.listFiles(isDotFile);
        if (dotFile == null) {
            return;
        } // not a directory
        if (dotFile.length == 0) { // no .project file
            final File[] files = folder.listFiles();
            if (files != null)
                for (final File f : folder.listFiles()) {
                    findProjects(f, found);
                }
            return;
        }
        found.put(folder, new Path(dotFile[0].getAbsolutePath()));

    }

    /**
     * @param plugin
     * @param core
     * @param workspace
     * @param project
     */
    private static void importBuiltInProjects(final String plugin, final boolean core, final IWorkspace workspace,
            final Map<File, IPath> projects) {

        for (final Map.Entry<File, IPath> entry : projects.entrySet()) {
            final File project = entry.getKey();
            final IPath location = entry.getValue();
            final WorkspaceModifyOperation operation = new WorkspaceModifyOperation() {

                @Override
                protected void execute(final IProgressMonitor monitor)
                        throws CoreException, InvocationTargetException, InterruptedException {
                    IProject proj = workspace.getRoot().getProject(project.getName());
                    if (!proj.exists()) {
                        proj.create(workspace.loadProjectDescription(location), monitor);
                    } else {
                        // project exists but is not accessible
                        if (!proj.isAccessible()) {
                            proj.delete(true, null);
                            proj = workspace.getRoot().getProject(project.getName());
                            proj.create(workspace.loadProjectDescription(location), monitor);
                        }
                    }
                    proj.open(IResource.NONE, monitor);
                    setValuesProjectDescription(proj, true, !core, plugin);
                }
            };
            try {
                operation.run(null);
            } catch (final InterruptedException e) {
                e.printStackTrace();
            } catch (final InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    }

    static public IProject createOrUpdateProject(final String name) {
        final IWorkspace ws = ResourcesPlugin.getWorkspace();
        final IProject[] projectHandle = new IProject[] { null };
        final WorkspaceModifyOperation op = new WorkspaceModifyOperation() {

            @Override
            protected void execute(final IProgressMonitor monitor) throws CoreException {
                monitor.beginTask("Creating or updating " + name, 2000);
                final IProject project = ws.getRoot().getProject(name);
                // IProjectDescription desc = null;
                if (!project.exists()) {
                    // desc = project.getDescription();
                    // } else {
                    final IProjectDescription desc = ws.newProjectDescription(name);
                    project.create(desc, new SubProgressMonitor(monitor, 1000));
                }
                if (monitor.isCanceled()) {
                    throw new OperationCanceledException();
                }
                project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(monitor, 1000));
                projectHandle[0] = project;
                setValuesProjectDescription(project, false, false, null);
            }
        };
        try {
            op.run(null);
        } catch (final InvocationTargetException e) {
            e.printStackTrace();
        } catch (final InterruptedException e) {
            e.printStackTrace();
        }
        return projectHandle[0];
    }

    static public void setValuesProjectDescription(final IProject proj, final boolean builtin,
            final boolean inPlugin, final String pluginName) {
        /* Modify the project description */
        IProjectDescription desc = null;
        try {
            desc = proj.getDescription();
            /* Automatically associate GamaNature and Xtext nature to the project */
            // String[] ids = desc.getNatureIds();
            final String[] newIds = new String[builtin ? 3 : 2];
            // System.arraycopy(ids, 0, newIds, 0, ids.length);
            newIds[1] = GAMA_NATURE;
            newIds[0] = XTEXT_NATURE;
            // Addition of a special nature to the project.
            if (builtin) {
                newIds[2] = inPlugin ? PLUGIN_NATURE : BUILTIN_NATURE;
            }
            desc.setNatureIds(newIds);
            if (inPlugin && pluginName != null) {
                desc.setComment(pluginName);
            } else {
                desc.setComment("");
            }
            proj.setDescription(desc, IResource.FORCE, null);
            // Addition of a special persistent property to indicate that the project is built-in
            if (builtin) {
                proj.setPersistentProperty(BUILTIN_PROPERTY, BUILTIN_VERSION);
            }
        } catch (final CoreException e) {
            e.printStackTrace();
        }
    }

    // static private IProjectDescription setProjectDescription(final File project) {
    // final IProjectDescription description = ResourcesPlugin.getWorkspace().newProjectDescription(project.getName());
    // final IPath location = new Path(project.getAbsolutePath());
    // description.setLocation(location);
    // return description;
    // }

    public static void stampWorkspaceFromModels() {
        final IWorkspace workspace = ResourcesPlugin.getWorkspace();
        try {
            final String stamp = getCurrentGamaStampString();
            final IWorkspaceRoot root = workspace.getRoot();
            final String oldStamp = root.getPersistentProperty(BUILTIN_PROPERTY);
            if (oldStamp != null) {
                final File stampFile = new File(
                        new Path(root.getLocation().toOSString() + File.separator + oldStamp).toOSString());
                if (stampFile.exists()) {
                    stampFile.delete();
                }
            }
            root.setPersistentProperty(BUILTIN_PROPERTY, stamp);
            final File stampFile = new File(
                    new Path(root.getLocation().toOSString() + File.separator + stamp).toOSString());
            if (!stampFile.exists()) {
                stampFile.createNewFile();
            }
        } catch (final CoreException e) {
            e.printStackTrace();
        } catch (final IOException e) {
            e.printStackTrace();
        }
    }

    public static String getCurrentGamaStampString() {
        String gamaStamp = null;
        try {
            final URL tmpURL = new URL("platform:/plugin/msi.gama.models/models/");
            final URL new_url = FileLocator.resolve(tmpURL);
            final String path_s = new_url.getPath().replaceFirst("^/(.:/)", "$1");
            final java.nio.file.Path normalizedPath = Paths.get(path_s).normalize();
            final File modelsRep = normalizedPath.toFile();

            // loading file from URL Path is not a good idea. There are some bugs
            // File modelsRep = new File(urlRep.getPath());

            final long time = modelsRep.lastModified();
            gamaStamp = ".built_in_models_" + time;
            System.out.println(">GAMA version " + WorkspaceModelsManager.BUILTIN_VERSION + " loading...");
            System.out.println(">GAMA models library version: " + gamaStamp);
        } catch (final IOException e) {
            e.printStackTrace();
        }
        return gamaStamp;
    }

    public boolean isGamaProject(final File f) throws CoreException {
        for (final String s : f.list()) {
            if (s.equals(".project")) {
                IPath p = new Path(f.getAbsolutePath());
                p = p.append(".project");
                final IProjectDescription pd = ResourcesPlugin.getWorkspace().loadProjectDescription(p);
                if (pd.hasNature(this.GAMA_NATURE)) {
                    return true;
                }
            }
        }
        return false;
    }

}