Package org.openquark.cal.services

Source Code of org.openquark.cal.services.CALWorkspace$Renaming

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* CALWorkspace.java
* Creation date: Apr 28, 2003.
* By: Edward Lam
*/
package org.openquark.cal.services;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.logging.Logger;

import org.openquark.cal.compiler.ClassInstance;
import org.openquark.cal.compiler.ClassInstanceIdentifier;
import org.openquark.cal.compiler.CompilerMessageLogger;
import org.openquark.cal.compiler.FunctionalAgent;
import org.openquark.cal.compiler.LanguageInfo;
import org.openquark.cal.compiler.ModuleContainer;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleSourceDefinition;
import org.openquark.cal.compiler.ModuleSourceDefinitionGroup;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.ScopedEntity;
import org.openquark.cal.compiler.SourceMetrics;
import org.openquark.cal.compiler.SourceMetricsManager;
import org.openquark.cal.compiler.TypeClass;
import org.openquark.cal.compiler.TypeConstructor;
import org.openquark.cal.compiler.ProgramModifier.ProgramChangeListener;
import org.openquark.cal.machine.Module;
import org.openquark.cal.machine.ProgramResourceRepository;
import org.openquark.cal.metadata.CALFeatureMetadata;
import org.openquark.cal.metadata.MetadataManager;
import org.openquark.cal.metadata.ScopedEntityMetadata;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.runtime.ResourceAccess;
import org.openquark.cal.services.Vault.VaultProvider;
import org.openquark.cal.services.WorkspaceResource.SyncTime;
import org.openquark.util.FileSystemHelper;
import org.w3c.dom.Document;


/**
* A CALWorkspace represents a set of modules from which a program can be compiled.
* A metaprogram encapsulates a compiled program and all related metadata.  It holds a bunch of metamodules.
* <p>
* A particular workspace within an environment can be located by its discrete workspace id.
* ie. a workspace instantiated with a particular id will point to the location of any previous workspace instantiated with that id.
* A special case is where the id is null, in which case the workspace is considered "nullary".
* This means that the workspace location coincides with the StandardVault.
*
* @author Edward Lam
*/
public abstract class CALWorkspace implements ResourceAccess, ProgramChangeListener{
    /*
     * TODOEL:
     *   1) add a workspace listener to listen to changes in the workspace composition? (ie. add / remove modules).
     *   2) revert a modification (add module, revert..)
     */
   
    /*
     * Logging
     */
   
    /** The namespace for log messages from this package. */
    public static final String SERVICES_LOGGER_NAMESPACE = CALWorkspace.class.getPackage().getName();
   
    /** An instance of a Logger for messages from this package. */
    static final Logger SERVICES_LOGGER = Logger.getLogger(SERVICES_LOGGER_NAMESPACE);
   
    /*
     * Members
     */
   
    /** The registry which contains the registered vault providers. */
    private final VaultRegistry vaultRegistry = new VaultRegistry();
   
    /** Map from module name to module */
    private final Map<ModuleName, MetaModule> nameToMetaModuleMap;

    /** the list of metamodules in this program,
     *  in the order in which they were originally added. */
    private final List<MetaModule> metaModuleList = new ArrayList<MetaModule>();
   
    /**
     * the first module name encountered when the workspace declaration file are loaded.
     */
    private ModuleName firstModule;

    /** The ModuleSourceDefinitionGroup for the current Workspace. */
    private ModuleSourceDefinitionGroup sourceDefinitionGroup;
   
    /** The resource path to the workspace description file. */
    private final ResourcePath.FilePath workspaceDescriptionFile;
   
    /** The virtual resource manager. */
    private final VirtualResourceManager virtualResourceManager;
   
    /** Map from module name to the vault info for that module. */
    private final Map<ModuleName, VaultElementInfo> moduleNameToVaultInfoMap = new HashMap<ModuleName, VaultElementInfo>();

    /** The revision info for the resources for modules in the workspace.
     *  Does not contain info for StandardVault modules if workspace is nullary. */
    private final ResourceRevision.Info resourceRevisionInfo = new ResourceRevision.Info();

    /** Sync time info for resources managed by this manager. */
    private final SyncTimeInfo resourceSyncTimeInfo = new SyncTimeInfo();
   
    /** The persistence manager for the workspace. */
    private final WorkspacePersistenceManager persistenceManager;

    /** The source metrics for the workspace */
    private final SourceMetricsManager sourceMetrics;
   
    /** The module container for this workspace */
    private final ModuleContainer moduleContainer;
   
   
    /**
     * A simple container class to hold info about a managed resource.
     * @author Edward Lam
     */
    static class ResourceInfo {
        private final ResourceRevision.Info resourceRevisionInfo;
        private final SyncTimeInfo resourceSyncTimeInfo;
   
        ResourceInfo(ResourceRevision.Info resourceRevisionInfo, SyncTimeInfo resourceSyncTimeInfo) {
            this.resourceRevisionInfo = resourceRevisionInfo;
            this.resourceSyncTimeInfo = resourceSyncTimeInfo;
        }
       
        /**
         * @return the resource RevisionInfo.
         */
        public ResourceRevision.Info getResourceRevisionInfo() {
            return resourceRevisionInfo;
        }
        /**
         * @return Returns the resourceSyncTimeInfo.
         */
        public SyncTimeInfo getResourceSyncTimeInfo() {
            return resourceSyncTimeInfo;
        }
    }

    /**
     * A class to track sync time for managed module resources.
     * @author Edward Lam
     */
    static class SyncTimeInfo {
   
        /**
         * The map which tracks resource sync times.
         * Map from resource type to a map for that type.
         * The map for the type maps ResourceName to SyncTime for that type.
         */
        private final Map<String, Map<ResourceName,SyncTime>> resourceSyncTimeMap = new HashMap<String, Map<ResourceName,SyncTime>>();
       
        /**
         * Constructor for an empty SyncTimeInfo.
         */
        public SyncTimeInfo() {
        }
   
        /**
         * Constructor for a SyncTimeInfo.
         * @param syncTimeList the associated SyncTimes
         */
        public SyncTimeInfo(List<SyncTime> syncTimeList) {
            updateResourceSyncTimeInfo(syncTimeList);
        }
   
        /**
         * Update some sync time info in this object.
         * @param resourceSyncTime an updated sync time.
         */
        void updateResourceSyncTime(WorkspaceResource.SyncTime resourceSyncTime) {
            String resourceType = resourceSyncTime.getResourceType();
            ResourceName resourceName = resourceSyncTime.getIdentifier().getResourceName();
           
            Map<ResourceName, SyncTime> resourceTypeMap = resourceSyncTimeMap.get(resourceType);
            if (resourceTypeMap == null) {
                resourceTypeMap = new HashMap<ResourceName, SyncTime>();
                resourceSyncTimeMap.put(resourceType, resourceTypeMap);
            }
           
            resourceTypeMap.put(resourceName, resourceSyncTime);
        }
       
        /**
         * Update some sync time info in this object.
         * @param updatedInfo some updated sync time info.
         */
        void updateResourceSyncTimeInfo(SyncTimeInfo updatedInfo) {
            for (final String updatedFeatureType : updatedInfo.resourceSyncTimeMap.keySet()) {
                Map<ResourceName,SyncTime> updatedFeatureNameToSyncTimeMap = updatedInfo.resourceSyncTimeMap.get(updatedFeatureType);
   
                for (final WorkspaceResource.SyncTime updatedSyncTime : updatedFeatureNameToSyncTimeMap.values()) {
                    updateResourceSyncTime(updatedSyncTime);
                }
            }
        }
       
        /**
         * Update some sync time info in this object.
         * @param updatedInfoList the info with which to update.
         */
        void updateResourceSyncTimeInfo(List<SyncTime> updatedInfoList) {
            for (final SyncTime resourceSyncTime : updatedInfoList) {
                updateResourceSyncTime(resourceSyncTime);
            }
        }
   
        /**
         * @return the resource types in this object.
         */
        public String[] getResourceTypes() {
            Set<String> resourceTypeSet = resourceSyncTimeMap.keySet();
            String[] returnVal = new String[resourceTypeSet.size()];
            return resourceTypeSet.toArray(returnVal);
        }
   
        /**
         * Get the resource sync time for a given module resource.
         * @param resourceIdentifier the identifier of the resource.
         * @return the sync time of that resource, or 0L if that resource is not in this object.
         */
        public long getResourceSyncTime(ResourceIdentifier resourceIdentifier) {
            Map<ResourceName,SyncTime> resourceTypeMap = resourceSyncTimeMap.get(resourceIdentifier.getResourceType());
            if (resourceTypeMap == null) {
                return 0L;
            }
           
            WorkspaceResource.SyncTime resourceSyncTime = resourceTypeMap.get(resourceIdentifier.getResourceName());
            if (resourceSyncTime == null) {
                return 0L;
            }
           
            return resourceSyncTime.getSyncTime();
        }
       
        /**
         * Get the resource sync times for a given resource type.
         * @param resourceType the type for which the resource sync times should be returned.
         * @return the resource sync times for that type.
         */
        public Set<SyncTime> getResourceSyncTimes(String resourceType) {
            Map<ResourceName,SyncTime> resourceTypeMap = resourceSyncTimeMap.get(resourceType);
            if (resourceTypeMap == null) {
                return null;
            }
            return new HashSet<SyncTime>(resourceTypeMap.values());
        }
   
        /**
         * @param resourceIdentifier the identifier of the resource whose sync info should be removed.
         * @return true if there was a sync info to remove.
         */
        public boolean removeResourceSyncTime(ResourceIdentifier resourceIdentifier) {
            Map<ResourceName,SyncTime> resourceTypeMap = resourceSyncTimeMap.get(resourceIdentifier.getResourceType());
            if (resourceTypeMap != null) {
                return resourceTypeMap.remove(resourceIdentifier.getResourceName()) != null;
            }
            return false;
           
        }
       
        /**
         * @param moduleName the name of the module whose info to remove
         */
        public void removeModule(ModuleName moduleName) {
           
            // Iterate over the maps for the resource types.
            for (final Map<ResourceName,SyncTime> resourceNameToSyncTimeMap : resourceSyncTimeMap.values()) {
                // Iterate over the mappings in the map for that resource type.
                for (Iterator<ResourceName> it = resourceNameToSyncTimeMap.keySet().iterator(); it.hasNext(); ) {
                    ResourceName resourceName = it.next();
                    CALFeatureName featureName = (CALFeatureName)resourceName.getFeatureName();
   
                    // Check that the feature name has a module name which is the same as the one we're looking for.
                    if (featureName.hasModuleName() && featureName.toModuleName().equals(moduleName)) {
                        it.remove();
                    }
                }
            }
        }
   
        /**
         * Clear all sync time info from this object.
         */
        void clear() {
            resourceSyncTimeMap.clear();
        }
    }

    /**
     * Warning- this class should only be used by the CAL services implementation. It is not part of the
     * external API of the CAL platform.
     * <p>
     * A class to represent the results of a sync operation.
     * @author Edward Lam
     */
    public static class SyncInfo {

        /** Module resources which were updated. */
        private final Set<ResourceIdentifier> updatedResourceIdentifierSet = new HashSet<ResourceIdentifier>();
       
        /** Module resources which were not updated because the workspace resource was modified.
         *   Merge is required.*/
        private final Set<ResourceIdentifier> conflictSet = new HashSet<ResourceIdentifier>();
       
        /** Module resources which were not updated because the import operation failed. */
        private final Set<ResourceIdentifier> importFailureSet = new HashSet<ResourceIdentifier>();
       
        /** Module resources which were deleted . */
        private final Set<ResourceIdentifier> deletedResourceSet = new HashSet<ResourceIdentifier>();
       
        /**
         * Constructor for a SyncInfo.
         */
        public SyncInfo() {
        }
       
        /**
         * Augment the sync info with other sync info.
         * @param info the other sync info object with which this object will be updated.
         */
        public void addInfo(SyncInfo info) {
            updatedResourceIdentifierSet.addAll(info.updatedResourceIdentifierSet);
            conflictSet.addAll(info.conflictSet);
            importFailureSet.addAll(info.importFailureSet);
            deletedResourceSet.addAll(info.deletedResourceSet);
        }

        void addUpdatedResource(ResourceIdentifier identifier) {
            updatedResourceIdentifierSet.add(identifier);
        }
       
        void addSyncConflict(ResourceIdentifier identifier) {
            conflictSet.add(identifier);
        }
       
        void addImportFailure(ResourceIdentifier identifier) {
            importFailureSet.add(identifier);
        }
       
        void addDeletedResource(ResourceIdentifier identifier) {
            deletedResourceSet.add(identifier);
        }

        /**
         * @return Module resources which were updated.
         */
        public Set<ResourceIdentifier> getUpdatedResourceIdentifiers() {
            return new HashSet<ResourceIdentifier>(updatedResourceIdentifierSet);
        }
       
        /**
         * @return Module resources which were not updated because the workspace resource was modified.
         *   Merge is required.
         */
        public Set<ResourceIdentifier> getSyncConflictIdentifiers() {
            return new HashSet<ResourceIdentifier>(conflictSet);
        }
       
        /**
         * @return Module resources which were not updated because the import operation failed. */
        public Set<ResourceIdentifier> getResourceImportFailures() {
            return new HashSet<ResourceIdentifier>(importFailureSet);
        }
       
        /**
         * @return Module resources which were deleted. */
        public Set<ResourceIdentifier> getDeletedResourceIdentifiers() {
            return new HashSet<ResourceIdentifier>(deletedResourceSet);
        }
    }
   
    /**
     * Constructor for a CALWorkspace
     * @param workspaceDescriptionFile the file containing the workspace description.
     *   This will be used with the workspace is saved and loaded.
     */
    protected CALWorkspace(ResourcePath.FilePath workspaceDescriptionFile) {
        this.nameToMetaModuleMap = new HashMap<ModuleName, MetaModule>();
        this.persistenceManager = new WorkspacePersistenceManager(this);
        this.moduleContainer = new ModuleContainer () {
            @Override
            public int getNModules() {
                return CALWorkspace.this.getNMetaModules();
            }
           
            @Override
            public ModuleTypeInfo getNthModuleTypeInfo(int i) {
                return CALWorkspace.this.getNthMetaModule(i).getModule().getModuleTypeInfo();
            }
           
            @Override
            public ModuleTypeInfo getModuleTypeInfo(ModuleName moduleName) {
                return CALWorkspace.this.getMetaModule(moduleName).getModule().getModuleTypeInfo();
            }

            @Override
            public ModuleSourceDefinition getSourceDefinition(ModuleName moduleName) {
                return CALWorkspace.this.getSourceDefinition(moduleName);
            }

            @Override
            public boolean containsModule(ModuleName moduleName) {
                return CALWorkspace.this.containsModule(moduleName);
            }

            @Override
            public ResourceManager getResourceManager(ModuleName moduleName, String resourceType) {
                return CALWorkspace.this.getResourceManager(moduleName, resourceType);
            }

            @Override
            public boolean saveMetadata(CALFeatureMetadata metadata, Status saveStatus) {
                return CALWorkspace.this.saveMetadata(metadata, saveStatus);
            }

            @Override
            public boolean renameFeature(CALFeatureName oldFeatureName, CALFeatureName newFeatureName, Status renameStatus) {
                return CALWorkspace.this.renameFeature(oldFeatureName, newFeatureName, renameStatus);
            }

            @Override
            public ISourceManager getSourceManager(final ModuleName moduleName) {
                return
                new ISourceManager(){
                    private final CALSourceManager sourceManager = CALWorkspace.this.getSourceManager(moduleName);

                    public boolean isWriteable(ModuleName moduleName) {
                        return sourceManager.isWriteable(moduleName);
                    }
                   
                    public String getSource(ModuleName moduleName, CompilerMessageLogger messageLogger){
                        ModuleSourceDefinition moduleSourceDefinition = sourceManager.getSource(moduleName, new Status("getting module source"));
                        return moduleSourceDefinition.getSourceText(new Status("reading source for refactoring"), messageLogger);
                    }

                    public boolean saveSource(ModuleName moduleName, String moduleDefinition, Status saveStatus) {
                        return sourceManager.saveSource(moduleName, moduleDefinition, saveStatus);
                    }
                };
            }
        };

        this.sourceMetrics = new SourceMetricsManager(moduleContainer);
       
        this.workspaceDescriptionFile = workspaceDescriptionFile;

        this.virtualResourceManager = new VirtualResourceManager();
    }
   
    /**
     * @return the workspace id.  Null for a nullary workspace.
     */
    public abstract String getWorkspaceID();
   
    /**
     * @return a readable string identifying the location of the workspace.
     */
    public abstract String getWorkspaceLocationString();

    /**
     * @return whether this is the "nullary" workspace.
     * What this means is that the workspace points to the same location as the StandardVault.
     */
    private boolean isNullary() {
        return getWorkspaceID() == null;
    }

    public ModuleContainer asModuleContainer(){
        return moduleContainer;
    }
   
    /**
     * Get the ModuleSourceDefinitionGroup for the current workspace.
     * @return ModuleSourceDefinitionGroup the source definitions for this workspace.
     */
    synchronized final ModuleSourceDefinitionGroup getSourceDefinitionGroup() {
        return sourceDefinitionGroup;
    }
   
    /**
     * Returns a ProgramResourceRepository.Provider that provides Car-aware ProgramResourceRepository instances.
     *
     * @param baseProvider the base repository for use by the Car-aware ProgramResourceRepository when
     *                     a module name is not found in the Car mapping.
     *
     * @return the provider for Car-aware ProgramResourceRepository instances.
     */
    ProgramResourceRepository.Provider getProviderForCarAwareProgramResourceRepository(ProgramResourceRepository.Provider baseProvider) {
        return virtualResourceManager.getProviderForCarAwareProgramResourceRepository(baseProvider);
    }

    /**
     * Add a module to this program.
     * @param module the module to add.
     */
    synchronized private final void addMetaModule(MetaModule module) {

        Assert.isNotNullArgument(module, "module");

        nameToMetaModuleMap.put(module.getName(), module);
        metaModuleList.add(module);
    }

    /**
     * Remove a named module from this program.
     * @param moduleName the name of the module to remove
     */
    synchronized private final MetaModule removeMetaModule(ModuleName moduleName) {
        MetaModule module = nameToMetaModuleMap.remove(moduleName);
        metaModuleList.remove(module);
        return module;
    }

    /**
     * Gets the module with a given moduleName.
     * @param moduleName the module name
     * @return the module with a given module name
     */
    synchronized public final MetaModule getMetaModule(ModuleName moduleName) {
        // Get the module object with the given the module name
        return nameToMetaModuleMap.get(moduleName);
    }

    /**
     * Get the number of modules in this program
     * @return int the number of modules held by this program.
     */
    synchronized public final int getNMetaModules() {
        return metaModuleList.size();
    }
   
    /**
     * Get the nth metamodule.
     * The index n is based on the order in which modules are originally added to the program.
     * @param n 0-based index of the module to return.
     * @return MetaModule the module.
     */
    synchronized public final MetaModule getNthMetaModule(int n) {
        return metaModuleList.get(n);
    }

    /**
     * Get an entity by name.
     * @param entityName the name of the entity to retrieve.
     * @return the corresponding entity, or null if the entity or module do not exist.
     */
    public GemEntity getGemEntity(QualifiedName entityName) {
        Assert.isNotNullArgument(entityName, "entityName");

        MetaModule module = getMetaModule(entityName.getModuleName());
       
        if (module == null) {
            return null;
        }
       
        return module.getGemEntity(entityName.getUnqualifiedName());
    }
   
    /**
     * Get a scoped entity by name.
     * @param featureName the name to get a scoped entity for
     * @return the scoped entity for the name or null if no such entity
     * @throws IllegalArgumentException if the feature name is not for a scoped entity
     */
    public ScopedEntity getScopedEntity(CALFeatureName featureName) {
       
        QualifiedName name = featureName.toQualifiedName();
        MetaModule metaModule = getMetaModule(name.getModuleName());
       
        if (metaModule == null) {
            return null;
        }
       
        ModuleTypeInfo moduleInfo = metaModule.getTypeInfo();
        CALFeatureName.FeatureType type = featureName.getType();
       
        if (type == CALFeatureName.FUNCTION) {
            return moduleInfo.getFunctionalAgent(name.getUnqualifiedName());
           
        } else if (type == CALFeatureName.TYPE_CONSTRUCTOR) {
            return moduleInfo.getTypeConstructor(name.getUnqualifiedName());
           
        } else if (type == CALFeatureName.TYPE_CLASS) {
            return moduleInfo.getTypeClass(name.getUnqualifiedName());
           
        } else if (type == CALFeatureName.DATA_CONSTRUCTOR) {
            return moduleInfo.getDataConstructor(name.getUnqualifiedName());
           
        } else if (type == CALFeatureName.CLASS_METHOD) {
            return moduleInfo.getClassMethod(name.getUnqualifiedName());
        }
       
        throw new IllegalArgumentException("feature type is not a for a scoped entity: " + type);
    }
   
    /**
     * Get a class instance by name.
     * @param featureName the name to get a class instance for
     * @return the class instance for the name or null if the name does not represent a class instance, or if there is no such instance
     * @throws IllegalArgumentException if the feature name is not for a class instance
     */
    public ClassInstance getClassInstance(CALFeatureName featureName) {
       
        ModuleName moduleName = featureName.toModuleName();
        ClassInstanceIdentifier identifier = featureName.toInstanceIdentifier();
       
        MetaModule metaModule = getMetaModule(moduleName);
       
        if (metaModule == null) {
            return null;
        }
       
        return metaModule.getTypeInfo().getClassInstance(identifier);
    }

    /**
     * Saves the given source definition to the manager.
     * @param sourceDefinition the source definition to save.
     * @param saveStatus the tracking status object.
     * @return whether the source was successfully saved.
     */
    private boolean saveSource(ModuleSourceDefinition sourceDefinition, Status saveStatus) {
        CALSourceManager sourceManager = virtualResourceManager.getSourceManager(sourceDefinition.getModuleName());
        if (sourceManager == null) {
            saveStatus.add(new Status(Status.Severity.WARNING, "No registered source manager."));
            return false;
        }
        return sourceManager.importResource(sourceDefinition, saveStatus) != null;
    }

    /**
     * @param moduleName the name of the module whose resource manager is to be returned.
     * @param resourceType the resource type identifier.
     * @return the manager for that resource type and for the given module in the workspace.
     */
    public ResourceManager getResourceManager(ModuleName moduleName, String resourceType) {
        return virtualResourceManager.getModuleSpecificResourceManager(moduleName, resourceType);
    }
   
    /**
     * @return the registered workspace manager, if any.
     */
    public WorkspaceDeclarationManager getWorkspaceDeclarationManager() {
        return virtualResourceManager.getWorkspaceDeclarationManager();
    }
   
    /**
     * Get the metadata for a scoped entity.
     * @param entity the entity to get metadata for
     * @param locale the locale associated with the metadata.
     * @return the metadata for the entity. If the entity has no metadata, then default metadata is returned.
     */
    public ScopedEntityMetadata getMetadata(ScopedEntity entity, Locale locale) {
        if (entity instanceof FunctionalAgent) {
            GemEntity gemEntity = getGemEntity(entity.getName());
            if (gemEntity == null) {
                throw new IllegalStateException("The given entity does not exist in the workspace.");
            }
            return gemEntity.getMetadata(locale);

        } else if (entity instanceof TypeClass) {
            return (ScopedEntityMetadata)getMetadata(CALFeatureName.getTypeClassFeatureName(entity.getName()), locale);

        } else if (entity instanceof TypeConstructor) {
            return (ScopedEntityMetadata)getMetadata(CALFeatureName.getTypeConstructorFeatureName(entity.getName()), locale);

        } else {
            throw new UnsupportedOperationException("Unknown entity class: " + entity.getClass());
        }
    }

    /**
     * @param featureName the name of the feature to get metadata for.  This feature must live in a module in this workspace.
     * @param locale the locale associated with the metadata.
     * @return the metadata for the feature. If the feature has no metadata, then default
     * metadata is returned.
     */
    public CALFeatureMetadata getMetadata(CALFeatureName featureName, Locale locale) {
        MetaModule metaModule = getMetaModule(featureName.toModuleName());
        if (metaModule == null) {
            throw new IllegalStateException("The module for the given entity does not exist in the workspace.");
        }

        MetadataManager metadataManager = virtualResourceManager.getMetadataManager(featureName.toModuleName());
        if (metadataManager == null) {
            return MetadataManager.getEmptyMetadata(featureName, locale);
        }
       
        return metadataManager.getMetadata(featureName, locale);
    }
   
    /**
     * {@inheritDoc}
     */
    public InputStream getUserResource(String moduleNameAsString, String name, String extension, Locale locale) {
        ModuleName moduleName = ModuleName.make(moduleNameAsString);
        UserResourceManager userResourceManager = virtualResourceManager.getUserResourceManager(moduleName);
        return userResourceManager.getUserResource(moduleName, name, extension, locale);
    }
   
    /**
     * Returns the resource manager for user resources of the specified module.
     * @param moduleName the module name.
     * @return the resource manager for user resources of the specified module.
     */
    UserResourceManager getUserResourceManager(final ModuleName moduleName) {
        return virtualResourceManager.getUserResourceManager(moduleName);
    }
   
    /**
     * Used for errors that occur during the renameFeature() operation.
     * @author Peter Cardwell
     */
    private static final class RenamingException extends Exception {
       
        private static final long serialVersionUID = 3985993651784449695L;
              
        RenamingException (String errorMsg) {
            super(errorMsg);
        }
       
        String getErrorMessage() {
            return getMessage();
        }
    }
   
    /**
     * A helper method to collect a list of design resources that should be renamed.
     * @param renamings
     * @param oldFeatureName
     * @param newFeatureName
     * @param renameStatus
     */
    private void collectDesignRenamings(List<Renaming> renamings, CALFeatureName oldFeatureName, CALFeatureName newFeatureName, Status renameStatus) {
        GemDesignManager oldDesignManager = virtualResourceManager.getDesignManager(oldFeatureName.toModuleName());
        if (oldDesignManager == null) {
            renameStatus.add(new Status(Status.Severity.WARNING, "The design manager for the old design does not exist."));
            return;
        }
       
        GemDesignManager newDesignManager = virtualResourceManager.getDesignManager(newFeatureName.toModuleName());
        if (newDesignManager == null) {
            renameStatus.add(new Status(Status.Severity.WARNING, "The design manager for the new design does not exist."));
            return;
        }
       
        ResourceName oldResourceName = new ResourceName(oldFeatureName);
        if (oldFeatureName.getType().equals(CALFeatureName.MODULE)) {
            MetaModule metaModule = getMetaModule(oldFeatureName.toModuleName());
            if (metaModule == null) {
                return; //throw new IllegalStateException("The module for the given entity does not exist in the workspace.");
            }
           
            for (int i = 0, n = metaModule.getNGemEntities(); i < n; i++) {
                GemEntity nextGemEntity = metaModule.getNthGemEntity(i);
               
                if (oldDesignManager.hasGemDesign(nextGemEntity)) {
                    QualifiedName nextEntityName = nextGemEntity.getName();
                    if (!nextEntityName.getModuleName().equals(oldFeatureName.toModuleName())) {
                        throw new IllegalStateException("Feature name associated with, but not contained in, the renamed module.");
                    }
                    QualifiedName nextEntityNameRenamed = QualifiedName.make(newFeatureName.toModuleName(), nextEntityName.getUnqualifiedName());
                   
                    CALFeatureName nextFeatureName = CALFeatureName.getFunctionFeatureName(nextEntityName);
                    CALFeatureName nextFeatureNameRenamed = CALFeatureName.getFunctionFeatureName(nextEntityNameRenamed);
                   
                    renamings.add(new Renaming(new ResourceName(nextFeatureName), new ResourceName(nextFeatureNameRenamed), "design", oldDesignManager, newDesignManager));
                }
            }
        } else if (oldFeatureName.getType() == CALFeatureName.FUNCTION && oldDesignManager.getResourceStore().hasFeature(oldResourceName)) {
            renamings.add(new Renaming(oldResourceName, new ResourceName(newFeatureName), "design", oldDesignManager, newDesignManager));
        }
    }
   
    /**
     * A helper method to collect a list of user resources that should be renamed.
     * @param renamings
     * @param oldFeatureName
     * @param newFeatureName
     * @param renameStatus
     */
    private void collectUserResourceRenamings(List<Renaming> renamings, CALFeatureName oldFeatureName, CALFeatureName newFeatureName, Status renameStatus) {
        ModuleName oldFeatureModuleName = oldFeatureName.toModuleName();
        UserResourceManager oldUserResourceManager = virtualResourceManager.getUserResourceManager(oldFeatureModuleName);
        if (oldUserResourceManager == null) {
            renameStatus.add(new Status(Status.Severity.WARNING, "The user resource manager for the old feature's module does not exist."));
            return;
        }
       
        ModuleName newFeatureModuleName = newFeatureName.toModuleName();
        UserResourceManager newUserResourceManager = virtualResourceManager.getUserResourceManager(newFeatureModuleName);
        if (newUserResourceManager == null) {
            renameStatus.add(new Status(Status.Severity.WARNING, "The user resource manager for the new feature's module does not exist."));
            return;
        }
       
        if (oldFeatureName.getType().equals(CALFeatureName.MODULE)) {
           
            List<ResourceName> oldUserResourceNames = ((UserResourceStore)oldUserResourceManager.getResourceStore()).getModuleResourceNameList(oldFeatureModuleName);
           
            for (int i = 0, n = oldUserResourceNames.size(); i < n; i++) {
                ResourceName oldUserResourceName = oldUserResourceNames.get(i);
                Locale locale = LocalizedResourceName.localeOf(oldUserResourceName);
                UserResourceFeatureName oldUserResourceFeatureName = (UserResourceFeatureName)oldUserResourceName.getFeatureName();
                UserResourceFeatureName newUserResourceFeatureName = new UserResourceFeatureName(newFeatureModuleName, oldUserResourceFeatureName.getName(), oldUserResourceFeatureName.getExtension());
                ResourceName newUserResourceName = new LocalizedResourceName(newUserResourceFeatureName, locale);
               
                renamings.add(new Renaming(oldUserResourceName, newUserResourceName, "userResource", oldUserResourceManager, newUserResourceManager));
            }
        }
    }
   
    /**
     * A helper method to collect a list of metadata resources that should be renamed.
     * @param renamings
     * @param oldFeatureName
     * @param newFeatureName
     * @param renameStatus
     */
    private void collectMetadataRenamings(List<Renaming> renamings, CALFeatureName oldFeatureName, CALFeatureName newFeatureName, Status renameStatus) {
        MetadataManager oldMetadataManager = virtualResourceManager.getMetadataManager(oldFeatureName.toModuleName());
        if (oldMetadataManager == null) {
            renameStatus.add(new Status(Status.Severity.WARNING, "The metadata manager for the old metadata resource does not exist."));
            return;
        }
       
        MetadataManager newMetadataManager = virtualResourceManager.getMetadataManager(newFeatureName.toModuleName());
        if (newMetadataManager == null) {
            renameStatus.add(new Status(Status.Severity.WARNING, "The metadata manager for the new metadata resource does not exist."));
            return;
        }
       
        // If this is a module, we need to update the metadata for every entity contained by it.
        if (oldFeatureName.getType().equals(CALFeatureName.MODULE)) {
            MetaModule metaModule = getMetaModule(oldFeatureName.toModuleName());
            if (metaModule == null) {
                return;
            }
           
            Set<CALFeatureName> featureNames = metaModule.getFeatureNames();
            for (final CALFeatureName nextFeatureName : featureNames) {
                List<ResourceName> metadataResourceNames = metaModule.getMetadataResourceNamesForAllLocales(nextFeatureName);
               
                if (!metadataResourceNames.isEmpty()) {
                    // Calculate the new name for this feature
                    CALFeatureName nextFeatureNameRenamed;
                    if (nextFeatureName.getType().equals(CALFeatureName.MODULE)) {
                        nextFeatureNameRenamed = newFeatureName;
                    } else if (nextFeatureName.getType().equals(CALFeatureName.CLASS_INSTANCE)) {
                        throw new IllegalStateException("Class instances shouldn't have metadata associated with them.");
                    } else {
                        QualifiedName qualifiedNextFeatureName = QualifiedName.makeFromCompoundName(nextFeatureName.getName());
                        if (!qualifiedNextFeatureName.getModuleName().equals(oldFeatureName.toModuleName())) {
                            throw new IllegalStateException("Feature name associated with, but not contained in, the renamed module.");
                        }
                        QualifiedName qualifiedNextFeatureNameRenamed = QualifiedName.make(newFeatureName.toModuleName(), qualifiedNextFeatureName.getUnqualifiedName());
                        nextFeatureNameRenamed = new CALFeatureName(nextFeatureName.getType(), qualifiedNextFeatureNameRenamed.getQualifiedName());
                    }
                   
                    for (int i = 0, n = metadataResourceNames.size(); i < n; i++) {
                        ResourceName metadataResourceName = metadataResourceNames.get(i);
                        Locale locale = LocalizedResourceName.localeOf(metadataResourceName);
                       
                        renamings.add(new Renaming(new LocalizedResourceName(nextFeatureName, locale), new LocalizedResourceName(nextFeatureNameRenamed, locale), "metadata", oldMetadataManager, newMetadataManager));
                    }
                }
            }
        } else {
            if (oldFeatureName.hasModuleName()) {
                MetaModule metaModule = getMetaModule(oldFeatureName.toModuleName());
                if (metaModule == null) {
                    return;
                }
               
                List<ResourceName> metadataResourceNames = metaModule.getMetadataResourceNamesForAllLocales(oldFeatureName);
               
                for (int i = 0, n = metadataResourceNames.size(); i < n; i++) {
                    ResourceName metadataResourceName = metadataResourceNames.get(i);
                    Locale locale = LocalizedResourceName.localeOf(metadataResourceName);
                   
                    renamings.add(new Renaming(new LocalizedResourceName(oldFeatureName, locale), new LocalizedResourceName(newFeatureName, locale), "metadata", oldMetadataManager, newMetadataManager));
                }
            }
        }
    }
   
    /**
     * A helper class representing a renaming operation. It encapsulates all the information that is needed by the workspace
     * to actually perform the renaming.
     * @author Peter Cardwell
     */
    private class Renaming {
        final ResourceName fromName;
        final ResourceName toName;
        final String renamingType;
        final ResourceManager fromResourceManager;
        final ResourceManager toResourceManager;
       
        Renaming(ResourceName fromName, ResourceName toName, String renamingType, ResourceManager fromResourceManager, ResourceManager toResourceManager) {
            this.fromName = fromName;
            this.toName = toName;
            this.renamingType = renamingType;
            this.fromResourceManager = fromResourceManager;
            this.toResourceManager = toResourceManager;
        }
    }
   
    /**
     * Rename a module in the workspace. This will only change the name of any related design,
     * metadata, and module resources and update any internal references to it in the workspace.
     * It will NOT update references to the module within any resources themselves
     * (ie. The contents of CAL sources, designs, metadata, etc. will not be updated).
     * @param oldFeatureName The name of the module to rename
     * @param newFeatureName The new name to rename the module to
     * @param renameStatus the tracking status object.
     * @return boolean whether the module was successfully renamed.
     */
    synchronized public final boolean renameFeature(CALFeatureName oldFeatureName, CALFeatureName newFeatureName, Status renameStatus) {
       
        if (!oldFeatureName.getType().equals(newFeatureName.getType())) {
            throw new IllegalArgumentException("oldFeatureName type is not the same as newFeatureName type");
        }
       
        // Collect a list of renamings to perform.
        List<Renaming> renamings = new ArrayList<Renaming>();
        collectMetadataRenamings(renamings, oldFeatureName, newFeatureName, renameStatus);
        collectDesignRenamings(renamings, oldFeatureName, newFeatureName, renameStatus);
        collectUserResourceRenamings(renamings, oldFeatureName, newFeatureName, renameStatus);
       
        if (oldFeatureName.getType().equals(CALFeatureName.MODULE)) {
            renamings.add(new Renaming(
                new ResourceName(oldFeatureName), new ResourceName(newFeatureName), "CAL source",
                virtualResourceManager.getSourceManager(oldFeatureName.toModuleName()),
                virtualResourceManager.getSourceManager(newFeatureName.toModuleName())));
        }
       
        List<Renaming> undoList = new ArrayList<Renaming>();
        for (final Renaming renaming : renamings) {
            try {
                if (!renaming.fromResourceManager.getResourceStore().isWriteable(renaming.fromName)) {
                    throw new RenamingException("The "+ renaming.renamingType + " resource for " + renaming.fromName.getFeatureName().getName() + " is not writeable.");
                }
                if (!renaming.fromResourceManager.getResourceStore().renameResource(renaming.fromName, renaming.toName, renaming.toResourceManager.getResourceStore(), new Status("Resource renaming"))) {
                    throw new RenamingException("Error renaming the " + renaming.renamingType + " resource for " + renaming.fromName.getFeatureName().getName() + ".");
                }
               
                undoList.add(renaming);
            } catch (RenamingException e) {
                renameStatus.add(new Status(Status.Severity.ERROR, e.getErrorMessage()));
               
                // Renaming failed, undo all renamings done so far and return
                for (int i = undoList.size() - 1; i >= 0; i--) {
                    Renaming undoRenaming = undoList.get(i);
                   
                    if (!undoRenaming.toResourceManager.getResourceStore().renameResource(undoRenaming.toName, undoRenaming.fromName, undoRenaming.fromResourceManager.getResourceStore(), new Status("Undo rename status"))) {
                        String msg = "FATAL: Error undoing the renaming of the " + undoRenaming.renamingType + " resource for " + undoRenaming.fromName + ".";
                        renameStatus.add(new Status(Status.Severity.ERROR, msg));
                    }
                }
               
                return false;
            }
        }

        // If the entity being renamed is a module, we need to rename the cal source itself.
        if (oldFeatureName.getType().equals(CALFeatureName.MODULE)) {
            ModuleSourceDefinition sourceToRename = sourceDefinitionGroup.getModuleSource(oldFeatureName.toModuleName());
            ModuleSourceDefinition renamedSource = virtualResourceManager.getSourceManager(newFeatureName.toModuleName()).getSource(newFeatureName.toModuleName(), renameStatus);
           
            // Now remove the reference to the old module from the source definition group and add the new module
            List<ModuleSourceDefinition> newModuleSources = new ArrayList<ModuleSourceDefinition>(Arrays.asList(sourceDefinitionGroup.getModuleSources()));
            newModuleSources.remove(sourceToRename);
            newModuleSources.add(renamedSource);
            ModuleSourceDefinition[] newModuleSourceArray = newModuleSources.toArray(ModuleSourceDefinition.EMPTY_ARRAY);
            this.sourceDefinitionGroup = new ModuleSourceDefinitionGroup(newModuleSourceArray);
           
            // Update the vault info map..
            VaultElementInfo newVaultInfo = VaultElementInfo.makeBasic("NonExistent", newFeatureName.getName(), null, 0);
            updateVaultInfo(newFeatureName.toModuleName(), newVaultInfo);
           
            ModuleName oldFeatureNameAsModuleName = oldFeatureName.toModuleName();
            moduleNameToVaultInfoMap.remove(oldFeatureNameAsModuleName);
            resourceRevisionInfo.removeModule(oldFeatureNameAsModuleName);
            resourceSyncTimeInfo.removeModule(oldFeatureNameAsModuleName);
           
            // Finally, remove the old module from the meta module list.
            removeMetaModule(oldFeatureNameAsModuleName);
        }
       
        return true;
    }
       
    /**
     * Saves the given metadata object to the workspace.
     * @param metadata the metadata object to save
     * @return true if metadata was saved, false otherwise
     */
    public boolean saveMetadata(CALFeatureMetadata metadata) {
        return saveMetadata(metadata, new Status("Save status"));
    }

    /**
     * Saves the given metadata object to the workspace.
     * @param metadata the metadata object to save
     * @param saveStatus the tracking status object.
     * @return true if metadata was saved, false otherwise
     */
    public boolean saveMetadata(CALFeatureMetadata metadata, Status saveStatus) {
        MetadataManager metadataManager = virtualResourceManager.getMetadataManager(metadata.getFeatureName().toModuleName());
        if (metadataManager == null) {
            saveStatus.add(new Status(Status.Severity.WARNING, "No registered metadata manager."));
            return false;
        }
        return metadataManager.saveMetadata(metadata, saveStatus);
    }

    /**
     * Save a design to the workspace.
     * @param design the design to save.
     * @param saveStatus the tracking status object
     */
    public void saveDesign(GemDesign design, Status saveStatus) {
        GemDesignManager designManager = virtualResourceManager.getDesignManager(design.getDesignName().getModuleName());
        if (designManager == null) {
            saveStatus.add(new Status(Status.Severity.WARNING, "No registered design manager."));
            return;
        }
        designManager.saveGemDesign(design, saveStatus);
    }
   
    /**
     * @param typeConsName the name of the type constructor.
     * @return the type constructor entity with the given name.
     */
    public TypeConstructor getTypeConstructor(QualifiedName typeConsName) {
        MetaModule metaModule = getMetaModule(typeConsName.getModuleName());
        if (metaModule == null) {
            return null;
        }
       
        return metaModule.getTypeInfo().getTypeConstructor(typeConsName.getUnqualifiedName());
    }
   
    /**
     * {@inheritDoc}
     */
    public void moduleLoaded (Module module) {
        addMetaModule (new MetaModule (module, virtualResourceManager));
        sourceMetrics.addModuleMetrics(module.getModuleTypeInfo());
    }
   
    /**
     * {@inheritDoc}
     */
    public void moduleRemoved (Module module) {
        removeMetaModule (module.getName ());
        sourceMetrics.removeModuleMetrics(module.getModuleTypeInfo());
    }
   
    /**
     * returns the name of the first module specified in the workspace definition.
     * this may return null if the workspace is not initialized, or there are no
     * modules.
     * @return the first module in the workspace, or null if the workspace is not
     * properly initialized
     */
    synchronized public final ModuleName getFirstModuleName() {
        return firstModule;
    }
   
    /**
     * Initialize the workspace from the given workspace definition.
     * Any current workspace definition will be replaced with the new one.
     * @param storedModules the modules which comprise the initial workspace definition.
     */
    synchronized public final Status initializeWorkspace(StoredVaultElement.Module[] storedModules, Status initStatus) {

        // Clear existing modules from the resource manager..
        removeAllStoredModules(initStatus);

        // record the name of the first module in the stored module list       
        if (storedModules != null && storedModules.length > 0) {
            firstModule = ModuleName.make(storedModules[0].getName());
        } else {
            firstModule = null;
        }
        // Reset the source definition group.
        this.sourceDefinitionGroup = new ModuleSourceDefinitionGroup(ModuleSourceDefinition.EMPTY_ARRAY);


        // Add new modules..
       
        // Import the stored module resources.
        List<StoredVaultElement.Module> storedModulesList = new ArrayList<StoredVaultElement.Module>(Arrays.asList(storedModules));
       
        for (Iterator<StoredVaultElement.Module> it = storedModulesList.iterator(); it.hasNext(); ) {
           
            StoredVaultElement.Module storedModule = it.next();

            if (isNullary() && !isTopLevelStoredModuleElementInStandardVault(storedModule) && !isCarBasedStoredModuleElementInStandardVault(storedModule)) {
                String warningString = "Module " + storedModule.getName() +
                        " was not imported.  Only Modules from the StandardVault or from a Car file may be used to initialize a nullary workspace.";
                initStatus.add(new Status(Status.Severity.WARNING, warningString));
               
                it.remove();
                continue;
            }
           
            importStoredModule(storedModule, initStatus);
        }
       
        // Create the module sources, and the source provider.
        ModuleSourceDefinition[] sourceDefinitions = new ModuleSourceDefinition[storedModulesList.size()];
        int index = 0;
        for (final StoredVaultElement.Module storedModule : storedModulesList) {
            ModuleName moduleName = ModuleName.make(storedModule.getName());
           
            CALSourceManager sourceManager = virtualResourceManager.getSourceManager(moduleName);
            if (sourceManager == null) {
                initStatus.add(new Status(Status.Severity.WARNING, "No registered source manager."));
                return initStatus;
            }
           
            sourceDefinitions[index] = sourceManager.getSource(moduleName, initStatus);
            index++;
        }

        this.sourceDefinitionGroup = new ModuleSourceDefinitionGroup(sourceDefinitions);
       
        // Persist the workspace description.
        if (!isNullary() && sourceDefinitionGroup.getNModules() > 0) {
            saveWorkspaceDescription(initStatus);
        }
       
        // Return the import status.
        return initStatus;
    }
   
    /**
     * Load the persisted workspace, if any.
     *
     * @param loadStatus the tracking status object.
     *   If a previously-persisted workspace exists but could not be successfully loaded, there will be errors.
     *   Otherwise, there may be info or warnings.
     * @return true if there was a previously-persisted workspace, and it was successfully loaded.
     */
    public boolean loadPersistedWorkspace(Status loadStatus) {
        // Do not load persisted workspace if nullary.
        if (isNullary()) {
            return false;
        }
       
        // workspace description..
        boolean workspaceLoaded = loadWorkspaceDescription(loadStatus);
       
        // source definition group..
        calculateSourceDefinitionGroup();

        return workspaceLoaded;
    }

    /**
     * Calculate the source definition group managed by the workspace.
     * The sourceDefinitionGroup member will be set based on the module names in the moduleNameToVaultInfo map.
     */
    synchronized private final void calculateSourceDefinitionGroup() {
        // Get the names of the modules for which resources currently managed by this manager.
        Set<ModuleName> moduleNameSet = moduleNameToVaultInfoMap.keySet();

        List<ModuleSourceDefinition> sourceDefinitionList = new ArrayList<ModuleSourceDefinition>();
       
        // Add the source definitions for the given module names.
        for (final ModuleName moduleName : moduleNameSet) {
            CALSourceManager sourceManager = virtualResourceManager.getSourceManager(moduleName);
            if (sourceManager != null) {
               
                ModuleSourceDefinition sourceDefinition = sourceManager.getSource(moduleName, new Status("Source Status"));
               
                if (sourceDefinition != null) {
                    sourceDefinitionList.add(sourceDefinition);
                }
            }
        }
       
        // Convert the list of source definitions to an array.
        ModuleSourceDefinition[] sourceDefinitions = new ModuleSourceDefinition[sourceDefinitionList.size()];
        sourceDefinitionList.toArray(sourceDefinitions);

        this.sourceDefinitionGroup = new ModuleSourceDefinitionGroup(sourceDefinitions);
    }

    /**
     * Load the internal state of the workspace from its saved description.
     *
     * @param loadStatus the tracking status object.
     *   If a previously-persisted workspace exists but could not be successfully loaded, there will be errors.
     *   Otherwise, there may be info or warnings.
     * @return true if there was a previously-persisted workspace, and it was successfully loaded.
     */
    private boolean loadWorkspaceDescription(Status loadStatus) {

        ResourcePath.FilePath workspaceDescriptionFilePath = getWorkspaceDescriptionFile();
        InputStream inputStream = NullaryEnvironment.getNullaryEnvironment().getInputStream(workspaceDescriptionFilePath, loadStatus);
        if (inputStream == null) {
            return false;
        }

        try {
            return persistenceManager.loadWorkspaceDescription(inputStream, loadStatus);

        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * Persist the workspace description.
     * @param saveStatus the tracking status object.
     * @return whether the description was successfully saved.
     */
    private boolean saveWorkspaceDescription(Status saveStatus) {
       
        // Get the workspace file..
        ResourcePath.FilePath workspaceDescriptionFilePath = getWorkspaceDescriptionFile();
        File workspaceFile = NullaryEnvironment.getNullaryEnvironment().getFile(workspaceDescriptionFilePath, true);      // assumes a file system file.
       
        // The file (if any) temporarily containing the workspace file being replaced.
        File oldDescriptionFile;
       
        if (FileSystemHelper.fileExists(workspaceFile)) {
            // Get a temp file for the old description.
            try {
                // TODOEL: create then delete?!
                oldDescriptionFile = File.createTempFile("workspace", "old");
               
                if (!oldDescriptionFile.delete()) {
                    saveStatus.add(new Status(Status.Severity.ERROR, "Could not save workspace description."));
                    return false;
                }
               
            } catch (IOException e) {
                saveStatus.add(new Status(Status.Severity.ERROR, "Could not save workspace description.", e));
                return false;
            }
           
            // Rename to the temporary file.
            workspaceFile.renameTo(oldDescriptionFile);
           
        } else {
            oldDescriptionFile = null;
        }
       
        boolean newWorkspaceCreated = false;
       
        // Create an output stream on the workspace file.
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(workspaceFile);

            // Save to the new file..
            ResourceInfo resourceInfo = new ResourceInfo(resourceRevisionInfo, resourceSyncTimeInfo);
            persistenceManager.saveWorkspaceDescription(outputStream, resourceInfo);
           
            newWorkspaceCreated = true;
           
//        } catch (FileNotFoundException e) {
//            saveStatus.add(new Status(Status.Severity.ERROR, "Could not create file for workspace description.", e));
//
        } catch (Exception e) {
            saveStatus.add(new Status(Status.Severity.ERROR, "Could not create file for workspace description.", e));
           
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                }
            }
        }
       
        if (!newWorkspaceCreated) {
            // Delete the failed workspace file.
            if (workspaceFile.exists()) {
                workspaceFile.delete();
            }
            // Restore the old workspace file.
            if (oldDescriptionFile != null && !oldDescriptionFile.renameTo(workspaceFile)) {
                // This is really bad.
                saveStatus.add(new Status(Status.Severity.ERROR, "(FATAL) Could not restore old workspace description."));
            }
            return false;
        }
       
        // Delete the old file.
        if (oldDescriptionFile != null) {
            oldDescriptionFile.delete();
        }
       
        return true;
    }

    /**
     * Get the file which describes the contents of the workspace.
     * @return the file which contains the description of the workspace.
     */
    private ResourcePath.FilePath getWorkspaceDescriptionFile() {
        return workspaceDescriptionFile;
    }

    /**
     * Add a module to the workspace.
     * @param storedModule the module to add.
     * @param checkExisting if true, this operation will fail (with appropriate Status) if the module already exists in the workspace.
     * If false, the added module will replace any existing module resources in the workspace.
     * @param addStatus the tracking status object.
     * @return boolean whether the module was successfully added.  This will fail if the module already exists.
     */
    synchronized public final boolean addModule(StoredVaultElement.Module storedModule, boolean checkExisting, Status addStatus) {
       
        // check that the module does not already exist in the workspace.
        ModuleName moduleName = ModuleName.make(storedModule.getName());
        if (moduleName == null) {
            addStatus.add(new Status(Status.Severity.ERROR, "Could not determine module name.", null));
            return false;
        }

        if (checkExisting) {
            if (containsModule(moduleName)) {
                addStatus.add(new Status(Status.Severity.ERROR, "Module " + moduleName + " already exists in the current workspace.", null));
                return false;
            }
           
            // If this is a nullary workspace, check that we are not importing something into the StandardVault which already exists.
            if (isNullary() && !isTopLevelStoredModuleElementInStandardVault(storedModule) && !isCarBasedStoredModuleElementInStandardVault(storedModule) &&
                    StandardVault.getInstance().getSourceStore().hasFeature(new ResourceName(CALFeatureName.getModuleFeatureName(moduleName)))) {   // The get shouldn't fail.
               
                String errorString = "Module " + moduleName + " conflicts with a module which exists in the StandardVault.";
                addStatus.add(new Status(Status.Severity.ERROR, errorString, null));
                return false;
            }
        }
       
        // Import the resources.
        importStoredModule(storedModule, addStatus);

        CALSourceManager sourceManager = virtualResourceManager.getSourceManager(moduleName);
        if (sourceManager == null) {
            addStatus.add(new Status(Status.Severity.WARNING, "No registered source manager."));
            return true;            // true or false??
        }
       
        // Update the module source provider.
        ModuleSourceDefinition newModuleSourceDefinition = sourceManager.getSource(moduleName, addStatus);
        List<ModuleSourceDefinition> newModuleSources = new ArrayList<ModuleSourceDefinition>(Arrays.asList(sourceDefinitionGroup.getModuleSources()));
       
        ModuleSourceDefinition existingModuleSource = sourceDefinitionGroup.getModuleSource(moduleName);
        if (existingModuleSource != null) {
            newModuleSources.remove(existingModuleSource);
        }
        newModuleSources.add(newModuleSourceDefinition);

        ModuleSourceDefinition[] newModuleSourceArray = newModuleSources.toArray(ModuleSourceDefinition.EMPTY_ARRAY);
        this.sourceDefinitionGroup = new ModuleSourceDefinitionGroup(newModuleSourceArray);

        // Persist the workspace description if not nullary.
        if (!isNullary()) {
            saveWorkspaceDescription(addStatus);
        }
       
        return true;
    }
   
    /**
     * Remove a set of modules from the workspace.
     * @param moduleNames
     * @param removeStatus
     * @return boolean whether the modules were successfully removed from the workspace.
     */
    synchronized public final boolean removeModules (Set<ModuleName> moduleNames, Status removeStatus) {
   
        List<ModuleSourceDefinition> sourcesToRemove = new ArrayList<ModuleSourceDefinition> ();
       
        // Check that all listed modules exist.
        for (final ModuleName moduleName : moduleNames) {
            if (moduleName.equals(CAL_Prelude.MODULE_NAME)) {
                removeStatus.add(new Status(Status.Severity.ERROR, "Cannot remove Cal.Core.Prelude module.", null));
                return false;
            }
            ModuleSourceDefinition sourceToRemove = sourceDefinitionGroup.getModuleSource(moduleName);
            if (sourceToRemove == null) {
                return false;
            }
            sourcesToRemove.add (sourceToRemove);
        }
       
        List<ModuleSourceDefinition> newModuleSources = new ArrayList<ModuleSourceDefinition>(Arrays.asList(sourceDefinitionGroup.getModuleSources()));
       
        for (final ModuleSourceDefinition sourceToRemove : sourcesToRemove) {
            newModuleSources.remove(sourceToRemove);
            ModuleName moduleName = sourceToRemove.getModuleName();

            // Remove module resources from the resource manager if not nullary.
            // We can also remove if nullary, if we imported (during this session) from another vault.
            VaultElementInfo vaultInfo = getVaultInfo(moduleName);
            if (!isNullary() || vaultInfo == null || !isStandardVaultDescriptor(vaultInfo.getVaultDescriptor())) {
                removeStoredModule(moduleName, removeStatus);
            }
        }
       
        ModuleSourceDefinition[] newModuleSourceArray = newModuleSources.toArray(ModuleSourceDefinition.EMPTY_ARRAY);
           
        this.sourceDefinitionGroup = new ModuleSourceDefinitionGroup(newModuleSourceArray);

        // Persist the workspace description (if not nullary).
        if (!isNullary()) {
            boolean descriptionSaved = saveWorkspaceDescription(removeStatus);
            if (!descriptionSaved) {
                return false;
            }
        }
           
        return true;
    }
   
    /**
     * Remove a module from the workspace.
     * @param moduleName the name of the module to remove.
     * @param removeStatus the tracking status object.
     * @return boolean whether the module was successfully removed from the workspace.
     */
    public boolean removeModule(ModuleName moduleName, Status removeStatus) {
        Set<ModuleName> moduleNames = new HashSet<ModuleName>();
        moduleNames.add (moduleName);
        return removeModules (moduleNames, removeStatus);
    }

    /**
     * Remove the stored resources for a module from the workspace.
     * @param moduleName the name of the module.
     * @param removeStatus the tracking status object.
     */
    private void removeStoredModule(ModuleName moduleName, Status removeStatus) {
        for (final ResourceManager resourceManager : virtualResourceManager.getResourceManagersForModule(moduleName)) {
            if (resourceManager instanceof ModuleResourceManager) {
                ((ModuleResourceManager)resourceManager).removeModuleResources(moduleName, removeStatus);
            }
        }
       
        virtualResourceManager.unlinkModuleFromCars(moduleName);
        moduleNameToVaultInfoMap.remove(moduleName);
        resourceRevisionInfo.removeModule(moduleName);
        resourceSyncTimeInfo.removeModule(moduleName);
    }
   
    /**
     * Remove all stored module resources from the manager.
     * @param removeStatus the tracking status object.
     */
    private void removeAllStoredModules(Status removeStatus) {
        if (!isNullary()) {
            // Remove all resources from the respective managers.
            virtualResourceManager.removeAllResources(removeStatus);
        }
       
        // Clear the info maps.
        moduleNameToVaultInfoMap.clear();
        resourceRevisionInfo.clear();
        resourceSyncTimeInfo.clear();
    }

    /**
     * @return The WorkspaceSourceMetricsManager associated with this workspace.  This return value is guaranteed to be non-null.
     */
    public SourceMetrics getSourceMetrics() {
        return sourceMetrics;
    }
   
    /**
     * @param moduleName the name of a module.
     * @return the source definition for the specified module, or null if there is no source definition for that module.
     */
    public ModuleSourceDefinition getSourceDefinition(ModuleName moduleName) {
        return getSourceDefinitionGroup().getModuleSource(moduleName);
    }
   
    /**
     * @param moduleName the name of a module.
     * @return whether a module with the given name exists in the workspace.
     */
    synchronized public final boolean containsModule(ModuleName moduleName) {
        return getSourceDefinition(moduleName) != null;
    }
   
    /**
     * @param moduleName the name of a module.
     * @return debugging information about the module, e.g. the actual location of the module's resources. Can be null if the module does not exist.
     */
    public String getDebugInfoForModule(ModuleName moduleName) {
        CALSourceManager sourceManager = virtualResourceManager.getSourceManager(moduleName);
        if (sourceManager == null) {
            return null;
        } else {
            CALFeatureName moduleFeatureName = CALFeatureName.getModuleFeatureName(moduleName);
            ResourceName moduleResourceName = new ResourceName(moduleFeatureName);
            return sourceManager.getDebugInfo(moduleResourceName);
        }
    }
   
    /**
     * @return debugging information about the modules, e.g. the actual location of each module's resources.
     */
    public String getDebugInfo() {
       
        StringBuilder details = new StringBuilder();
       
        ModuleName[] moduleNames = getModuleNames();
        Arrays.sort(moduleNames);
       
        int nModules = moduleNames.length;
        if (nModules > 0) {
            for (int i = 0; i < nModules; i++) {
                details.append(moduleNames[i]);
               
                VaultElementInfo vaultInfo = getVaultInfo(moduleNames[i]);
               
                if (vaultInfo != null) {
                    long moduleRevision = vaultInfo.getRevision();
                   
                    String descriptor = vaultInfo.getVaultDescriptor();
                    String locationString = vaultInfo.getLocationString();
                   
                    details.append("\n   - " + descriptor);
                    if (locationString != null) {
                        details.append("(" + locationString + ")");
                    }
                    details.append("  revision " + moduleRevision);
                }
               
                details.append("\n");
               
                String debugInfo = getDebugInfoForModule(moduleNames[i]);
               
                if (debugInfo != null) {
                    details.append("   - ").append(debugInfo).append("\n");
                }
            }
        } else {
            details.append("No modules in workspace");
        }
       
        return details.toString();
    }
   
    /**
     * Get the vault associated with a module.
     * @param moduleName the name of the module.
     * @return the vault associated with the module, or null if there is no such vault.
     */
    public Vault getVault(ModuleName moduleName) {
        VaultElementInfo currentVaultInfo = getVaultInfo(moduleName);
       
        if (currentVaultInfo instanceof VaultElementInfo.Nested) {
           
            if (currentVaultInfo.getVaultDescriptor().equals(CarVault.getVaultClassDescriptor())) {
                VaultElementInfo.Nested nestedVaultInfo = (VaultElementInfo.Nested)currentVaultInfo;
               
                VaultElementInfo outerVaultElementInfo = nestedVaultInfo.getOuterVaultElementInfo();
               
                String outerVaultDescriptor = outerVaultElementInfo.getVaultDescriptor();
                String outerVaultLocationString = outerVaultElementInfo.getLocationString();
               
                Vault outerVault = vaultRegistry.getVault(outerVaultDescriptor, outerVaultLocationString);
               
                Status status = new Status("Getting a vault");
                StoredVaultElement.Car car = outerVault.getCar(outerVaultElementInfo.getElementName(), outerVaultElementInfo.getRevision(), status);
               
                if (car == null) {
                    return null;
                } else {
                    return car.getCarVault();
                }
               
            } else {
                return null;
            }
           
        } else { // currentVaultInfo is a VaultElementInfo.Basic instance
           
            String vaultDescriptor = currentVaultInfo.getVaultDescriptor();
            String locationString = currentVaultInfo.getLocationString();
           
            return vaultRegistry.getVault(vaultDescriptor, locationString);
        }
    }
   
    /**
     * Get the storage info for a module.
     * @param moduleName the name of a module.
     * @return the storage info for the given module, or null if there is no such info known to this manager.
     */
    public VaultElementInfo getVaultInfo(ModuleName moduleName) {
        return moduleNameToVaultInfoMap.get(moduleName);
    }

    /**
     * Update the vault info associated with a module.
     * @param moduleName the name of the module whose vault info should be updated.
     * @param vaultInfo the updated vault info for the module.
     */
    void updateVaultInfo(ModuleName moduleName, VaultElementInfo vaultInfo) {
        moduleNameToVaultInfoMap.put(moduleName, vaultInfo);
    }
   
    /**
     * Update the resource info for resources in the workspace.
     * @param resourceInfo updated resource info.
     */
    void updateResourceInfo(ResourceInfo resourceInfo) {
        ResourceRevision.Info revisionInfo = resourceInfo.getResourceRevisionInfo();
        resourceRevisionInfo.updateResourceRevisionInfo(revisionInfo);
       
        SyncTimeInfo syncTimeInfo = resourceInfo.getResourceSyncTimeInfo();
        resourceSyncTimeInfo.updateResourceSyncTimeInfo(syncTimeInfo);
    }

    /**
     * @return Returns the vaultRegistry.
     */
    public VaultRegistry getVaultRegistry() {
        return vaultRegistry;
    }

    /**
     * Register a Vault Provider with this registry.
     * @param newProvider the provider to register.
     */
    public void registerVaultProvider(VaultProvider newProvider) {
        vaultRegistry.registerVaultProvider(newProvider);
    }
   
    /**
     * Register a Vault Authenticator with this registry.
     * @param newAuthenticator the authenticator to register.
     */
    public void registerVaultAuthenticator(VaultAuthenticator newAuthenticator) {
        vaultRegistry.registerVaultAuthenticator(newAuthenticator);
    }
   
    /**
     * Register a non-Car-aware resource manager with the workspace.
     * @param resourceManager the manager to register.
     */
    public void registerNonCarAwareResourceManager(ResourceManager resourceManager) {
        virtualResourceManager.registerNonCarAwareResourceManager(resourceManager);
    }
   
    /**
     * Register a Car-aware resource manager with the workspace through a provider.
     * @param provider the provider for the Car-aware resource manager.
     */
    public void registerCarAwareResourceManager(VirtualResourceManager.CarAwareResourceManagerProvider provider) {
        virtualResourceManager.registerCarAwareResourceManager(provider);
    }
   
    /**
     * Get the names of the modules in the workspace.
     * @return the names of the modules in the workspace.
     */
    synchronized public final ModuleName[] getModuleNames() {
        int nModules = sourceDefinitionGroup.getNModules();
        ModuleName[] moduleNames = new ModuleName[nModules];

        for (int i = 0; i < nModules; i++) {
            ModuleSourceDefinition sourceDefinition = sourceDefinitionGroup.getModuleSource(i);
            moduleNames[i] = sourceDefinition.getModuleName();
        }
       
        return moduleNames;
    }
   
    /**
     * Check whether the file definition of a CAL entity contains a specified string.
     * @param moduleName the module name of the entity.
     * @param unqualifiedName the unqualified name of the entity.
     * @param searchText the text to match within the CAL definition.
     *
     * @return true if search string is found; false if not
     */
    public boolean checkDefinitionContent(ModuleName moduleName, String unqualifiedName, String searchText) throws IOException {
   
        QualifiedName qualifiedScName = QualifiedName.make(moduleName, unqualifiedName);
        String beginString = "// @@@begin ";
        String endString = "// @@@end ";
        String beginNameString = beginString + qualifiedScName;
        String endNameString = endString + qualifiedScName;
   
        // Flag indicating whether search string was found
        boolean found = false;
        // Flag indicating whether currently searching inside proper section
        boolean insideSection = false;

        Reader sourceReader = null;
        try {
            ModuleSourceDefinition sourceDefinition = getSourceDefinition(moduleName);
            if (sourceDefinition == null) {
                return false;
            }

            sourceReader = sourceDefinition.getSourceReader(new Status("Reader status"));
            if (sourceReader == null) {
                return false;
            }
           
            BufferedReader bufferedReader = new BufferedReader(sourceReader);
   
            for (String lineString = bufferedReader.readLine(); lineString != null; lineString = bufferedReader.readLine()) {
               
                if (!insideSection) {
                    // We are looking for section start
                    if (lineString.startsWith(beginNameString)) {
                        // Make sure that we didn't find part of another name
                        // Grab the part of the line string that starts with the gem name
                        StringTokenizer tokenizer =
                            new StringTokenizer(lineString.substring(beginString.length()));
                        if (tokenizer.hasMoreElements() && tokenizer.nextToken().equals(qualifiedScName.getQualifiedName())) {
                            insideSection = true;
                        }
                    }
   
                } else {
                    // We are inside section, looking for match or section end
                    if (lineString.indexOf(searchText) >= 0) {
                        found = true;
                        break;
                    }
                       
                    if (lineString.startsWith(endNameString)) {
   
                        // Make sure that we didn't find part of another name
                        // Grab the part of the line string that starts with the gem name
                        StringTokenizer tokenizer =
                            new StringTokenizer(lineString.substring(endString.length()));
                        if (tokenizer.hasMoreElements() && tokenizer.nextToken().equals(qualifiedScName.getQualifiedName())) {
                            insideSection = false;
                            break;
                        }
                    }
                }
            }
        } catch (FileNotFoundException fnfe) {
           
            // Couldn't find the file for the given module.
            // Re-throw the exception with a proper error message.
            throw new FileNotFoundException("Couldn't find the file for module " + moduleName + ".");
   
        } finally {
            // Close the file now
            try {
                if (sourceReader != null){
                    sourceReader.close();
                }
            } catch (IOException e) { // do nothing
            }
        }
       
        return found;
    }

    /**
     * Save an entity to the workspace.
     * @param entityName the name of the entity to save.
     * @param definitionText the text of the definition.
     * @param metadata any metadata to save with the entity.  If null, no metadata will be associated with the entity.
     * @param gemDesignDocument any design to associate with the saved entity.  If null, no design will be associated with the entity.
     * @return Status the status of the save.  If the definition could not be saved, this will be level Status.Severity.ERROR.
     *   Other failures will result in WARNING.  Otherwise, they will be Status.Severity.OK.
     */
    public Status saveEntity(QualifiedName entityName, String definitionText, ScopedEntityMetadata metadata, Document gemDesignDocument) {
       
        boolean scExists = getGemEntity(entityName) != null;
        if (LanguageInfo.isValidFunctionName(entityName.getUnqualifiedName())) {
            scExists = getGemEntity(entityName) != null;
        } else {
            scExists = getTypeConstructor(entityName) != null;
        }
   
        Status saveStatus = new Status("Save status");
       
        // Save it out.
        boolean saveSucceeded = saveDefinition(entityName, definitionText, scExists, saveStatus);
        if (!saveSucceeded) {
            saveStatus.add(new Status(Status.Severity.ERROR, "Failed to save gem definition.", null));
            return saveStatus;
        }
       
        if (metadata != null) {
            boolean metadataSaveSucceeded = saveMetadata(metadata, saveStatus);
            if (!metadataSaveSucceeded) {
                saveStatus.add(new Status(Status.Severity.WARNING, "Failed to save gem metadata.", null));
            }
        }
   
        if (gemDesignDocument != null) {
            saveDesign(new GemDesign(entityName, gemDesignDocument), saveStatus);
        }
       
        return saveStatus;
    }

    /**
     * Save out the definition for a CAL entity.
     * @param entityName the name of the entity.
     * @param definitionText the text of the CAL definition.
     * @param scExists whether the entity already exists in this module.
     * @param saveStatus the tracking status object.
     * @return whether the definition was sucessfully saved.
     */
    private boolean saveDefinition(QualifiedName entityName, String definitionText, boolean scExists, Status saveStatus) {
       
        final ModuleName moduleName = entityName.getModuleName();
       
        String beginString = "// @@@begin ";
        String endString = "// @@@end ";
        String beginNameString = beginString + entityName;
        String endNameString = endString + entityName;
       
        ModuleSourceDefinition sourceDefinition = getSourceDefinition(moduleName);
        if (sourceDefinition == null) {
            return false;
        }
       
        Reader sourceReader = sourceDefinition.getSourceReader(null);
        if (sourceReader == null) {
            String message = "Couldn't read definition for \"" + entityName + "\".";
            saveStatus.add(new Status(Status.Severity.ERROR, message));
            return false;
        }
        BufferedReader bufferedReader = new BufferedReader(sourceReader);
       
        try {
            // The new file text..
            StringBuilder newFileTextBuilder = new StringBuilder();
           
            if (!scExists) {
                // The gem does not exist.  Simply append its text to the end of the file.
               
                try {
                    // Add all the file text
                    for (String lineString = bufferedReader.readLine(); lineString != null; lineString = bufferedReader.readLine()) {
                        newFileTextBuilder.append(lineString + "\n");
                    }
                } catch (IOException ioe) {
                    String message = "Couldn't read definition for \"" + entityName + "\".";
                    saveStatus.add(new Status(Status.Severity.ERROR, message, ioe));
                    return false;
                }
               
                // Add the gem definition.
                newFileTextBuilder.append(getDefinition(definitionText, beginNameString, endNameString));
               
            } else {
                // The gem exists.  Attempt to replace its text in the file.
               
                // Read in all the file text
                List<String> fileText = new ArrayList<String>();
                int beginIndex = -1;
                int endIndex = -1;
                boolean shouldEatOneMoreLine = false;
                try {
                    for (String lineString = bufferedReader.readLine(); lineString != null; lineString = bufferedReader.readLine()) {
                       
                        // Add the text to the List
                        fileText.add(lineString);
                        int lineIndex = fileText.size() - 1;            // ArrayList size is a (faster) variable lookup
                       
                        // Keep track of where to replace the gem text
                        if (beginIndex < 0){
                            if (lineString.startsWith(beginNameString)) {
                               
                                // Make sure that we didn't find part of another name
                                // Grab the part of the line string that starts with the gem name
                                String endLineString = lineString.substring(beginString.length());
                               
                                // the first token should be the gem's name
                                StringTokenizer tokenizer = new StringTokenizer(endLineString);
                                if (tokenizer.hasMoreElements() && tokenizer.nextToken().equals(entityName.getQualifiedName())) {
                                    beginIndex = lineIndex;
                                }
                            }
                           
                        } else if (endIndex < 0) {
                            if (lineString.startsWith(endNameString)) {
                               
                                // Make sure that we didn't find part of another name
                                // Grab the part of the line string that starts with the gem name
                                String endLineString = lineString.substring(endString.length());
                               
                                // the first token should be the gem's name
                                StringTokenizer tokenizer = new StringTokenizer(endLineString);
                                if (tokenizer.hasMoreElements() && tokenizer.nextToken().equals(entityName.getQualifiedName())) {
                                    endIndex = lineIndex;
                                }
                            }
                        }
                       
                        // See if the line following the line with the end marker is blank.
                        if (endIndex >= 0 && lineIndex == endIndex + 1) {
                           
                            // Convert to a char array
                            char[] charArray = lineString.toCharArray();
                           
                            // Now check each character (if any) to see if they're whitespace
                            boolean isBlank = true;
                            for (int i = 0; i < charArray.length; i++) {
                                if (!Character.isWhitespace(charArray[i])) {
                                    isBlank = false;
                                    break;
                                }
                            }
                            shouldEatOneMoreLine = isBlank;
                        }
                    }
                } catch (IOException ioe) {
                    String message = "Couldn't read definition for \"" + entityName + "\".";
                    saveStatus.add(new Status(Status.Severity.ERROR, message, ioe));
                    return false;
                }
               
                // Replace the line following the line with the end marker, if appropriate
                if (shouldEatOneMoreLine) {
                    endIndex++;
                }
               
                if (beginIndex < 0 || endIndex < 0) {
                    // couldn't find where to replace the lines..
                    // the finally clause should take care of closing everything
                    String markersString;
                    if (beginIndex < 0 && endIndex < 0) {
                        markersString = "begin or end";
                    } else if (beginIndex < 0) {
                        markersString = "begin";
                    } else {
                        markersString = "end";
                    }
                   
                    String message = "Couldn't find " + markersString + " marker for \"" + entityName + "\".";
                    saveStatus.add(new Status(Status.Severity.ERROR, message, null));
                    return false;
                }
               
                //
                // Add all the file text
                //
               
                List<String> beforeText = fileText.subList(0, beginIndex);
                List<String> afterText = fileText.subList(endIndex + 1, fileText.size());
               
                // Add text appearing before the gem definition
                for (final String writeString : beforeText) {
                    newFileTextBuilder.append(writeString + "\n");
                }
               
                // Add the gem definition
                newFileTextBuilder.append(getDefinition(definitionText, beginNameString, endNameString));
               
                // Add text appearing after the gem definition
                for (final String writeString : afterText) {
                    newFileTextBuilder.append(writeString + "\n");
                }
            }
       
            // Convert to a moduleSource.
            final String newFileTextString = newFileTextBuilder.toString();
           
            ModuleSourceDefinition sourceToSave = new StringModuleSourceDefinition(moduleName, newFileTextString);
           
            // Save..
            return saveSource(sourceToSave, saveStatus);
           
        } finally {
            try {
                bufferedReader.close();
            } catch (IOException e) {
            }
        }
       
    }

    /**
     * Get CAL definition text..
     *
     * @param definitionText the text of a CAL definition.
     * @param beginString the string with which to start the definition.  Should be a CAL comment.
     * @param endString the string with which to end the definition.  Should be a CAL comment.
     * @return the string definition.
     */
    private static String getDefinition(String definitionText, String beginString, String endString) {
   
        StringBuilder sb = new StringBuilder();
       
        Date date = new Date();
   
        sb.append(beginString + " saved " + date + ".\n");
        sb.append("// Warning: this section may be automatically regenerated by the GemCutter.\n");
       
        sb.append(definitionText);
   
        sb.append(endString + "\n");
        sb.append("\n");
       
        return sb.toString();
    }

    /**
     * @param moduleName the name of the module whose associated source manager is to be returned.
     * @return a source manager for the specified module.
     */
    public CALSourceManager getSourceManager(ModuleName moduleName) {
        // TODOEL: reduce scope.  This is here for hacks in the GemCutter, the ExcludeTestModulesFilter, and JFit.
        // todo-jowong edit the note above when ExcludeTestModulesFilter is refactored
        return virtualResourceManager.getSourceManager(moduleName);
    }

    /**
     * @return a ModuleNameToResourceManagerMapping that can be used to fetch the
     *         source manager for a particular module.
     */
    public VirtualResourceManager.ModuleNameToResourceManagerMapping getModuleNameToSourceManagerMapping() {
        // TODOEL: reduce scope.  This is here for the RenameRefactorer, which is in the compiler package.
        return virtualResourceManager.getModuleNameToSourceManagerMapping();
    }

    /**
     * Import stored module resources into the workspace.
     * @param storedModule the stored module resource.
     * @param importStatus the tracking status object.
     */
    private void importStoredModule(StoredVaultElement.Module storedModule, Status importStatus) {
       
        ModuleName moduleName = ModuleName.make(storedModule.getName());
        VaultElementInfo storedModuleVaultInfo = storedModule.getVaultInfo();
       
        if (storedModuleVaultInfo instanceof VaultElementInfo.Nested) {
            importStoredModuleFromNestedVault(moduleName, storedModuleVaultInfo, importStatus);
        } else {
            importStoredModuleFromTopLevelVault(storedModule, moduleName, storedModuleVaultInfo, importStatus);
        }
    }

    /**
     * Import the resources of a stored module that resides in a top-level vault (e.g. the StandardVault or the EnterpriseVault).
     * @param storedModule the stored module.
     * @param moduleName the name of the stored module.
     * @param storedModuleVaultInfo the vault element info for the stored module.
     * @param importStatus the tracking status object.
     */
    private void importStoredModuleFromTopLevelVault(StoredVaultElement.Module storedModule, ModuleName moduleName, VaultElementInfo storedModuleVaultInfo, Status importStatus) {
        // Check for an import from the StandardVault into a nullary workspace.
        if (!(isNullary() && isTopLevelStoredModuleElementInStandardVault(storedModule))) {

            for (Iterator<WorkspaceResource> it = storedModule.getResourceIterator(); it.hasNext(); ) {
                WorkspaceResource storedModuleResource = it.next();
                String resourceType = storedModuleResource.getResourceType();
               
                ResourceManager resourceManager = virtualResourceManager.getModuleSpecificResourceManager(moduleName, resourceType);
                if (resourceManager == null) {
                    importStatus.add(new Status(Status.Severity.ERROR, "Could not find resource manager for resource type " + resourceType + " for module " + moduleName));
                    continue;
                }
               
                WorkspaceResource.SyncTime syncTime = resourceManager.importResource(storedModuleResource, importStatus);
                if (syncTime == null) {
                    importStatus.add(new Status(Status.Severity.ERROR, "Could not import resource " + storedModuleResource.getIdentifier()));
                    continue;
                }
               
                resourceSyncTimeInfo.updateResourceSyncTime(syncTime);
            }
           
            // Note: only update the resource revision info if necessary, as this can be expensive to calculate.
            resourceRevisionInfo.updateResourceRevisionInfo(storedModule.getResourceRevisionInfo());
        }
       
        // disassociate this module from any Cars
        virtualResourceManager.unlinkModuleFromCars(moduleName);
       
        // Update the stored module info maps.
        updateVaultInfo(moduleName, storedModuleVaultInfo);
    }

    /**
     * Import the resources of a stored module that resides in a nested vault (e.g. from a Car).
     * @param moduleName the name of the stored module.
     * @param storedModuleVaultInfo the vault element info for the stored module.
     * @param importStatus the tracking status object.
     */
    private void importStoredModuleFromNestedVault(ModuleName moduleName, VaultElementInfo storedModuleVaultInfo, Status importStatus) {
        if (!storedModuleVaultInfo.getVaultDescriptor().equals(CarVault.getVaultClassDescriptor())) {
            importStatus.add(new Status(Status.Severity.ERROR, "The nested vault " + storedModuleVaultInfo.toString() + " is not supported"));
            return;
        }
       
        VaultElementInfo.Nested nestedElementInfo = (VaultElementInfo.Nested)storedModuleVaultInfo;
        VaultElementInfo.Basic outerElementInfo = nestedElementInfo.getOuterVaultElementInfo();
       
        String carName = outerElementInfo.getElementName();
       
        CarManager carManager = virtualResourceManager.getCarManager();
       
        // Check for an import from the StandardVault into a nullary workspace.
        if (!(isNullary() && isStandardVaultDescriptor(outerElementInfo.getVaultDescriptor()))) {
           
            boolean carAlreadyImported = carManager.getResourceStore().hasFeature(new ResourceName(CarFeatureName.getCarFeatureName(carName)));
           
            // import the Car if it is not already imported
            if (!carAlreadyImported) {
               
                Vault vaultContainingCar = vaultRegistry.getVault(outerElementInfo.getVaultDescriptor(), outerElementInfo.getLocationString());
                if (vaultContainingCar == null) {
                    importStatus.add(new Status(Status.Severity.ERROR, "Error getting the " + outerElementInfo.getVaultDescriptor() + " containing the Car"));
                    return;
                }
               
                Car car = vaultContainingCar.getCarAsResource(carName, outerElementInfo.getRevision(), importStatus);
                if (car == null) {
                    importStatus.add(new Status(Status.Severity.ERROR, "Error getting the Car " + carName + " from the " + outerElementInfo.getVaultDescriptor()));
                    return;
                }
               
                WorkspaceResource.SyncTime syncTime = carManager.importResource(car, importStatus);
                if (syncTime == null) {
                    importStatus.add(new Status(Status.Severity.ERROR, "Could not import resource " + car.getIdentifier()));
                    return;
                }
               
                resourceSyncTimeInfo.updateResourceSyncTime(syncTime);
               
                // update the resource revision info.
                ResourceRevision.Info resourceRevisionInfo = new ResourceRevision.Info(
                    Collections.singletonList(
                        new ResourceRevision(car.getIdentifier(), outerElementInfo.getRevision())));
               
                resourceRevisionInfo.updateResourceRevisionInfo(resourceRevisionInfo);
               
                // associate this module with the Car
                virtualResourceManager.linkModuleWithCar(moduleName, car.getAccessor(importStatus));
               
            } else {
                CarStore carStore = (CarStore)carManager.getResourceStore();
               
                Car car = carStore.getCar(carName);
                if (car == null) {
                    importStatus.add(new Status(Status.Severity.ERROR, "Error getting the Car " + carName + " from the CarStore"));
                    return;
                }
               
                // associate this module with the Car
                virtualResourceManager.linkModuleWithCar(moduleName, car.getAccessor(importStatus));
            }
        } else {
            CarStore carStore = (CarStore)carManager.getResourceStore();
           
            Car car = carStore.getCar(carName);
            if (car == null) {
                importStatus.add(new Status(Status.Severity.ERROR, "Error getting the Car " + carName + " from the CarStore"));
                return;
            }
           
            // associate this module with the Car
            virtualResourceManager.linkModuleWithCar(moduleName, car.getAccessor(importStatus));
        }
       
        // now import the stored module
        updateVaultInfo(moduleName, storedModuleVaultInfo);
    }
   
    /**
     * @param storedModule a stored module.
     * @return whether the given stored module is from the StandardVault
     */
    private boolean isTopLevelStoredModuleElementInStandardVault(StoredVaultElement.Module storedModule) {
        return isStandardVaultDescriptor(storedModule.getVaultInfo().getVaultDescriptor());
    }
   
    /**
     * @param descriptorString a vault descriptor string.
     * @return whether the given string is the descriptor for the StandardVault.
     */
    private boolean isStandardVaultDescriptor(String descriptorString) {
        return descriptorString.equals(StandardVault.getVaultClassProvider().getVaultDescriptor());
    }
   
    /**
     * @param storedModule a stored module.
     * @return whether the given stored module is from a Car in the StandardVault.
     */
    private boolean isCarBasedStoredModuleElementInStandardVault(StoredVaultElement.Module storedModule) {
       
        VaultElementInfo vaultInfo = storedModule.getVaultInfo();
       
        if (vaultInfo instanceof VaultElementInfo.Nested) {
            VaultElementInfo.Nested nestedVaultInfo = (VaultElementInfo.Nested)vaultInfo;
           
            return nestedVaultInfo.getVaultDescriptor().equals(CarVault.getVaultClassDescriptor()) &&
                isStandardVaultDescriptor(nestedVaultInfo.getOuterVaultElementInfo().getVaultDescriptor());
        }
        return false;
    }
   
    /**
     * Synchronize a module with the vault with which it is associated.
     *
     * @param moduleName the name of the module to synchronize.
     * @param syncStatus the tracking status object.
     * @return the result of the sync.
     */
    public SyncInfo syncModule(ModuleName moduleName, Status syncStatus) {
        return syncModuleToRevision(moduleName, -1, false, syncStatus);
    }
   
    /**
     * Synchronize a module with a given revision, with respect to the vault with which it is associated.
     *
     * @param moduleName the name of the module to synchronize.
     * @param revisionNum the revision number of the module.
     *   If <0, will sync to the latest revision.
     *   Otherwise, will sync to the given revision, even if that revision is less than the current revision.
     * @param syncStatus the tracking status object.
     * @param force if true, merge / conflicts will be ignored.  Any changes to resources will be clobbered.
     * @return the result of the sync.
     */
    public SyncInfo syncModuleToRevision(ModuleName moduleName, int revisionNum, boolean force, Status syncStatus) {
       
        SyncInfo syncInfo = new SyncInfo();
       
        // Get the vault for the module.
        Vault vault = getVault(moduleName);
       
        if (vault == null) {
            syncStatus.add(new Status(Status.Severity.ERROR, "The vault for module " + moduleName + " is not accessible."));
            return syncInfo;
        }
       
        // Check for a sync from the StandardVault to the nullary workspace, or a sync from the CarVault (which is an unmodifiable vault)
        String vaultDescriptor = vault.getVaultProvider().getVaultDescriptor();
        if ((isNullary() && isStandardVaultDescriptor(vaultDescriptor)) || CarVault.getVaultClassDescriptor().equals(vaultDescriptor)) {
            // Always sync'd.
            return syncInfo;
        }
       
        if (!(vault instanceof NonExistentVault)) {
            // Get the stored module from the vault.
            StoredVaultElement.Module vaultStoredModule = vault.getStoredModule(moduleName, revisionNum, syncStatus);
            if (vaultStoredModule == null) {
                syncStatus.add(new Status(Status.Severity.ERROR, "Could not retrieve info for module " + moduleName + "."));
                return syncInfo;
            }
           
            syncModuleToStoredModule(vaultStoredModule, force, syncStatus, syncInfo);
        }
       
        return syncInfo;
    }
   
    /**
     * Synchronize a module with a given stored module.
     *
     * @param storedModule the stored module to with which to synchronize.
     * @param syncStatus the tracking status object.
     * @param force if true, merge / conflicts will be ignored.  Any changes to resources will be clobbered.
     *   if false, merge / conflicts will be noted, but affected resources will not be updated.
     * @return the result of the sync.
     */
    public SyncInfo syncModuleToStoredModule(StoredVaultElement.Module storedModule, boolean force, Status syncStatus) {
        SyncInfo syncInfo = new SyncInfo();
        syncModuleToStoredModule(storedModule, force, syncStatus, syncInfo);
        return syncInfo;
    }
   
    /**
     * Synchronize a module with a given stored module.
     *
     * @param storedModule the stored module to with which to synchronize.
     * @param syncStatus the tracking status object.
     * @param force if true, merge / conflicts will be ignored.  Any changes to resources will be clobbered.
     *   if false, merge / conflicts will be noted, but affected resources will not be updated.
     * @param syncInfo the object to which to add the result of the sync.
     */
    synchronized private final void syncModuleToStoredModule(StoredVaultElement.Module storedModule, boolean force, Status syncStatus, SyncInfo syncInfo) {
        // TODOEL: syncing to a non-existent module is equivalent to addModule().
        //  However, in the case of a sync, the results should be recorded in the sync info object.
       
        ModuleName moduleName = ModuleName.make(storedModule.getName());
        int revisionNum = storedModule.getVaultInfo().getRevision();
       
        VaultElementInfo currentVaultInfo = getVaultInfo(moduleName);
        if (!force && currentVaultInfo != null && !currentVaultInfo.sameVault(storedModule.getVaultInfo())) {
            String statusMessage = "Module \"" + moduleName + "\" not synced." +
                                   "  Attempt to sync to a vault different from the module's originating vault.";
            syncStatus.add(new Status(Status.Severity.WARNING, statusMessage));
            return;
        }
       
        // Get the resource revision info for the vault store module.
        ResourceRevision.Info vaultResourceRevisionInfo = storedModule.getResourceRevisionInfo();

        // Map from resource type to the resources of that type from the module revision from the vault.
        Map<String, Set<WorkspaceResource>> resourceTypeToResourcesFromVaultMap = new HashMap<String, Set<WorkspaceResource>>();

        // Populate the map.
        for (Iterator<WorkspaceResource> it = storedModule.getResourceIterator(); it.hasNext(); ) {
            WorkspaceResource vaultStoredModuleResource = it.next();
            String resourceType = vaultStoredModuleResource.getResourceType();

            Set<WorkspaceResource> resourceTypeSet = resourceTypeToResourcesFromVaultMap.get(resourceType);
            if (resourceTypeSet == null) {
                resourceTypeSet = new HashSet<WorkspaceResource>();
                resourceTypeToResourcesFromVaultMap.put(resourceType, resourceTypeSet);
            }
           
            resourceTypeSet.add(vaultStoredModuleResource);
        }
       
        // To calculate deleted resources, first get all the resources.
        //   Later, we'll remove the resources which sync'd.
        Set<ResourceIdentifier> deletedResourceIdentifierSet = new HashSet<ResourceIdentifier>();
        for (final ResourceManager resourceManager : virtualResourceManager.getResourceManagersForModule(moduleName)) {
            // Iterate over the resources managed by the manager.  Add the identifier.
            for (Iterator<WorkspaceResource> it = resourceManager.getResourceStore().getResourceIterator(); it.hasNext(); ) {
                WorkspaceResource workspaceResource = it.next();
                FeatureName featureName = workspaceResource.getIdentifier().getFeatureName();
               
                if (featureName instanceof CALFeatureName) {
                    CALFeatureName calFeatureName = (CALFeatureName)featureName;
                    if (calFeatureName.hasModuleName() && calFeatureName.toModuleName().equals(moduleName)) {
                        deletedResourceIdentifierSet.add(workspaceResource.getIdentifier());
                    }
                }
            }
        }

        // Whether this is an absolute revision num, ie. sync to a given revision num.  -1 would mean sync to latest.
        boolean syncToSpecificRevision = revisionNum > 0;
       
        // Now populate each of the affected managers.
        for (final Map.Entry<String, Set<WorkspaceResource>> entry : resourceTypeToResourcesFromVaultMap.entrySet()) {
           
            final String resourceType = entry.getKey();
           
            ResourceManager resourceManager = virtualResourceManager.getModuleSpecificResourceManager(moduleName, resourceType);
            if (resourceManager == null) {
                String statusMessage = "Resources could not be imported for resources of type " + resourceType + " -- no registered resource manager.";
                syncStatus.add(new Status(Status.Severity.WARNING, statusMessage));
                continue;
            }
           
            // Either / both of: out of date, modified.
            // If not a forced sync, bring up to date ONLY if out of date and not modified.
           
            Set<WorkspaceResource> vaultResourceSet = entry.getValue();
           
            for (final WorkspaceResource moduleResource : vaultResourceSet) {
                ResourceIdentifier resourceIdentifier = moduleResource.getIdentifier();
               
                long currentRevision = resourceRevisionInfo.getResourceRevision(resourceIdentifier);
                long revisionToWhichToSync = syncToSpecificRevision ?
                        revisionNum : vaultResourceRevisionInfo.getResourceRevision(resourceIdentifier);
               
                long resourceSyncTime = resourceSyncTimeInfo.getResourceSyncTime(resourceIdentifier);
                long lastModified = resourceManager.getTimeStamp(resourceIdentifier.getResourceName());
               
                // Check for eligibility for import.
                boolean resourceWantsUpdate;
                if (force) {
                    resourceWantsUpdate = resourceSyncTime != lastModified;
                } else {
                    // This includes the case where current revision == 0 (ie. does not exist).
                    resourceWantsUpdate = currentRevision != revisionToWhichToSync;
                }
               
                if (resourceWantsUpdate) {
                   
                    // Check that the resource is not modified, such that it requires merging.
                   
                    if (force || resourceSyncTime == 0 || resourceSyncTime == lastModified) {
                       
                        // Import the updated resource.
                        WorkspaceResource.SyncTime newSyncTime = resourceManager.importResource(moduleResource, syncStatus);
                       
                        // Update the sync time and add to the set of imported resources.
                        if (newSyncTime == null) {
                            syncInfo.addImportFailure(moduleResource.getIdentifier());
                           
                        } else {
                            resourceRevisionInfo.updateResourceRevision(resourceIdentifier, revisionToWhichToSync);
                            resourceSyncTimeInfo.updateResourceSyncTime(newSyncTime);
                            syncInfo.addUpdatedResource(moduleResource.getIdentifier());
                        }
                       
                        // Update the stored module info map.
                       
                    } else {
                        // Merge / conflict
                        syncInfo.addSyncConflict(moduleResource.getIdentifier());
                    }
                }
               
                // Remove from the deleted resources set.
                deletedResourceIdentifierSet.remove(resourceIdentifier);
            }
        }
       
        // Delete deleted resources.
        // If a resource exists in the workspace but not in the repository, it is either
        //   1) Added by the workspace session, or
        //   2) Deleted from the repository.
        // We can distinguish these, because if a resource comes from a vault, it has a sync time
        //   (at least, for non-nullary std vault case) and we are in case 1.
        for (final ResourceIdentifier deletedResourceIdentifier : deletedResourceIdentifierSet) {
            String resourceType = deletedResourceIdentifier.getResourceType();
            ResourceName resourceName = deletedResourceIdentifier.getResourceName();
           
            // Remove the resource from the manager.
            ResourceManager resourceManager = virtualResourceManager.getModuleSpecificResourceManager(moduleName, resourceType);
            resourceManager.removeResource(resourceName, syncStatus);
           
            // If removed, update the sync info.
            if (!resourceManager.getResourceStore().hasFeature(resourceName)) {
                syncInfo.addDeletedResource(deletedResourceIdentifier);
                resourceRevisionInfo.removeResourceRevision(deletedResourceIdentifier);
                resourceSyncTimeInfo.removeResourceSyncTime(deletedResourceIdentifier);
            }
        }
       
        // Update the stored module info map.
        updateVaultInfo(moduleName, storedModule.getVaultInfo());
       
        CALSourceManager sourceManager = virtualResourceManager.getSourceManager(moduleName);
        if (sourceManager == null) {
            syncStatus.add(new Status(Status.Severity.WARNING, "No registered source manager."));

        } else {
            // Get the old module sources, remove the old source definition (if any) and add the new one.
            List<ModuleSourceDefinition> newModuleSources = new ArrayList<ModuleSourceDefinition>(Arrays.asList(sourceDefinitionGroup.getModuleSources()));
            for (Iterator<ModuleSourceDefinition> it = newModuleSources.iterator(); it.hasNext(); ) {
                ModuleSourceDefinition moduleSourceDefinition = it.next();
                if (moduleSourceDefinition.getModuleName().equals(moduleName)) {
                    it.remove();
                    break;
                }
            }
            ModuleSourceDefinition newModuleSourceDefinition = sourceManager.getSource(moduleName, syncStatus);
            newModuleSources.add(newModuleSourceDefinition);

            // Create the new source provider.
            ModuleSourceDefinition[] newModuleSourceArray = newModuleSources.toArray(ModuleSourceDefinition.EMPTY_ARRAY);
            this.sourceDefinitionGroup = new ModuleSourceDefinitionGroup(newModuleSourceArray);
           
            // Persist the workspace description.
            if (!isNullary() && sourceDefinitionGroup.getNModules() > 0) {
                saveWorkspaceDescription(syncStatus);
            }
        }
    }
   
    /**
     * Get the vault status of module resources in the workspace.
     * @return the vault-related status of the module resources in the workspace.
     */
    public VaultStatus getVaultStatus() {
       
        VaultStatus vaultStatus = new VaultStatus();

        // Iterate over the modules in the workspace which have any vault info.
        for (final ModuleName moduleName : moduleNameToVaultInfoMap.keySet()) {
            getVaultStatus(moduleName, vaultStatus);
        }
       
        return vaultStatus;
    }
   
    /**
     * Get the vault status for a given module.
     * @param moduleName the name of the module.
     * @return the vault-related status of the module's resources in the workspace.
     */
    public VaultStatus getVaultStatus(ModuleName moduleName) {
        VaultStatus vaultStatus = new VaultStatus();
        getVaultStatus(moduleName, vaultStatus);

        return vaultStatus;
    }
   
    /**
     * Get the vault status for a given module.
     * @param moduleName the name of the module.
     * @param vaultStatus the vault status object into which the module's vault status should be stored.
     */
    private void getVaultStatus(ModuleName moduleName, VaultStatus vaultStatus) {
       
        VaultElementInfo currentVaultInfo = moduleNameToVaultInfoMap.get(moduleName);
        String vaultDescriptor = currentVaultInfo.getVaultDescriptor();
       
        boolean isNestedVault = (currentVaultInfo instanceof VaultElementInfo.Nested);
       
        // Get the vault associated with the module.
        Vault vault = getVault(moduleName);
        if (vault == null) {
            // The vault is inaccessible.
            vaultStatus.addInaccessibleVaultInfo(moduleName, currentVaultInfo);
            return;
        }
       
       
        // Get the latest module revision.
        Status getStatus = new Status("Get status");
        StoredVaultElement.Module latestStoredModule = vault.getStoredModule(moduleName, -1, getStatus);
       
        // Check whether module exists in the vault.
        if (latestStoredModule == null) {
            if (getStatus.getSeverity().compareTo(Status.Severity.ERROR) >= 0) {
                // An error occurs when retrieving the stored module info.
                vaultStatus.addInaccessibleModule(moduleName);
               
            } else {
                // The module does not exist in the vault.
                // Note: this means either that the module is newly-created, or that it has been deleted from the vault.
                vaultStatus.addWorkspaceOnlyModule(moduleName);
            }
           
            return;
        }
       
       
        // The rest is not applicable if nullary and this is a standard vault module.
        if (isNullary() && isStandardVaultDescriptor(vaultDescriptor)) {
            return;
        }
       
       
        // Check for out-of-date module.
        long currentModuleRevision = currentVaultInfo.getRevision();
        long latestModuleRevision = latestStoredModule.getVaultInfo().getRevision();
       
        if (latestModuleRevision > currentModuleRevision) {
            vaultStatus.addOutOfDateModule(moduleName);
        }
       
       
        // Get the revision info for the latest module revision.
        ResourceRevision.Info latestResourceRevisionInfo = latestStoredModule.getResourceRevisionInfo();
       
       
        // Resources: modified, out of date, workspace only, vault only, unmanaged (no resource manager).
       
        // Map from resource type to the resources of that type from the latest module revision.
        Map<String, Set<WorkspaceResource>> resourceTypeToLatestResourcesMap = new HashMap<String, Set<WorkspaceResource>>();
       
        // Populate the map.
        for (Iterator<WorkspaceResource> it = latestStoredModule.getResourceIterator(); it.hasNext(); ) {
            WorkspaceResource latestStoredModuleResource = it.next();
            String resourceType = latestStoredModuleResource.getResourceType();
           
            Set<WorkspaceResource> resourceTypeSet = resourceTypeToLatestResourcesMap.get(resourceType);
            if (resourceTypeSet == null) {
                resourceTypeSet = new HashSet<WorkspaceResource>();
                resourceTypeToLatestResourcesMap.put(resourceType, resourceTypeSet);
            }
           
            resourceTypeSet.add(latestStoredModuleResource);
        }
       
        // Now iterate over each of the resource types, if the module is not contained a nested vault (e.g. a Car vault).
        // We omit nested vaults because currently, resources contained in such vaults are not synchronized independently,
        // and a nested vault cannot contain multiple revisions of the same resource.
        if (!isNestedVault) {
            for (final Map.Entry<String, Set<WorkspaceResource>> entry : resourceTypeToLatestResourcesMap.entrySet()) {
                final String resourceType = entry.getKey();
                Set<WorkspaceResource> latestTypeResourceSet = entry.getValue();
               
                // Get the resource manager for this resource type and for the resource's module.
                ResourceManager resourceManager = virtualResourceManager.getModuleSpecificResourceManager(moduleName, resourceType);
               
                // Iterate over the resources for this type.
                for (final WorkspaceResource moduleResource : latestTypeResourceSet) {
                    ResourceIdentifier resourceIdentifier = moduleResource.getIdentifier();
                   
                    if (resourceManager == null) {
                        vaultStatus.addUnmanagedResource(resourceIdentifier);
                       
                    } else {
                        // Get the revisions.
                        long currentRevision = resourceRevisionInfo.getResourceRevision(resourceIdentifier);
                        long latestRevision = latestResourceRevisionInfo.getResourceRevision(resourceIdentifier);
                       
                        // Compare revisions.
                        if (latestRevision > currentRevision) {
                            vaultStatus.addOutOfDateResource(resourceIdentifier);
                        }
                       
                        long resourceSyncTime = resourceSyncTimeInfo.getResourceSyncTime(resourceIdentifier);
                        long lastModified = resourceManager.getTimeStamp(resourceIdentifier.getResourceName());
                       
                        if (resourceSyncTime == 0) {
                            // Never synced (vault only).
                            vaultStatus.addVaultOnlyResource(resourceIdentifier);
                           
                        } else if (resourceSyncTime < lastModified) {
                            vaultStatus.addModifiedResource(resourceIdentifier);
                        }
                    }
                }
            }
        }
       
       
        // Create the set of identifiers for resource in the latest stored module.
        Set<ResourceIdentifier> latestStoredModuleResourceIdentifierSet = new HashSet<ResourceIdentifier>();
       
        for (final Set<WorkspaceResource> latestTypeResourceSet : resourceTypeToLatestResourcesMap.values()) {
            for (final WorkspaceResource moduleResource : latestTypeResourceSet) {
                latestStoredModuleResourceIdentifierSet.add(moduleResource.getIdentifier());
            }
        }
       
        // Get the workspace-only resources.
        for (final ResourceManager resourceManager : virtualResourceManager.getResourceManagersForModule(moduleName)) {
            ResourceStore resourceStore = resourceManager.getResourceStore();
           
            if (!(resourceStore instanceof ResourceStore.Module)) {
                continue;
            }
           
            for (Iterator<WorkspaceResource> it = ((ResourceStore.Module)resourceStore).getResourceIterator(moduleName); it.hasNext(); ) {
                WorkspaceResource moduleResource = it.next();
                ResourceIdentifier resourceIdentifier = moduleResource.getIdentifier();
                if (!latestStoredModuleResourceIdentifierSet.contains(resourceIdentifier)) {
                    vaultStatus.addWorkspaceOnlyResource(resourceIdentifier);
                }
            }
        }
    }

    /**
     * @return the resource managers for the given module.
     */
    public Set<ResourceManager> getResourceManagersForModule(ModuleName moduleName) {
        return virtualResourceManager.getResourceManagersForModule(moduleName);
    }
   
    /**
     * Get the abstract path to a given resource.
     * @param identifier the identifier of the resource.
     * @return the path to that resource, or null if there is no such path
     *   (eg. a resource manager is not registered for that resource type).
     */
    public ResourcePath.FilePath getResourcePath(ResourceIdentifier identifier) {
        ResourceManager resourceManager = virtualResourceManager.getResourceManagerForResource(identifier);
        if (resourceManager == null) {
            return null;
        }
        // Note that the workspace uses path-based resource stores.
        return ((ResourcePathStore)resourceManager.getResourceStore()).getResourcePath(identifier.getResourceName());
    }
   
    /**
     * Returns a DependencyFinder that can be used for finding module dependencies.
     * @param rootModuleNames the names of the modules whose dependencies are to be collected.
     * @return a new DependencyFinder instance.
     */
    public DependencyFinder getDependencyFinder(Collection<ModuleName> rootModuleNames) {
        return new DependencyFinder(this, rootModuleNames);
    }
   
    /**
     * A helper class for finding module dependencies.
     *
     * @author James Wright
     */
    public static final class DependencyFinder {

        /** workspace to search */
        private final CALWorkspace workspace;
       
        /** Set of module names that we are checking the dependencies of */
        private final SortedSet<ModuleName> rootModuleSet;
       
        /** Set of module names that are imported by rootModuleSet */
        private final SortedSet<ModuleName> importedModuleSet = new TreeSet<ModuleName>();
       
        private DependencyFinder(CALWorkspace workspace, Collection<ModuleName> rootModules) {
            if(workspace == null || rootModules == null) {
                throw new NullPointerException();
            }
            this.workspace = workspace;
            this.rootModuleSet = new TreeSet<ModuleName>(rootModules);
           
            for(final ModuleName moduleName: rootModuleSet) {
                findModuleDependencies(moduleName);
            }
        }
       
        /**
         * Add all the dependencies for module moduleName to importedModuleSet.
         * @param moduleName name of module
         */
        private void findModuleDependencies(ModuleName moduleName) {
            MetaModule metaModule = workspace.getMetaModule(moduleName);
            ModuleTypeInfo moduleTypeInfo = metaModule.getTypeInfo();
           
            for(int i = 0, nImports = moduleTypeInfo.getNImportedModules(); i < nImports; i++) {
                ModuleTypeInfo importedModule = moduleTypeInfo.getNthImportedModule(i);
                ModuleName importedModuleName = importedModule.getModuleName();
                if(rootModuleSet.contains(importedModuleName) || importedModuleSet.contains(importedModuleName)) {
                    continue;
                }
                importedModuleSet.add(importedModuleName);
                findModuleDependencies(importedModuleName);
            }
        }
       
        /**
         * @return SortedSet of root module names
         */
        public final SortedSet<ModuleName> getRootSet() {
            return Collections.unmodifiableSortedSet(rootModuleSet);
        }
       
        /**
         * @return SortedSet of names of modules imported (directly or indirectly) by
         *          the modules in the root set.
         */
        public final SortedSet<ModuleName> getImportedModulesSet() {
            return Collections.unmodifiableSortedSet(importedModuleSet);
        }
    }
}
TOP

Related Classes of org.openquark.cal.services.CALWorkspace$Renaming

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.