org.lamport.tla.toolbox.tool.tlc.model.Model.java Source code

Java tutorial

Introduction

Here is the source code for org.lamport.tla.toolbox.tool.tlc.model.Model.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Microsoft Research. All rights reserved. 
 *
 * The MIT License (MIT)
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy 
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software. 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
 * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * Contributors:
 *   Markus Alexander Kuppe - initial API and implementation
 ******************************************************************************/

package org.lamport.tla.toolbox.tool.tlc.model;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.NotFileFilter;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.FindReplaceDocumentAdapter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.ui.editors.text.FileDocumentProvider;
import org.eclipse.ui.part.FileEditorInput;
import org.lamport.tla.toolbox.spec.Spec;
import org.lamport.tla.toolbox.tool.ToolboxHandle;
import org.lamport.tla.toolbox.tool.tlc.TLCActivator;
import org.lamport.tla.toolbox.tool.tlc.launch.IModelConfigurationConstants;
import org.lamport.tla.toolbox.tool.tlc.model.Model.StateChangeListener.ChangeEvent;
import org.lamport.tla.toolbox.tool.tlc.model.Model.StateChangeListener.ChangeEvent.State;
import org.lamport.tla.toolbox.tool.tlc.traceexplorer.SimpleTLCState;
import org.lamport.tla.toolbox.tool.tlc.util.ModelHelper;
import org.lamport.tla.toolbox.util.ResourceHelper;

import tlc2.output.MP;

/**
 * This class represents a Toolbox Model that can be executed by TLC.
 */
public class Model implements IModelConfigurationConstants, IAdaptable {
    /**
      * Marker indicating an error in the model
      */
    private static final String TLC_MODEL_ERROR_MARKER = "org.lamport.tla.toolbox.tlc.modelErrorMarker";
    /**
     * boolean attribute indicating if the original trace of a model checking
     * run should be shown in the error view for that model
     */
    private static final String IS_ORIGINAL_TRACE_SHOWN = "isOriginalTraceShown";
    /**
     * marker on .launch file with boolean attribute modelIsRunning; only used for historic clean-ups as of 1.5.5
     */
    private static final String TLC_MODEL_IN_USE_MARKER = "org.lamport.tla.toolbox.tlc.modelMarker";
    /**
     * marker on .launch file, binary semantics
     */
    private static final String TLC_CRASHED_MARKER = "org.lamport.tla.toolbox.tlc.crashedModelMarker";
    /**
     * marker on .launch file, with boolean attribute isOriginalTraceShown
     */
    private static final String TRACE_EXPLORER_MARKER = "org.lamport.tla.toolbox.tlc.traceExplorerMarker";

    public static final String SPEC_MODEL_DELIM = "___";

    static String sanitizeName(String aModelName) {
        Assert.isNotNull(aModelName);
        if (aModelName.contains(SPEC_MODEL_DELIM)) {
            // Strip spec prefix
            aModelName = aModelName.split(SPEC_MODEL_DELIM)[1];
        }
        if (aModelName.endsWith(".launch")) {
            // Strip file name extension
            aModelName = aModelName.substring(0, aModelName.length() - ".launch".length());
        }
        return aModelName;
    }

    /**
     * @param fullQualifiedModelName The full-qualified (includes the Spec name and separator too) name of the Model.
     * @return A Model, iff a Model by the given name exists and <code>null</code> otherwise.
     */
    public static Model getByName(final String modelName) {
        return TLCModelFactory.getByName(modelName);
    }

    /**
     * A {@link StateChangeListener} is notified when the running state of the model
     * changes. There is no guarantee as to which thread is being used to send the
     * notification. A {@link StateChangeListener} has subscribe and unsubscribe
     * via {@link Model#add(StateChangeListener)} and {@link Model#remove(StateChangeListener)}.
     */
    public static class StateChangeListener {

        public static class ChangeEvent {

            public enum State {
                RUNNING, NOT_RUNNING, DELETED, REMOTE_RUNNING, REMOTE_NOT_RUNNING;

                public boolean in(State... states) {
                    for (State state : states) {
                        if (state == this) {
                            return true;
                        }
                    }
                    return false;
                }
            }

            private final State state;
            private final Model model;

            private ChangeEvent(Model model, State state) {
                this.model = model;
                this.state = state;
            }

            public State getState() {
                return state;
            }

            public Model getModel() {
                return model;
            }
        }

        /**
         * @return true iff the listener should be unsubscribed from receiving future
         *         events after it handled the event.
         */
        public boolean handleChange(ChangeEvent event) {
            return false;
        }
    }

    private final Set<StateChangeListener> listeners = new CopyOnWriteArraySet<StateChangeListener>();
    private TLCSpec spec;
    private ILaunchConfiguration launchConfig;

    // transient fields
    /**
     * The working copy is the temporary storage for pending model changes until
     * they are saved. Upon save, the changes are merged back into launchConfig
     * and the working copy is nulled.
     * <p>
     * A null workingCopy indicates that the model is *not* dirty/has no pending,
     * unsaved changes.
     */
    private ILaunchConfigurationWorkingCopy workingCopy;
    private ILaunch launch;

    /**
     * Marker indicating the SANY Errors
     */
    public static final String TLC_MODEL_ERROR_MARKER_SANY = "org.lamport.tla.toolbox.tlc.modelErrorSANY";

    /**
     * Only supposed to be called by {@link TLCModelFactory}
     */
    protected Model(ILaunchConfiguration launchConfig) {
        /*
         * The only caller of this method should only ever be
         * TLCModelFactory.getAdapter(Object, Class<T>). All others should use
         * ILaunchConfiguration.getAdapter(Model.class).
         */
        Assert.isNotNull(launchConfig);
        this.launchConfig = launchConfig;
    }

    public boolean add(StateChangeListener stateChangeListener) {
        return this.listeners.add(stateChangeListener);
    }

    public boolean remove(StateChangeListener stateChangeListener) {
        return this.listeners.remove(stateChangeListener);
    }

    private void notifyListener(final StateChangeListener.ChangeEvent event) {
        for (StateChangeListener scl : listeners) {
            if (scl.handleChange(event)) {
                // Listener wants to be deregistered as a listener.
                listeners.remove(scl);
            }
        }
    }

    public TLCSpec getSpec() {
        if (this.spec == null) {
            final String launchName = this.launchConfig.getName();
            Assert.isTrue(launchName.contains(SPEC_MODEL_DELIM));

            final Spec spec = ToolboxHandle.getSpecByName(launchName.split(SPEC_MODEL_DELIM)[0]);
            Assert.isNotNull(spec, "Failed to lookup spec with name " + launchName.split(SPEC_MODEL_DELIM)[0]);

            this.spec = spec.getAdapter(TLCSpec.class);
        }
        return this.spec;
    }

    public Model copy(String newModelName) {
        newModelName = sanitizeName(newModelName);
        try {
            final ILaunchConfigurationWorkingCopy copy = this.launchConfig
                    .copy(getSpec().getName() + SPEC_MODEL_DELIM + newModelName);
            copy.setAttribute(ModelHelper.MODEL_NAME, newModelName);
            return copy.doSave().getAdapter(Model.class);
        } catch (CoreException e) {
            TLCActivator.logError("Error cloning model.", e);
            return null;
        }
    }

    public void rename(String newModelName, IProgressMonitor monitor) throws CoreException {
        final Collection<Model> snapshots = getSnapshots();

        // Rename the model directory to the new name.
        final IFolder modelDir = getTargetDirectory();
        if (modelDir != null && modelDir.exists()) {
            final IPath src = modelDir.getFullPath();
            final IPath dst = src.removeLastSegments(1).append(newModelName);
            modelDir.move(dst, true, monitor);
        }

        renameLaunch(getSpec(), sanitizeName(newModelName));

        for (Model snapshot : snapshots) {
            snapshot.rename(newModelName + snapshot.getSnapshotSuffix(), monitor);
        }
    }

    /**
     * The {@link Model}'s parent {@link TLCSpec} has been renamed. Update the {@link Model} too.
     * @param newSpecName
     */
    void specRename(final Spec newSpec) {
        final Collection<Model> snapshots = getSnapshots();

        renameLaunch(newSpec, getName());
        // The spec's name has changed. Force a re-lookup of the instance with
        // the updated name.
        this.spec = null;

        for (Model snapshot : snapshots) {
            snapshot.specRename(newSpec);
            ;
        }
    }

    private void renameLaunch(final Spec newSpec, String newModelName) {
        try {
            // create the model with the new name
            final ILaunchConfigurationWorkingCopy copy = this.launchConfig
                    .copy(newSpec.getName() + SPEC_MODEL_DELIM + newModelName);
            copy.setAttribute(SPEC_NAME, newSpec.getName());
            copy.setAttribute(ModelHelper.MODEL_NAME, newModelName);
            copy.setContainer(newSpec.getProject());
            final ILaunchConfiguration renamed = copy.doSave();

            // delete the old model
            this.launchConfig.delete();
            this.launchConfig = renamed;
        } catch (CoreException e) {
            TLCActivator.logError("Error renaming model.", e);
        }
    }

    public String getName() {
        try {
            return this.launchConfig.getAttribute(ModelHelper.MODEL_NAME, (String) null);
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            return null;
        }
    }

    public String getComments() {
        try {
            return this.launchConfig.getAttribute(IModelConfigurationConstants.MODEL_COMMENTS, "");
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            return "";
        }
    }

    public boolean isRunning() {
        final ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager();
        final ILaunch[] launches = launchManager.getLaunches();
        for (int i = 0; i < launches.length; i++) {
            final ILaunch launch = launches[i];
            if (launch.getLaunchConfiguration() != null
                    && launch.getLaunchConfiguration().contentsEqual(this.launchConfig)) {
                if (!launch.isTerminated()) {
                    return true;
                }
            }
        }
        return false;
    }

    public void setRunning(boolean isRunning) {
        if (isRunning) {
            // Clear the crashed marker. After all, the model is obviously
            // running now.
            recover();
        }
        notifyListener(new StateChangeListener.ChangeEvent(this, isRunning ? State.RUNNING : State.NOT_RUNNING));
    }

    /**
    * Looks up if the model has a stale marker. The stale marker is installed
    * when the TLC process crashed (terminated with exit code > 0).
    */
    public boolean isStale() {
        final IFile resource = getFile();
        if (resource.exists()) {
            IMarker[] foundMarkers;
            try {
                foundMarkers = resource.findMarkers(TLC_CRASHED_MARKER, false, IResource.DEPTH_ZERO);
                if (foundMarkers.length > 0) {
                    return true;
                } else {
                    return false;
                }
            } catch (CoreException shouldNotHappen) {
                TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            }
        }
        return false;
    }

    public void setStale() {
        try {
            getFile().createMarker(TLC_CRASHED_MARKER);
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
        }
    }

    private boolean isRunningRemotely = false;

    public boolean isRunningRemotely() {
        return this.isRunningRemotely;
    }

    public void setRunningRemotely(boolean isRunning) {
        this.isRunningRemotely = isRunning;
        notifyListener(new StateChangeListener.ChangeEvent(this,
                isRunning ? State.REMOTE_RUNNING : State.REMOTE_NOT_RUNNING));
    }

    /*
     * Snapshot related methods.
     */

    private static final String SNAP_SHOT = "_SnapShot_";
    static final String SNAPSHOT_REGEXP = SNAP_SHOT + "[0-9]*$";

    private String getSnapshotSuffix() {
        if (isSnapshot()) {
            final int idx = getName().lastIndexOf(SNAP_SHOT);
            return getName().substring(idx, getName().length());
        }
        return "";
    }

    public long getSnapshotTimeStamp() {
        final int idx = getName().lastIndexOf(SNAP_SHOT) + 10;
        return Long.valueOf(getName().substring(idx));
    }

    public Collection<Model> getSnapshots() {
        return getSpec().getModels(Pattern.quote(getName()) + SNAPSHOT_REGEXP, true).values();
    }

    public boolean isSnapshot() {
        return getName().matches(".*" + SNAPSHOT_REGEXP);
    }

    public boolean hasSnapshots() {
        return !getSpec().getModels(Pattern.quote(getName()) + SNAPSHOT_REGEXP, true).isEmpty();
    }

    public Model getSnapshotFor() {
        return getSpec().getModel(getName().replaceFirst(SNAPSHOT_REGEXP, ""));
    }

    public Model snapshot() throws CoreException {
        // Create a copy of the underlying launch configuration.
        final Model snapshot = copy(getName() + SNAP_SHOT + System.currentTimeMillis());

        // Snapshot the model's markers as well (e.g. the information about errors, see hasErrors()).
        final IMarker[] markers = getMarkers();
        for (IMarker iMarker : markers) {
            snapshot.setMarker(iMarker.getAttributes(), iMarker.getType());
        }

        // Set the snapshot to be locked? Do we want the user to be able to run it again?
        //       snapshot.setLocked(true);

        /*
         * Snapshot (copy) the model folder which include the TLC output as well as the version
         * of the spec and module overwrites with which TLC ran.
         */
        final IPath snapshotPath = getSpec().getProject().getFolder(snapshot.getName()).getLocation();
        final IPath modelFolder = getSpec().getProject().getFolder(this.getName()).getLocation();
        // Use non-Eclipse API instead of modelFolder.copy(snapshotFolder, false,
        // monitor which supports a non-recursive copy. A recursive copy includes the
        // states/ directory leftover from TLC which waste quite some space and might
        // take some time to copy.
        try {
            FileUtils.copyDirectory(modelFolder.toFile(), snapshotPath.toFile(),
                    new NotFileFilter(DirectoryFileFilter.DIRECTORY));

            // Rename .dot file name because hasStateGraphDump checks if a .dot file exists
            // that matches the name of the model.
            if (hasStateGraphDump()) {
                final IPath oldDotFile = getSpec().getProject()
                        .getFolder(snapshot.getName() + File.separator + this.getName() + ".dot").getLocation();
                final IPath newDotFile = getSpec().getProject()
                        .getFolder(snapshot.getName() + File.separator + snapshot.getName() + ".dot").getLocation();
                FileUtils.moveFile(oldDotFile.toFile(), newDotFile.toFile());
            }

            // Now that we've had a successful save, prune any snapshots, starting with the oldest, in order to assure the
            // cardinality no greater than snapshotKeepCount.
            pruneOldestSnapshots();

            // Refresh the snapshot folder after having copied files without using the
            // Eclipse resource API. Otherwise, the resource API does not see the files
            // which e.g. results in an incomplete model deletion or hasStateGraphDump
            // incorrectly returning false.
            snapshot.getFolder().refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
        } catch (IOException e) {
            throw new CoreException(new Status(Status.ERROR, TLCActivator.PLUGIN_ID, e.getMessage(), e));
        }

        return snapshot;
    }

    private void pruneOldestSnapshots() throws CoreException {
        // Sort model by snapshot timestamp and remove oldest ones.
        final int snapshotKeepCount = TLCActivator.getDefault().getPreferenceStore()
                .getInt(TLCActivator.I_TLC_SNAPSHOT_KEEP_COUNT);
        final List<Model> snapshotModels = new ArrayList<>(getSnapshots());
        if (snapshotModels.size() > snapshotKeepCount) {
            final int pruneCount = snapshotModels.size() - snapshotKeepCount;
            Collections.sort(snapshotModels, new Comparator<Model>() {
                public int compare(final Model model1, final Model model2) {
                    final long ts1 = model1.getSnapshotTimeStamp();
                    final long ts2 = model2.getSnapshotTimeStamp();
                    return Long.compare(ts1, ts2);
                }
            });
            for (int i = 0; i < pruneCount; i++) {
                final Model model = snapshotModels.get(i);
                model.delete(new NullProgressMonitor());
            }
        }
    }

    /*
     * End of snapshot related methods.
     */

    /*
     * State Graph dump in dot file format (GraphViz).
     */

    private static final String DOT_FILE_EXT = ".dot";

    public boolean hasStateGraphDump() {
        final String name = getName().concat(DOT_FILE_EXT);
        // Convert to java.io.File rather than Eclipse's IFile. For the latter
        // file.exists might return null if Eclipse's internal FS cache is stale.
        final File file = getFolder().getFile(name).getLocation().toFile();
        return file.exists();
    }

    public IFile getStateGraphDump() {
        final String name = getName().concat(DOT_FILE_EXT);
        return getFolder().getFile(name);
    }

    /*
     * End of state graph dump related methods.
     */

    /**
      * Returns whether the original trace or the trace with trace explorer expressions from the
      * most recent run of the trace explorer for the model should be shown in the TLC error view.
      * 
      * See {@link ModelHelper#setOriginalTraceShown(ILaunchConfiguration, boolean)} for setting this
      * return value.
      * 
      * @return whether the original trace or the trace with trace explorer expressions from the
      * most recent run of the trace explorer for the model should be shown in the TLC error view
      */
    public boolean isOriginalTraceShown() {
        return isMarkerSet(TRACE_EXPLORER_MARKER, IS_ORIGINAL_TRACE_SHOWN);
    }

    /**
     * Sets whether the original trace or the trace with trace explorer expressions
     * should be shown in the TLC error view for the model represented by this
     * configuration.
     * 
     * Code the raises the TLC error view or updates the TLC error view for a model
     * can use {@link ModelHelper#isOriginalTraceShown(ILaunchConfiguration)} to determine
     * if the original trace should be shown for a given model.
     * 
     * @param isOriginalTraceShown true if the original trace should be shown, false if
     * the trace with trace explorer expressions should be shown
     */
    public void setOriginalTraceShown(boolean isOriginalTraceShown) {
        setMarker(TRACE_EXPLORER_MARKER, IS_ORIGINAL_TRACE_SHOWN, isOriginalTraceShown);
    }

    public boolean hasError() {
        return getMarkers().length > 0;
    }

    private boolean isMarkerSet(String markerType, final String attributeName) {
        // marker
        final IFile resource = getFile();
        if (resource.exists()) {
            try {
                final IMarker[] foundMarkers = resource.findMarkers(markerType, false, IResource.DEPTH_ZERO);
                if (foundMarkers.length > 0) {
                    boolean set = foundMarkers[0].getAttribute(attributeName, false);
                    // remove trash if any
                    for (int i = 1; i < foundMarkers.length; i++) {
                        foundMarkers[i].delete();
                    }
                    return set;
                } else {
                    return false;
                }
            } catch (CoreException shouldNotHappen) {
                TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            }
        }
        return false;
    }

    private void setMarker(final String markerType, final String attributeName, boolean value) {
        final IFile resource = getFile();
        if (resource.exists()) {
            try {
                IMarker marker;
                final IMarker[] foundMarkers = resource.findMarkers(markerType, false, IResource.DEPTH_ZERO);
                if (foundMarkers.length > 0) {
                    marker = foundMarkers[0];
                    // remove trash if any
                    for (int i = 1; i < foundMarkers.length; i++) {
                        foundMarkers[i].delete();
                    }
                } else {
                    marker = resource.createMarker(markerType);
                }

                marker.setAttribute(attributeName, value);
            } catch (CoreException shouldNotHappen) {
                TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            }
        }
    }

    /**
     * Installs a marker on the model
    * @param properties a map of attribute names to attribute values 
    *      (key type : <code>String</code> value type : <code>String</code>, 
    *      <code>Integer</code>, or <code>Boolean</code>) or <code>null</code>
     */
    public IMarker setMarker(Map<String, Object> properties, String markerType) {
        try {
            IMarker marker = getFile().createMarker(markerType);
            marker.setAttributes(properties);
            return marker;
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
        }
        return null;
    }

    public IMarker[] getMarkers() {
        final IFile resource = getFile();
        if (resource.exists()) {
            try {
                return resource.findMarkers(TLC_MODEL_ERROR_MARKER, true, IResource.DEPTH_ZERO);
            } catch (CoreException shouldNotHappen) {
                TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            }
        }
        return new IMarker[0];
    }

    public void removeMarkers(final String markerType) {
        try {
            final IMarker[] foundMarkers = getFile().findMarkers(markerType, true, IResource.DEPTH_ONE);
            for (int i = 0; i < foundMarkers.length; i++) {
                foundMarkers[i].delete();
            }
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
        }
    }

    /**
     * Tries to recover model after an abnormal TLC termination
     * It deletes all temporary files on disk and restores the state to unlocked.
     */
    public void recover() {
        final IFile resource = getFile();
        if (resource.exists()) {
            try {
                // remove any crashed markers
                IMarker[] foundMarkers = resource.findMarkers(TLC_CRASHED_MARKER, false, IResource.DEPTH_ZERO);
                if (foundMarkers.length == 0) {
                    return;
                }

                for (int i = 0; i < foundMarkers.length; i++) {
                    foundMarkers[i].delete();
                }

                foundMarkers = resource.findMarkers(TLC_MODEL_IN_USE_MARKER, false, IResource.DEPTH_ZERO);
                for (int i = 0; i < foundMarkers.length; i++) {
                    foundMarkers[i].delete();
                }
            } catch (CoreException shouldNotHappen) {
                TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            }
        }
    }

    public ILaunchConfiguration getLaunchConfiguration() {
        return this.launchConfig;
    }

    public void delete(IProgressMonitor monitor) throws CoreException {

        // First delete the Model's snapshots. If left undelete, they disappear from the
        // Toolbox have to be deleted manually.
        final Collection<Model> snapshots = getSnapshots();
        for (Model model : snapshots) {
            model.delete(SubMonitor.convert(monitor));
        }

        notifyListener(new ChangeEvent(this, State.DELETED));

        final IResource[] members;

        // if the model has never been model checked, no model folder will exist
        final IFolder modelFolder = getTargetDirectory();
        if (modelFolder != null) {
            members = new IResource[2];
            members[0] = modelFolder; // model folder
            members[1] = getLaunchConfiguration().getFile(); // modle launch config
        } else {
            members = new IResource[] { getLaunchConfiguration().getFile() };
        }

        // schedule combined deletion of both the model folder as well as the
        // launch config
        final ISchedulingRule deleteRule = ResourceHelper.getDeleteRule(members);

        ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() {

            /* (non-Javadoc)
            * @see org.eclipse.core.resources.IWorkspaceRunnable#run(org.eclipse.core.runtime.IProgressMonitor)
            */
            public void run(IProgressMonitor subMonitor) throws CoreException {
                subMonitor.beginTask("Deleting files", members.length);

                // actually deletes all IResource members
                try {
                    for (int i = 0; i < members.length; i++) {
                        members[i].delete(IResource.FORCE, SubMonitor.convert(subMonitor, 1));
                    }
                } catch (CoreException e) {
                    TLCActivator.logError("Error deleting a file " + e.getMessage(), e);
                    throw e;
                }

                subMonitor.done();
            }
        }, deleteRule, IWorkspace.AVOID_UPDATE, SubMonitor.convert(monitor, members.length));
    }

    /**
    * Retrieves the working directory for the model. Returns null if the model has
    * not run yet. In other words, gets created by running TLC.<br>
    * Note, this is a handle operation only, the resource returned may not exist
    * 
    * @param config
    * @return the Folder.
    * @see Model#getFolder()
    */
    public IFolder getTargetDirectory() {
        return (IFolder) getSpec().getProject().findMember(getName());
    }

    public List<IFile> getSavedTLAFiles() {
        try {
            final List<IFile> res = new ArrayList<>();
            final IFolder targetDirectory = getTargetDirectory();
            if (targetDirectory == null) {
                // Model has not been run yet.
                return res;
            }
            final List<IResource> asList = Arrays.asList(targetDirectory.members());
            for (final IResource iResource : asList) {
                if (iResource instanceof IFile) {
                    final IFile f = (IFile) iResource;
                    if (f.exists() && "tla".equalsIgnoreCase(f.getFileExtension())) {
                        res.add(f);
                    }
                }
            }
            return res;
        } catch (CoreException e) {
            return new ArrayList<>();
        }
    }

    /**
     * Retrieves the TLA file that is being model checked on the model run. This is
     * the MC.tla file.
     * 
     * @param config configuration representing the model
     * @return a file handle or <code>null</code>
     */
    public IFile getTLAFile() {
        return getFile(ModelHelper.FILE_TLA);
    }

    /**
     * Retrieves the TE file that is being used by the trace explorer.
     * 
     * @param config
     *            configuration representing the model
     * @return a file handle or <code>null</code>
     */
    public IFile getTEFile() {
        return getFile(ModelHelper.TE_FILE_TLA);
    }

    public IFile getOutputLogFile() {
        return getOutputLogFile(false);
    }

    /**
     * Retrieves a file where the log of the TLC run is written. If isTraceExploration is true, this
     * will return the log file for trace exploration. If that flag is false, this will return the log file
     * for normal model checking.
     * 
     * @param config configuration representing the model
     * @param getTraceExplorerOutput flag indicating if the log file for trace exploration is to be returned
     * @return the file handle, or null
     */
    public IFile getOutputLogFile(boolean getTraceExplorerOutput) {
        if (getTraceExplorerOutput) {
            return getFile(ModelHelper.TE_FILE_OUT);
        } else {
            return getFile(ModelHelper.FILE_OUT);
        }
    }

    /**
     * Retrieves the TLA file used by the trace explorer
     * @return a file handle or <code>null</code>
     */
    public IFile getTraceExplorerTLAFile() {
        return getFile(ModelHelper.TE_FILE_TLA);
    }

    private IFile getFile(final String id) {
        final IFolder targetFolder = getTargetDirectory();
        if (targetFolder != null && targetFolder.exists()) {
            final IFile teFile = (IFile) targetFolder.findMember(id);
            if (teFile != null && teFile.exists()) {
                return teFile;
            }
        }
        return null;
    }

    /**
     * Returns a handle to the output file {@link ModelHelper#TE_TRACE_SOURCE} used by the
     * trace explorer to retrieve the trace from the most recent run of TLC on
     * the config.
     * 
     * Note that this is a handle-only operation. The file need not exist in the
     * underlying file system.
     * 
     * @return
     */
    public IFile getTraceSourceFile() {
        final IFolder targetFolder = getTargetDirectory();
        if (targetFolder != null && targetFolder.exists()) {
            final IFile logFile = targetFolder.getFile(ModelHelper.TE_TRACE_SOURCE);
            Assert.isNotNull(logFile);
            return logFile;
        }
        return null;
    }

    public void createModelOutputLogFile(InputStream is, IProgressMonitor monitor) throws CoreException {
        IFolder targetFolder = getTargetDirectory();
        // Create targetFolder which might be missing if the model has never
        // been checked but the user wants to load TLC output anyway.
        // This happens with distributed TLC, where the model is executed
        // remotely and the log is send to the user afterwards.
        if (targetFolder == null || !targetFolder.exists()) {
            String modelName = getName();
            targetFolder = getSpec().getProject().getFolder(modelName);
            targetFolder.create(true, true, monitor);
        }
        if (targetFolder != null && targetFolder.exists()) {
            // Always refresh the folder in case it has to override an existing
            // file that is out-of-sync with the Eclipse foundation layer.
            targetFolder.refreshLocal(IFolder.DEPTH_INFINITE, monitor);

            // Import MC.out
            IFile mcOutFile = targetFolder.getFile(ModelHelper.FILE_OUT);
            if (mcOutFile.exists()) {
                mcOutFile.delete(true, monitor);
            }
            mcOutFile.create(is, true, monitor); // create closes the InputStream is.

            // Import MC_TE.out by copying the MC.out file to MC_TE.out.
            // The reason why there are two identical files (MC.out and
            // MC_TE.out) has been lost in history.
            IFile mcTEOutfile = targetFolder.getFile(ModelHelper.TE_TRACE_SOURCE);
            if (mcTEOutfile.exists()) {
                mcTEOutfile.delete(true, monitor);
            }
            mcOutFile.copy(mcTEOutfile.getFullPath(), true, monitor);
        }
    }

    public IFolder getFolder() {
        return getSpec().getProject().getFolder(getName());
    }

    private static final String CHECKPOINT_STATES = ModelHelper.MC_MODEL_NAME + ".st.chkpt";
    private static final String CHECKPOINT_QUEUE = "queue.chkpt";
    private static final String CHECKPOINT_VARS = "vars.chkpt";

    /**
     * Checks whether the checkpoint files exist for a given model
     * If doRefresh is set to true, this method will refresh the model directory,
     * and if a checkpoint folder is found, it will refresh the contents of that folder.
     * This means that the eclipse workspace representation of that directory will
     * synch with the file system. This is a long running job, so this method should not
     * be called within the running of another job unless the scheduling rule for
     * refreshing the model directory is included in the scheduling rule of the job which
     * is calling this method. This scheduling rule can be found by calling
     * 
     * Note: Because the Toolbox deletes any existing checkpoint when running TLC,
     * there should be at most one checkpoint.  Therefore, this method should return an array
     * of length 0 or 1.
     * 
     * {@link IResourceRuleFactory#refreshRule(IResource)}
     * @param config
     * @param doRefresh whether the model directory's contents and any checkpoint
     * folders contents should be refreshed
     * @return the array of checkpoint directories, sorted from last to first
     */
    public IResource[] getCheckpoints(boolean doRefresh) throws CoreException {
        // yy-MM-dd-HH-mm-ss
        Pattern pattern = Pattern.compile("[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}");

        Vector<IResource> checkpoints = new Vector<IResource>();
        IFolder directory = getTargetDirectory();

        if (directory != null && directory.exists()) {
            // refreshing is necessary because TLC creates
            // the checkpoint folders, but they may not have
            // been incorporated into the toolbox workspace
            // yet
            // the depth is one to find any checkpoint folders
            if (doRefresh) {
                directory.refreshLocal(IResource.DEPTH_ONE, null);
            }
            IResource[] members = directory.members();
            for (int i = 0; i < members.length; i++) {
                if (members[i].getType() == IResource.FOLDER) {
                    Matcher matcher = pattern.matcher(members[i].getName());
                    if (matcher.matches()) {
                        // if there is a checkpoint folder, it is necessary
                        // to refresh its contents because they may not
                        // be part of the workspace yet
                        if (doRefresh) {
                            members[i].refreshLocal(IResource.DEPTH_ONE, null);
                        }
                        if (((IFolder) members[i]).findMember(CHECKPOINT_QUEUE) != null
                                && ((IFolder) members[i]).findMember(CHECKPOINT_VARS) != null
                                && ((IFolder) members[i]).findMember(CHECKPOINT_STATES) != null) {
                            checkpoints.add(members[i]);
                        }
                    }
                }
            }
        }
        IResource[] result = (IResource[]) checkpoints.toArray(new IResource[checkpoints.size()]);
        // sort the result
        Arrays.sort(result, new Comparator<IResource>() {
            public int compare(IResource arg0, IResource arg1) {
                return arg0.getName().compareTo(arg1.getName());
            }
        });

        return result;
    }

    private static final String CR = "\n";
    private static final String SPACE = " ";

    /**
     * Returns a possibly empty List of {@link SimpleTLCState} that represents
     * the error trace produced by the most recent run of TLC on config, if an error
     * trace was produced.
     */
    public List<SimpleTLCState> getErrorTrace() {
        /*
         * Use a file editor input and file document provider to gain access to the
         * document representation of the file containing the trace.
         */
        FileEditorInput logFileEditorInput = new FileEditorInput(getTraceSourceFile());
        FileDocumentProvider logFileDocumentProvider = new FileDocumentProvider();
        try {
            logFileDocumentProvider.connect(logFileEditorInput);
            IDocument logFileDocument = logFileDocumentProvider.getDocument(logFileEditorInput);

            FindReplaceDocumentAdapter logFileSearcher = new FindReplaceDocumentAdapter(logFileDocument);

            // the regular expression for searching for the start tag for state print outs
            String regExStartTag = MP.DELIM + MP.STARTMSG + "[0-9]{4}" + MP.COLON + MP.STATE + SPACE + MP.DELIM
                    + CR;
            // the regular expression for searching for the end tag for state print outs
            String regExEndTag = MP.DELIM + MP.ENDMSG + "[0-9]{4}" + SPACE + MP.DELIM;

            IRegion startTagRegion = logFileSearcher.find(0, regExStartTag, true, true, false, true);

            // vector of SimpleTLCStates
            Vector<SimpleTLCState> trace = new Vector<SimpleTLCState>();

            while (startTagRegion != null) {
                IRegion endTagRegion = logFileSearcher.find(startTagRegion.getOffset() + startTagRegion.getLength(),
                        regExEndTag, true, true, false, true);

                if (endTagRegion != null) {
                    int stateInputStart = startTagRegion.getOffset() + startTagRegion.getLength();
                    int stateInputLength = endTagRegion.getOffset() - stateInputStart;
                    // string from which the state can be parsed
                    String stateInputString = logFileDocument.get(stateInputStart, stateInputLength);

                    trace.add(SimpleTLCState.parseSimpleTLCState(stateInputString));

                } else {
                    TLCActivator.logDebug("Found start tag region in model log file without end tag for model "
                            + getName() + ".");
                }
                // TLCActivator.getDefault().logDebug(logFileDocument.get(startTagRegion.getOffset() + startTagRegion.getLength(),
                // endTagRegion.getOffset() - startTagRegion.getLength() - startTagRegion.getOffset()));

                startTagRegion = logFileSearcher.find(startTagRegion.getOffset() + startTagRegion.getLength(),
                        regExStartTag, true, true, false, true);
            }

            return trace;
        } catch (CoreException e) {
            TLCActivator.logError("Error connecting to model log file for model " + getName() + ".", e);
        } catch (BadLocationException e) {
            TLCActivator.logError("Error searching model log file for " + getName() + ".", e);
        } finally {
            /*
             * The document provider is not needed. Always disconnect it to avoid a memory leak.
             * 
             * Keeping it connected only seems to provide synchronization of
             * the document with file changes. That is not necessary in this context.
             */
            logFileDocumentProvider.disconnect(logFileEditorInput);
        }

        return new Vector<SimpleTLCState>();
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    public String toString() {
        // The model's full-qualified name
        return getSpec().getName() + SPEC_MODEL_DELIM + getName();
    }

    public List<String> getTraceExplorerExpressions() {
        final Vector<String> defaultValue = new Vector<String>();
        try {
            return this.launchConfig.getAttribute(IModelConfigurationConstants.TRACE_EXPLORE_EXPRESSIONS,
                    defaultValue);
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            return defaultValue;
        }
    }

    public void setTraceExplorerExpression(List<String> serializedInput) {
        // TODO Why is this written into a working copy? => One can only write
        // into a working copy, which is synced on save. Thus, make sure never
        // to write to different copies concurrently.
        try {
            getWorkingCopy().setAttribute(IModelConfigurationConstants.TRACE_EXPLORE_EXPRESSIONS, serializedInput);
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
        }
    }

    public IFile getFile() {
        return getLaunchConfiguration().getFile();
    }

    /**
     * @return The last {@link ILaunch} or <code>null</code> if {@link Model} has never been run.
     */
    public ILaunch getLastLaunch() {
        return launch;
    }

    public void launch(String mode, IProgressMonitor subProgressMonitor, boolean build) throws CoreException {
        Assert.isTrue(this.workingCopy == null, "Cannot launch dirty model, save first.");
        this.launch = this.launchConfig.launch(mode, subProgressMonitor, build);
    }

    public Model save(final IProgressMonitor monitor) {
        if (this.workingCopy != null) {
            //TODO This is a workspace operation and thus should be decoupled from the UI thread.
            try {
                this.launchConfig = this.workingCopy.doSave();
                // Null the temporary working copy . Save effectively merges the
                // changes in the working copy back into the launch config.
                this.workingCopy = null;
            } catch (CoreException shouldNotHappen) {
                TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            }
        }
        TLCActivator.logDebug("Trying to save a clean Model.");

        // Fluent
        return this;
    }

    private ILaunchConfigurationWorkingCopy getWorkingCopy() throws CoreException {
        if (this.workingCopy == null) {
            this.workingCopy = this.launchConfig.getWorkingCopy();
        }
        return this.workingCopy;
    }

    public void unsavedSetEvalExpression(final String expression) {
        setAttribute(MODEL_EXPRESSION_EVAL, expression);
    }

    public String getEvalExpression() {
        try {
            return this.launchConfig.getAttribute(MODEL_EXPRESSION_EVAL, "");
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
            return "";
        }
    }

    public int getAttribute(String key, int defaultValue) throws CoreException {
        // TODO Replace this generic lookup method with real getters for the
        // various keys. E.g. see getEvalExpression/unsavedSetEvalExpression
        return this.launchConfig.getAttribute(key, defaultValue);
    }

    public String getAttribute(String key, String defaultValue) throws CoreException {
        // TODO Replace this generic lookup method with real getters for the
        // various keys. E.g. see getEvalExpression/unsavedSetEvalExpression
        return this.launchConfig.getAttribute(key, defaultValue);
    }

    public boolean getAttribute(String key, boolean defaultValue) throws CoreException {
        // TODO Replace this generic lookup method with real getters for the
        // various keys. E.g. see getEvalExpression/unsavedSetEvalExpression
        return this.launchConfig.getAttribute(key, defaultValue);
    }

    public List<String> getAttribute(String key, List<String> defaultValue) throws CoreException {
        // TODO Replace this generic lookup method with real getters for the
        // various keys. E.g. see getEvalExpression/unsavedSetEvalExpression
        return this.launchConfig.getAttribute(key, defaultValue);
    }

    public void setAttribute(String key, int value) {
        // TODO Replace this generic lookup method with real getters for the
        // various keys. E.g. see getEvalExpression/unsavedSetEvalExpression
        try {
            getWorkingCopy().setAttribute(key, value);
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
        }
    }

    public void setAttribute(String key, String value) {
        // TODO Replace this generic lookup method with real getters for the
        // various keys. E.g. see getEvalExpression/unsavedSetEvalExpression
        try {
            getWorkingCopy().setAttribute(key, value);
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
        }
    }

    public void setAttribute(String key, boolean value) {
        // TODO Replace this generic lookup method with real getters for the
        // various keys. E.g. see getEvalExpression/unsavedSetEvalExpression
        try {
            getWorkingCopy().setAttribute(key, value);
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
        }
    }

    public void setAttribute(String key, List<String> value) {
        // TODO Replace this generic lookup method with real getters for the
        // various keys. E.g. see getEvalExpression/unsavedSetEvalExpression
        try {
            getWorkingCopy().setAttribute(key, value);
        } catch (CoreException shouldNotHappen) {
            TLCActivator.logError(shouldNotHappen.getMessage(), shouldNotHappen);
        }
    }

    /* IAdaptable */

    public <T> T getAdapter(Class<T> adapter) {
        return Platform.getAdapterManager().getAdapter(this, adapter);
    }
}