Package org.openquark.cal.services

Source Code of org.openquark.cal.services.CarBuilder

/*
* 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.
*/


/*
* CarBuilder.java
* Creation date: Jan 18, 2006.
* By: Joseph Wong
*/
package org.openquark.cal.services;

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
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.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarOutputStream;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;

import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.machine.ProgramResourceLocator;
import org.openquark.cal.machine.ProgramResourceRepository;
import org.openquark.util.FileSystemHelper;
import org.openquark.util.Messages;
import org.openquark.util.Pair;
import org.openquark.util.TextEncodingUtilities;


/**
* This class implements a CAL Archive (Car) builder that will go through the
* resources in a supplied workspace and add those resources to a new Car.
*
* This class is not meant to be instantiated or subclassed.
*
* @author Joseph Wong
*/
public final class CarBuilder {
   
    private static final String DOT_CWS = "." + WorkspaceDeclarationPathMapper.INSTANCE.getFileExtension();

    private static final String DOT_CAR = "." + CarPathMapper.INSTANCE.getFileExtension();
   
    private static final String DOT_JAR = ".jar";

    public static final String DOT_CAR_DOT_JAR = DOT_CAR + DOT_JAR;
       
    private static final String WORKSPACE_DECLARATIONS_FOLDER_NAME = WorkspaceDeclarationPathMapper.INSTANCE.getBaseResourceFolder().getPathStringMinusSlash();

    private static final String CAR_FOLDER_NAME = CarPathMapper.INSTANCE.getBaseResourceFolder().getPathStringMinusSlash();

    /**
     * The default name to use for the workspace spec file that contains all the modules in the Car.
     */
    private static final String DEFAULT_MAIN_SPEC_NAME = "main.carspec";
   
    /**
     * The singleton instance for accessing the message bundle.
     */
    private static final Messages MESSAGES = new Messages(CarBuilder.class, "carbuilder");

    /**
     * The marker file to be added to all Cars that identifies a file as a Car.
     */
    public static final String CAR_MARKER_NAME = "Car.marker";
   
    /**
     * The name of the special file that contains a list of the additional files in the Car.
     */
    public static final String CAR_ADDITIONAL_FILES_NAME = "Car.additionalFiles";

    /**
     * This class encapsulates the configuration options for the CarBuilder.
     *
     * @author Joseph Wong
     */
    public final static class Configuration {
       
        /**
         * The workspace manager that will provide the resources to be added to the Car.
         */
        private final WorkspaceManager workspaceManager;
       
        /**
         * The names of the modules to be included in the Car.
         */
        private final ModuleName[] moduleNames;
       
        /**
         * A Map mapping a Car Workspace Spec file name to the corresponding CarSpec instance.
         */
        private final Map<String, CarSpec> carSpecs;
       
        /**
         * The name of the main CarSpec.
         */
        private final String mainSpecName;
       
        /**
         * The monitor to be used for monitoring the progress of the Car building operation.
         */
        private Monitor monitor;
       
        /**
         * Whether to build the Car with sourceless modules (i.e. the CAL files will be empty stubs).
         */
        private boolean buildSourcelessModules;
       
        /**
         * A map from relative paths to the contents of additional files to be included in the Car.
         */
        private final Map<String, byte[]> additionalFiles;
       
        /**
         * Constructs an instance of CarBuilder.Configuration where all the modules in the workspace manager
         * will be added to the Car.
         *
         * @param workspaceManager the workspace manager that will provide the resources to be added to the Car.
         */
        public Configuration(WorkspaceManager workspaceManager) {
            this(workspaceManager, workspaceManager.getWorkspace().getModuleNames());
        }
       
        /**
         * Factory method for constructing an instance of CarBuilder.Configuration where modules that are
         * imported from existing Cars can optionally be excluded.
         *
         * @param workspaceManager the workspace manager that will provide the resources to be added to the Car.
         * @param options configuration options for the Car builder.
         * @return an instance of this class.
         */
        public static Configuration makeConfigOptionallySkippingModulesAlreadyInCars(WorkspaceManager workspaceManager, BuilderOptions options) {
           
            Configuration config;
           
            if (!options.shouldSkipModulesAlreadyInCars) {
                config = new Configuration(workspaceManager);
               
            } else {
                // modules in Cars should be skipped...
               
                List<ModuleName> nonCarModulesList = new ArrayList<ModuleName>();
               
                CALWorkspace workspace = workspaceManager.getWorkspace();
                ModuleName[] allModuleNames = workspace.getModuleNames();
               
                for (final ModuleName moduleName : allModuleNames) {
                    VaultElementInfo vaultInfo = workspace.getVaultInfo(moduleName);
                   
                    if (!isVaultElementFromCar(vaultInfo)) {
                        nonCarModulesList.add(moduleName);
                    }
                }
               
                ModuleName[] nonCarModules = nonCarModulesList.toArray(new ModuleName[nonCarModulesList.size()]);
               
                config = new Configuration(workspaceManager, nonCarModules);
            }
           
            config.setBuildSourcelessModule(options.buildSourcelessModules);
           
            return config;
        }
       
        /**
         * Constructs an instance of CarBuilder.Configuration where only the named modules will be added to the Car.
         *
         * @param workspaceManager the workspace manager that will provide the resources to be added to the Car.
         * @param moduleNames the names of the modules to be included in the Car.
         */
        public Configuration(WorkspaceManager workspaceManager, ModuleName[] moduleNames) {
            this(workspaceManager, moduleNames, DEFAULT_MAIN_SPEC_NAME);
        }

        /**
         * Constructs an instance of CarBuilder.Configuration where only the named modules will be added to the Car.
         *
         * @param workspaceManager the workspace manager that will provide the resources to be added to the Car.
         * @param moduleNames the names of the modules to be included in the Car.
         * @param mainSpecName the name to use for the workspace spec file that contains all the modules in the Car.
         */
        public Configuration(WorkspaceManager workspaceManager, ModuleName[] moduleNames, String mainSpecName) {
           
            if (workspaceManager == null || moduleNames == null || mainSpecName == null) {
                throw new NullPointerException();
            }
           
            this.workspaceManager = workspaceManager;
            this.moduleNames = moduleNames.clone();
            this.carSpecs = new HashMap<String, CarSpec>();
            this.mainSpecName = mainSpecName;
            this.monitor = DefaultMonitor.INSTANCE;
            this.buildSourcelessModules = false;
            this.additionalFiles = new HashMap<String, byte[]>();
           
            // sort the module names in alphabetical order
            Arrays.sort(this.moduleNames);
           
            // add the main spec to the car specs map
            carSpecs.put(mainSpecName, new CarSpec.ModuleList(mainSpecName, this.moduleNames));
        }
       
        /**
         * Specifies the monitor to be used for monitoring the progress of the Car building operation.
         *
         * @param monitor the monitor to be used by the CarBuilder. If this argument is null, then a {@link
         *                CarBuilder.DefaultMonitor DefaultMonitor} will be used as the monitor.
         */
        public void setMonitor(Monitor monitor) {
            if (monitor == null) {
                this.monitor = DefaultMonitor.INSTANCE;
            } else {
                this.monitor = monitor;
            }
        }
       
        /**
         * Specifies whether to build the Car with sourceless modules (i.e. the CAL files will be empty stubs).
         * @param buildSourcelessModules the setting.
         */
        public void setBuildSourcelessModule(boolean buildSourcelessModules) {
            this.buildSourcelessModules = buildSourcelessModules;
        }
       
        /**
         * Adds a Car Workspace Spec file to the Car.
         * @param specName the name of the spec file.
         * @param modulesInSpec the names of the modules to be included in the spec file.
         */
        public void addCarSpec(String specName, ModuleName[] modulesInSpec) {
           
            if (specName == null || modulesInSpec == null) {
                throw new NullPointerException();
            }
           
            carSpecs.put(specName, new CarSpec.ModuleList(specName, modulesInSpec));
        }
       
        /**
         * Adds a preexisting Car Workspace Spec file from the file system to the Car.
         * @param specName the name of the spec file when added to the Car.
         * @param specFile the preexisting spec file.
         */
        public void addFileBasedCarSpec(String specName, File specFile) {
           
            if (specName == null || specFile == null) {
                throw new NullPointerException();
            }
           
            carSpecs.put(specName, new CarSpec.FileBased(specName, specFile));
        }
       
        /**
         * Adds an additional file to be included with the Car.
         * @param relativePath the relative path of the file.
         * @param contents the contents of the file.
         */
        public void addAdditionalFile(String relativePath, byte[] contents) {
            additionalFiles.put(relativePath, contents);
        }
       
        /**
         * Returns the import declaration corresponding to the Car being built, if the Car were to be
         * found in the StandardVault.
         *
         * @param carName the name of the Car.
         */
        private String getWorkspaceDeclarationImportLineForStandardVaultCar(String carName) {
            return "import car StandardVault " + carName + " " + mainSpecName;
        }
    }
   
    /**
     * This abstract class represents the contents of a Car Workspace Spec file.
     *
     * @author Joseph Wong
     */
    private static abstract class CarSpec {
       
        /**
         * The name of the spec file.
         */
        private final String specName;
       
        /**
         * This class represents the contents of a spec file as an array of module names to be included in the file.
         *
         * @author Joseph Wong
         */
        private static final class ModuleList extends CarSpec {
           
            /**
             * The names of the modules to be included in the spec file.
             */
            private final ModuleName[] modulesInSpec;

            /**
             * Constructs a CarSpec.ModuleList instance.
             * @param specName the name of the spec file.
             * @param modulesInSpec the names of the modules to be included in the spec file.
             */
            private ModuleList(String specName, ModuleName[] modulesInSpec) {
                super(specName);
               
                if (modulesInSpec == null) {
                    throw new NullPointerException();
                }
               
                this.modulesInSpec = modulesInSpec.clone();
            }

            /**
             * {@inheritDoc}
             */
            @Override
            void writeTo(OutputStream outputStream) throws IOException {
               
                String spec = getWorkspaceSpec(getName(), modulesInSpec);
               
                writeStringToStream(outputStream, spec);
            }

            /**
             * Returns the string contents of the spec file given the file name and the array of module names.
             * @param specName the name of the spec file.
             * @param moduleNames the array of module names to be included in the file.
             * @return the string contents of the spec file.
             */
            private static String getWorkspaceSpec(String specName, ModuleName[] moduleNames) {
                StringBuilder builder = new StringBuilder();
               
                builder.append("// ").append(specName).append("\n// Created at " + new Date() + "\n\n");
               
                for (final ModuleName element : moduleNames) {
                    builder.append(element).append("\n");
                }
               
                return builder.toString();
            }
        }
       
        /**
         * This class represents the contents of a spec file as a preexisting file on the file system.
         *
         * @author Joseph Wong
         */
        private static final class FileBased extends CarSpec {
           
            /**
             * The preexisting spec file on the file system.
             */
            private final File specFile;
           
            /**
             * Constructs an instance of CarSpec.FileBased.
             * @param specName the name of the spec file.
             * @param specFile the preexisting spec file on the file system.
             */
            private FileBased(String specName, File specFile) {
                super(specName);
               
                if (specFile == null) {
                    throw new NullPointerException();
                }
               
                this.specFile = specFile;
            }

            /**
             * {@inheritDoc}
             */
            @Override
            void writeTo(OutputStream outputStream) throws IOException {
                FileInputStream fis = new FileInputStream(specFile);
                try {
                    FileSystemResourceHelper.transferData(fis, outputStream);
                } finally {
                    fis.close();
                }
            }
        }
       
        /**
         * Private constructor for subclasses.
         * @param specName the name of the spec file.
         */
        private CarSpec(String specName) {
           
            if (specName == null) {
                throw new NullPointerException();
            }
           
            this.specName = specName;
        }
       
        /**
         * @return the name of the spec file.
         */
        String getName() {
            return specName;
        }
       
        /**
         * Writes the string contents of the spec file to the specified output stream.
         * @param outputStream the stream to be written to.
         * @throws IOException
         */
        abstract void writeTo(OutputStream outputStream) throws IOException;
    }
   
    /**
     * This interface specifies a progress monitor to be used with a {@link CarBuilder} for
     * monitoring the progress of the Car building operation.
     *
     * @author Joseph Wong
     */
    public static interface Monitor {
       
        /**
         * Returns whether the operation has been canceled by the user. This method is
         * meant to be queried by the {@link CarBuilder} in determining whether to abort the operation.
         *
         * @return true if the operation has been canceled, false otherwise.
         */
        public boolean isCanceled();
       
        /**
         * Displays the specified messages to the user (if appropriate).
         * @param messages the messages to be shown.
         */
        public void showMessages(String[] messages);
       
        /**
         * Invoked when the CarBuilder has started its operation.
         * @param nCars the number of Cars to be built.
         * @param nTotalModules the total number of modules across all Cars.
         */
        public void operationStarted(int nCars, int nTotalModules);
       
        /**
         * Invoked when the CarBuilder has started processing the modules and adding them to a Car.
         * @param carName the name of the Car being built.
         * @param nModules the number of modules that will be added to the Car.
         */
        public void carBuildingStarted(String carName, int nModules);
       
        /**
         * Invoked when the CarBuilder has started processing the named module and adding it to the current Car.
         * @param carName the name of the Car being built.
         * @param moduleName the name of the module being processed by the CarBuilder.
         */
        public void processingModule(String carName, ModuleName moduleName);
       
        /**
         * Invoked when the CarBuilder has finished building a Car.
         * @param carName the name of the Car just built.
         */
        public void carBuildingDone(String carName);
       
        /**
         * Invoked when the CarBuilder has finished its operation.
         */
        public void operationDone();
    }
   
    /**
     * This is a default implementation of the Monitor interface which does nothing, and which always returns true
     * for isCanceled().
     *
     * This class is meant to be used through its singleton instance.
     *
     * @author Joseph Wong
     */
    public static final class DefaultMonitor implements Monitor {
        /** Private constructor. */
        private DefaultMonitor() {}
       
        // default implementation
        public boolean isCanceled() {
            return false;
        }
        public void showMessages(String[] messages) {}
        public void operationStarted(int nCars, int nTotalModules) {}
        public void carBuildingStarted(String carName, int nModules) {}
        public void processingModule(String carName, ModuleName moduleName) {}
        public void carBuildingDone(String carName) {}
        public void operationDone() {}
       
        /** Singleton instance. */
        public static final DefaultMonitor INSTANCE = new DefaultMonitor();
    }
   
    /**
     * Encapsulates the options for the Car builder. Note that this is different from
     * the per-Car configuration encapsulated by the {@link CarBuilder.Configuration} class.
     *
     * @author Joseph Wong
     */
    public static final class BuilderOptions {
       
        /**
         * Specifies whether modules already in Cars should be skipped over.
         */
        private final boolean shouldSkipModulesAlreadyInCars;
       
        /**
         * Specifies whether a corresponding workspace declaration should be generated also.
         */
        private final boolean shouldGenerateCorrespWorkspaceDecl;
       
        /**
         * Specifies whether the generated workspace declaration should end simply with '.cws' instead of '.car.cws'.
         */
        private final boolean noCarSuffixInWorkspaceDeclName;
       
        /**
         * Specifies whether to build the Car with sourceless modules (i.e. the CAL files will be empty stubs).
         */
        private final boolean buildSourcelessModules;
       
        /**
         * Specifies the names of Cars to be explicitly excluded.
         */
        private final Set<String> carsToExclude;
       
        /**
         * Specifies whether to generate Cars with the suffix .car.jar.
         */
        private final boolean shouldGenerateCarJarSuffix;

        /**
         * Constructs an instance of BuilderOptions.
         * @param shouldSkipModulesAlreadyInCars whether modules already in Cars should be skipped over.
         * @param shouldGenerateCorrespWorkspaceDecl whether a corresponding workspace declaration should be generated also.
         * @param noCarSuffixInWorkspaceDeclName whether the generated workspace declaration should end simply with '.cws' instead of '.car.cws'.
         * @param buildSourcelessModules whether to build the Car with sourceless modules (i.e. the CAL files will be empty stubs).
         * @param carsToExclude the names of Cars to be explicitly excluded.
         * @param shouldGenerateCarJarSuffix whether to generate Cars with the suffix .car.jar.
         */
        public BuilderOptions(
            boolean shouldSkipModulesAlreadyInCars,
            boolean shouldGenerateCorrespWorkspaceDecl,
            boolean noCarSuffixInWorkspaceDeclName,
            boolean buildSourcelessModules,
            Collection<String> carsToExclude,
            boolean shouldGenerateCarJarSuffix) {
           
            this.shouldSkipModulesAlreadyInCars = shouldSkipModulesAlreadyInCars;
            this.shouldGenerateCorrespWorkspaceDecl = shouldGenerateCorrespWorkspaceDecl;
            this.noCarSuffixInWorkspaceDeclName = noCarSuffixInWorkspaceDeclName;
            this.buildSourcelessModules = buildSourcelessModules;
            this.carsToExclude = Collections.unmodifiableSet(new HashSet<String>(carsToExclude));
            this.shouldGenerateCarJarSuffix = shouldGenerateCarJarSuffix;
        }
    }
   
    /**
     * Encapsulates the configuration for building a Car and saving it to the file system,
     * including in particular the {@link CarBuilder.Configuration} for the Car.
     *
     * @author Joseph Wong
     */
    public static final class FileOutputConfiguration {
       
        /**
         * The basic Car building configuration.
         */
        private final Configuration config;
       
        /**
         * The location of the output Car file.
         */
        private final File carFile;
       
        /**
         * The name of the output Car. Can be different from the name of the Car file, e.g. when the Car file ends with a
         * .car.jar suffix.
         */
        private final String carName;
       
        /**
         * The location of the corresponding workspace declaration file. Can be null if none is to be generated.
         */
        private final File cwsFile;
       
        /**
         * A Collection containing the names of additional declarations to be added as imports to the workspace declaration,
         * if it is to be generated.
         */
        private Collection<String> additionalImportsForWorkspaceDecl;

        /**
         * Constructs an instance of FileOutputConfiguration.
         * @param config the basic Car building configuration.
         * @param carFile the location of the output Car file.
         * @param carName the name of the output Car.
         * @param cwsFile the corresponding workspace declaration file. Can be null if none is to be generated.
         */
        public FileOutputConfiguration(Configuration config, File carFile, String carName, File cwsFile) {
           
            if (config == null || carFile == null || carName == null) {
                throw new NullPointerException();
            }
           
            this.carFile = carFile;
            this.carName = carName;
            this.config = config;
            this.cwsFile = cwsFile; // can be null
            this.additionalImportsForWorkspaceDecl = Collections.emptySet();
        }

        /**
         * @return the basic Car building configuration.
         */
        public Configuration getConfig() {
            return config;
        }

        /**
         * @return the location of the output Car file.
         */
        public File getCarFile() {
            return carFile;
        }
       
        /**
         * @return the name of the output Car.
         */
        public String getCarName() {
            return carName;
        }

        /**
         * @return the location of the corresponding workspace declaration file, or null if none is to be generated.
         */
        public File getWorkspaceDeclarationFile() {
            return cwsFile;
        }
       
        /**
         * Sets the Collection containing the names of additional declarations to be added as imports to the workspace declaration,
         * if it is to be generated.
         * @param additionalImportsForWorkspaceDecl
         */
        private void setAdditionalImportsForWorkspaceDecl(Collection<String> additionalImportsForWorkspaceDecl) {
            this.additionalImportsForWorkspaceDecl = Collections.unmodifiableCollection(additionalImportsForWorkspaceDecl);
        }
       
        /**
         * @return the Collection containing the names of additional declarations to be added as imports to the workspace declaration,
         *         if it is to be generated.
         */
        public Collection<String> getAdditionalImportsForWorkspaceDecl() {
            return additionalImportsForWorkspaceDecl;
        }
       
        /**
         * @return whether to generate Cars with the suffix .car.jar.
         */
        public boolean shouldGenerateCarJarSuffix() {
            return carFile.getName().endsWith(DOT_JAR);
        }
    }
   
    /**
     * Encapsulates a set of {@link CarBuilder.Configuration}s that are constructed for building one
     * Car per workspace declaration.
     *
     * @author Joseph Wong
     */
    public static final class ConfigurationsForOneCarPerWorkspaceDeclaration {
       
        /**
         * The map (workspace decl name -> {@link CarBuilder.FileOutputConfiguration}) that contains all the configurations.
         */
        private final Map<String, FileOutputConfiguration> workspaceDeclToConfigMap;
       
        /**
         * The map (module name -> LinkedHashSet of (Pair of (workspace decl name, VaultElementInfo))) that contains
         * information about conflicting entries in workspace declaration files:
         *
         * A module may appear in more than one declaration, and these declaration entries may be for different vaults/revisions.
         * Only one declaration entry correspond to the resources in the workspace. The remainder are conflicts. So we
         * store information about each conflicting entry (a VaultElementInfo) and its corresponding workspace declaration file name.
         */
        private final Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToConflictingWorkspaceEntriesMap;
       
        /**
         * A map containing the names of the modules included by the workspace declaration and
         * their associated entries in all the workspace declarations in the tree.
         */
        private final Map<String, LinkedHashSet<String>> importsMap;
       
        /**
         * Private constructor. Intended only to be called by {@link CarBuilder#makeConfigurationsForOneCarPerWorkspaceDeclaration}.
         * @param workspaceDeclToConfigMap
         * @param moduleNameToConflictingWorkspaceEntriesMap
         */
        private ConfigurationsForOneCarPerWorkspaceDeclaration(Map<String, FileOutputConfiguration> workspaceDeclToConfigMap, Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToConflictingWorkspaceEntriesMap, Map<String, LinkedHashSet<String>> importsMap) {
            if (moduleNameToConflictingWorkspaceEntriesMap == null || workspaceDeclToConfigMap == null || importsMap == null) {
                throw new NullPointerException();
            }
           
            // wrap the maps with unmodifiable wrappers since they're returned directly by accessors
            this.moduleNameToConflictingWorkspaceEntriesMap = Collections.unmodifiableMap(moduleNameToConflictingWorkspaceEntriesMap);
            this.workspaceDeclToConfigMap = Collections.unmodifiableMap(workspaceDeclToConfigMap);
            this.importsMap = Collections.unmodifiableMap(importsMap);
        }
       
        /**
         * @return the map (workspace decl name -> {@link CarBuilder.FileOutputConfiguration}) that contains all the configurations.
         */
        public Map<String, FileOutputConfiguration> getWorkspaceDeclToConfigMap() {
            return workspaceDeclToConfigMap;
        }
       
        /**
         * @return the map (module name -> LinkedHashSet of (Pair of (workspace decl name, VaultElementInfo))) that contains
         * information about conflicting entries in workspace declaration files.
         */
        public Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> getModuleNameToConflictingWorkspaceEntriesMap() {
            return moduleNameToConflictingWorkspaceEntriesMap;
        }
       
        /**
         * @return a map containing the names of the modules included by the workspace declaration and
         * their associated entries in all the workspace declarations in the tree.
         */
        public Map<String, LinkedHashSet<String>> getImportsMap() {
            return importsMap;
        }
    }

    /** Private constructor. This class is not meant to be instantiated. */
    private CarBuilder() {}
   
    /**
     * Constructs a set of {@link CarBuilder.FileOutputConfiguration}s that are constructed for building one
     * Car per workspace declaration.
     *
     * @param workspaceManager the workspace manager that will provide the resources to be added to the Cars.
     * @param monitor the {@link CarBuilder.Monitor} to be used.
     * @param outputDirectory the output directory to which the Car files will be written.
     * @param options configuration options for the builder.
     * @return a {@link CarBuilder.ConfigurationsForOneCarPerWorkspaceDeclaration} encapsulating the configurations and information on
     *         any identified conflicts.
     */
    public static ConfigurationsForOneCarPerWorkspaceDeclaration makeConfigurationsForOneCarPerWorkspaceDeclaration(WorkspaceManager workspaceManager, Monitor monitor, File outputDirectory, BuilderOptions options) {
        WorkspaceDeclaration.StreamProvider workspaceDeclarationProvider = workspaceManager.getInitialWorkspaceDeclarationProvider();
        CALWorkspace workspace = workspaceManager.getWorkspace();
        Status status = new Status("Reading workspace declarations");
       
        // fetch information about all imported workspace declaration files using the WorkspaceLoader
        Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToWorkspaceDeclInfoMap = WorkspaceLoader.getStoredModuleNameToWorkspaceDeclNamesMap(workspaceDeclarationProvider, workspace, status);
       
        Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToConflictingWorkspaceEntriesMap = new HashMap<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>>();
       
        ////
        /// Invert the moduleNameToWorkspaceDeclNamesMap to (workspace decl name -> Set of module names), and build up
        /// the map containing conflict information
        //
        Map<String, Set<ModuleName>> workspaceDeclToModuleNamesMap = new HashMap<String, Set<ModuleName>>();
       
        for (final Map.Entry<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> entry : moduleNameToWorkspaceDeclInfoMap.entrySet()) {
           
            ModuleName moduleName = entry.getKey();
            LinkedHashSet<Pair<String, VaultElementInfo>> workspaceDeclInfo = entry.getValue();
           
            VaultElementInfo actualVaultInfo = null;
            for (final Pair<String, VaultElementInfo> workspaceDeclNameAndVaultElementInfoPair : workspaceDeclInfo) {
               
                String workspaceDeclName = workspaceDeclNameAndVaultElementInfoPair.fst();
                VaultElementInfo vaultInfo = workspaceDeclNameAndVaultElementInfoPair.snd();
               
                // we know that the workspaceDeclInfo is ordered in such a way that the first entry
                // correspond to the vault info that is actually used by the workspace, and that the subsequent entries
                // correspond to "shadowed" declaration entries that may in fact conflict with the "actualVaultInfo"
               
                // detect if there were conflicting entries for the same module in different workspace declaration files
                if (actualVaultInfo == null) {
                    actualVaultInfo = vaultInfo;
                   
                    // if the module comes from a Car, then skip it if shouldSkipModulesAlreadyInCars is specified
                    if (options.shouldSkipModulesAlreadyInCars && isVaultElementFromCar(actualVaultInfo)) {
                       
                        break; // break out of the inner loop
                    }
                   
                } else {
                    if (!actualVaultInfo.equals(vaultInfo)) {
                        // insert the conflicting vault info into the conflict map under the module name
                        LinkedHashSet<Pair<String, VaultElementInfo>> conflictingWorkspaceEntries = moduleNameToConflictingWorkspaceEntriesMap.get(moduleName);
                       
                        if (conflictingWorkspaceEntries == null) {
                            conflictingWorkspaceEntries = new LinkedHashSet<Pair<String, VaultElementInfo>>();
                            moduleNameToConflictingWorkspaceEntriesMap.put(moduleName, conflictingWorkspaceEntries);
                        }
                        conflictingWorkspaceEntries.add(new Pair<String, VaultElementInfo>(workspaceDeclName, vaultInfo));
                    }
                }
               
                Set<ModuleName> modulesInWorkspaceDecl = workspaceDeclToModuleNamesMap.get(workspaceDeclName);
               
                if (modulesInWorkspaceDecl == null) {
                    modulesInWorkspaceDecl = new HashSet<ModuleName>();
                    workspaceDeclToModuleNamesMap.put(workspaceDeclName, modulesInWorkspaceDecl);
                }
                modulesInWorkspaceDecl.add(moduleName);
            }
        }
       
        // fetch information about the tree of imports using the WorkspaceLoader
        Map<String, LinkedHashSet<String>> importsMap = WorkspaceLoader.getWorkspaceDeclarationImportsMap(workspaceDeclarationProvider, workspace, status);
       
        ////
        /// Build the result map (workspace decl name -> Configuration) of the Configuration objects
        ///
        /// NOTE: We produce empty Cars too (so that their cws files also get generated, in case any client code refers to them explicitly)
        //
        Map<String, FileOutputConfiguration> workspaceDeclToConfigMap = new HashMap<String, FileOutputConfiguration>();
       
        // We first gather the names of the all the workspace declarations involved.
        Set<String> allWorkspaceDeclsInvolved = importsMap.keySet();
       
        // For each workspace declaration, we construct a Configuration object and add it to the result map
        for (final String workspaceDeclName : allWorkspaceDeclsInvolved) {
            Set<ModuleName> modulesInWorkspaceDecl = workspaceDeclToModuleNamesMap.get(workspaceDeclName);

            final int nModules;
           
            if (modulesInWorkspaceDecl == null) {
                modulesInWorkspaceDecl = Collections.emptySet();
                nModules = 0;
            } else {
                nModules = modulesInWorkspaceDecl.size();
            }
           
            Configuration config = new Configuration(workspaceManager, modulesInWorkspaceDecl.toArray(new ModuleName[nModules]));
            config.setMonitor(monitor);
            config.setBuildSourcelessModule(options.buildSourcelessModules);
           
            String carName = makeCarNameFromSourceWorkspaceDeclName(workspaceDeclName);
            File carFile = makeOutputCarFile(outputDirectory, carName, options);
           
            File cwsFile;
            if (options.shouldGenerateCorrespWorkspaceDecl) {
                cwsFile = makeOutputCorrespWorkspaceDeclFile(outputDirectory, carName, options.noCarSuffixInWorkspaceDeclName);
            } else {
                cwsFile = null;
            }
           
            FileOutputConfiguration fileOutputConfig = new FileOutputConfiguration(config, carFile, carName, cwsFile);
           
            workspaceDeclToConfigMap.put(workspaceDeclName, fileOutputConfig);
        }
       
        ConfigurationsForOneCarPerWorkspaceDeclaration configs = new ConfigurationsForOneCarPerWorkspaceDeclaration(workspaceDeclToConfigMap, moduleNameToConflictingWorkspaceEntriesMap, importsMap);
       
        addImportsToConfigurationsForOneCarPerWorkspaceDeclaration(configs);
       
        return configs;
    }

    /**
     * Modify the given configurations with additional import declarations for the workspace declaration files that are to be generated.
     * @param configs the configurations to be modified.
     */
    private static void addImportsToConfigurationsForOneCarPerWorkspaceDeclaration(ConfigurationsForOneCarPerWorkspaceDeclaration configs) {
       
        final Map<String, FileOutputConfiguration> workspaceDeclToConfigMap = configs.workspaceDeclToConfigMap;
       
        for (final Map.Entry<String,FileOutputConfiguration> entry : workspaceDeclToConfigMap.entrySet()) {
           
            String workspaceDeclName = entry.getKey();
            FileOutputConfiguration fileOutputConfig = entry.getValue();
           
            LinkedHashSet<String> imports = getListOfImports(configs, workspaceDeclName);
           
            fileOutputConfig.setAdditionalImportsForWorkspaceDecl(imports);
        }
    }

    /**
     * Returns whether the specified vault element info refers to a vault element stored in a Car.
     * @param vaultInfo the vault element info.
     * @return true if the vault element is stored in a Car.
     */
    private static boolean isVaultElementFromCar(VaultElementInfo vaultInfo) {
        return vaultInfo instanceof VaultElementInfo.Nested &&
            vaultInfo.getVaultDescriptor().equals(CarVault.getVaultClassDescriptor());
    }
   
    /**
     * Builds one Car file per workspace declaration file that constitutes the initial declaration of the CAL workspace.
     * @param workspaceManager the workspace manager that will provide the resources to be added to the Cars.
     * @param monitor the {@link CarBuilder.Monitor} to be used.
     * @param outputDirectory the output directory to which the Car files will be written.
     * @param options configuration options for the builder.
     * @return an array of the names of the Car files built.
     * @throws FileNotFoundException if the output file is not a valid location.
     * @throws IOException
     */
    public static String[] buildOneCarPerWorkspaceDeclaration(WorkspaceManager workspaceManager, Monitor monitor, File outputDirectory, BuilderOptions options) throws FileNotFoundException, IOException {
        boolean directoryExists = FileSystemHelper.ensureDirectoryExists(outputDirectory);
        if (!directoryExists) {
            throw new FileNotFoundException("Output directory does not exist: " + outputDirectory);
        }
       
        ConfigurationsForOneCarPerWorkspaceDeclaration configs = makeConfigurationsForOneCarPerWorkspaceDeclaration(workspaceManager, monitor, outputDirectory, options);
        final Map<String, FileOutputConfiguration> workspaceDeclToConfigMap = configs.workspaceDeclToConfigMap;
        final Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToConflictingWorkspaceEntriesMap = configs.moduleNameToConflictingWorkspaceEntriesMap;
       
        ////
        /// If there are conflicting workspace declaration entries for any module, display a warning
        //
        if (!moduleNameToConflictingWorkspaceEntriesMap.isEmpty()) {
            showMessagesAboutConflictingWorkspaceEntries(monitor, moduleNameToConflictingWorkspaceEntriesMap);
        }
       
        ////
        /// Now proceed to build the Cars, one at a time using the supplied configurations
        //
       
        // identify the total number of Cars to be built, as well as the total number of modules across all Cars
        int nCars = workspaceDeclToConfigMap.size();
       
        int nTotalModules = 0;
        for (final FileOutputConfiguration fileOutputConfig : workspaceDeclToConfigMap.values()) {
            nTotalModules += fileOutputConfig.getConfig().moduleNames.length;
        }
       
        // run the build helper, one per Car
        monitor.operationStarted(nCars, nTotalModules);
        try {
            List<String> carsBuilt = new ArrayList<String>();
           
            for (final Map.Entry<String, FileOutputConfiguration> entry : workspaceDeclToConfigMap.entrySet()) {
               
                // break out of the loop and quit if the user canceled the operation
                if (monitor.isCanceled()) {
                    break;
                }
               
                FileOutputConfiguration fileOutputConfig = entry.getValue();
               
                String carName = fileOutputConfig.getCarName();
                if (!options.carsToExclude.contains(carName)) {
                    boolean notCanceled = buildCarHelper(fileOutputConfig);
                    if (notCanceled) {
                        carsBuilt.add(carName);
                    }
                   
                } else {
                    // skip the module which is specified to be excluded
                    monitor.carBuildingStarted(carName, 0);
                    monitor.carBuildingDone(carName);
                }
            }
           
            return carsBuilt.toArray(new String[carsBuilt.size()]);
           
        } finally {
            monitor.operationDone();
        }
    }

    /**
     * Show messages via the given monitor about conflicting workspace entries.
     * @param monitor the monitor through which the messages will be shown.
     * @param moduleNameToConflictingWorkspaceEntriesMap
     *
     * a map (module name -> LinkedHashSet of (Pair of (workspace decl name, VaultElementInfo))) that contains
     * information about conflicting entries in workspace declaration files:
     *
     * A module may appear in more than one declaration, and these declaration entries may be for different vaults/revisions.
     * Only one declaration entry correspond to the resources in the workspace. The remainder are conflicts. So we
     * store information about each conflicting entry (a VaultElementInfo) and its corresponding workspace declaration file name.
     */
    private static void showMessagesAboutConflictingWorkspaceEntries(Monitor monitor, Map<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> moduleNameToConflictingWorkspaceEntriesMap) {
        List<String> messages = new ArrayList<String>();
       
        for (final Map.Entry<ModuleName, LinkedHashSet<Pair<String, VaultElementInfo>>> entry : moduleNameToConflictingWorkspaceEntriesMap.entrySet()) {
           
            ModuleName moduleName = entry.getKey();
            LinkedHashSet<Pair<String, VaultElementInfo>> conflictingWorkspaceEntries = entry.getValue();
           
            messages.add(MESSAGES.getString("moduleHasVaultInfoConflictsHeader", moduleName));
           
            for (final Pair<String, VaultElementInfo> workspaceDeclNameAndVaultElementPair : conflictingWorkspaceEntries) {
               
                String workspaceDeclName = workspaceDeclNameAndVaultElementPair.fst();
                VaultElementInfo vaultInfo = workspaceDeclNameAndVaultElementPair.snd();
               
                StringBuilder vaultInfoString = new StringBuilder(vaultInfo.getVaultDescriptor());
               
                String locationString = vaultInfo.getLocationString();
                if (locationString != null) {
                    vaultInfoString.append(' ').append(locationString);
                }
               
                vaultInfoString.append(' ').append(MESSAGES.getString("revisionNumber", Integer.valueOf(vaultInfo.getRevision())));
               
                messages.add(MESSAGES.getString("conflictingVaultInfo", workspaceDeclName, vaultInfoString.toString()));
            }
        }
       
        monitor.showMessages(messages.toArray(new String[0]));
    }

    /**
     * Calculates the workspace declarations that should be imported by the given workspace declaration.
     * Note that we elide any declaration that does not have a corresponding Car generated.
     *
     * @param configs the set of configurations for the various Cars being generated.
     * @param workspaceDeclName the name of the workspace declaration to look up.
     * @return the LinkedHashSet of declarations to be imported.
     */
    private static LinkedHashSet<String> getListOfImports(ConfigurationsForOneCarPerWorkspaceDeclaration configs, String workspaceDeclName) {
        LinkedHashSet<String> imports = new LinkedHashSet<String>();
        Set<String> alreadyVisitedDecls = new HashSet<String>();
        addToListOfImports(imports, alreadyVisitedDecls, configs, workspaceDeclName);
        return imports;
    }

    /**
     * Calculates the workspace declarations that should be imported by the given workspace declaration.
     * Note that we elide any declaration that does not have a corresponding Car generated.
     *
     * @param imports the LinkedHashSet to be added to.
     * @param alreadyVisitedDecls the workspace declarations that have already been visited.
     * @param configs the set of configurations for the various Cars being generated.
     * @param workspaceDeclName the name of the workspace declaration to look up.
     */
    private static void addToListOfImports(Set<String> imports, Set<String> alreadyVisitedDecls, ConfigurationsForOneCarPerWorkspaceDeclaration configs, String workspaceDeclName) {
       
        if (alreadyVisitedDecls.contains(workspaceDeclName)) {
            return;
        }
       
        alreadyVisitedDecls.add(workspaceDeclName);
       
        LinkedHashSet<String> origImports = configs.importsMap.get(workspaceDeclName);
        if (origImports == null) {
            return;
        }
       
        for (final String importedDecl : origImports) {
           
            FileOutputConfiguration fileOutputConfigForImportedDecl = configs.workspaceDeclToConfigMap.get(importedDecl);
           
            if (fileOutputConfigForImportedDecl != null) {
               
                File cwsFile = fileOutputConfigForImportedDecl.cwsFile;
                if (cwsFile != null) {
                    String correspWorkspaceDecl = cwsFile.getName();
                   
                    if (correspWorkspaceDecl != null) {
                        imports.add(correspWorkspaceDecl);
                    }
                }
               
            } else {
                // recursive call to extract the imports of this declaration which does not have a corresponding
                // Car being generated.
                addToListOfImports(imports, alreadyVisitedDecls, configs, importedDecl);
            }
        }
    }

    /**
     * Strips the trailing '.cws' from the given string.
     * @param workspaceDeclName the workspace declaration name.
     * @return the given string with the trailing '.cws' stripped.
     */
    private static String stripTrailingDotCWS(String workspaceDeclName) {
        return workspaceDeclName.replaceAll("\\" + DOT_CWS + "$", "");
    }

    /**
     * Strips the trailing '.car' from the given string.
     * @param carName the Car name.
     * @return the given string with the trailing '.car' stripped.
     */
    private static String stripTrailingDotCAR(String carName) {
        return carName.replaceAll("\\" + DOT_CAR + "$", "");
    }
   
    /**
     * Builds a Car according to the given configuration and writes it out to the specified output file.
     * @param config the configuration for building the Car.
     * @param outputDir the output directory to be written to.
     * @param options configuration options for the Car builder.
     * @throws FileNotFoundException if the output file is not a valid location.
     * @return true if, at the end, the operation is not canceled by the user.
     * @throws IOException
     */
    public static boolean buildCar(Configuration config, File outputDir, String carName, BuilderOptions options) throws FileNotFoundException, IOException {
        File cwsFile;
       
        if (options.shouldGenerateCorrespWorkspaceDecl) {
            cwsFile = makeOutputCorrespWorkspaceDeclFile(outputDir, carName, options.noCarSuffixInWorkspaceDeclName);
        } else {
            cwsFile = null;
        }
       
        FileOutputConfiguration fileOutputConfig = new FileOutputConfiguration(config, makeOutputCarFile(outputDir, carName, options), carName, cwsFile);
       
        return buildCar(fileOutputConfig);
    }
   
    /**
     * Constructs a Car name from a source workspace declaration name. For example
     * everything.default.cws -> everything.default.car
     *
     * @param sourceWorkspaceDeclName the source workspace declaration name.
     * @return the corresponding Car name.
     */
    public static String makeCarNameFromSourceWorkspaceDeclName(String sourceWorkspaceDeclName) {
        return stripTrailingDotCWS(sourceWorkspaceDeclName) + DOT_CAR;
    }
   
    /**
     * Constructs a {@link File} for the named Car to be generated.
     * @param rootOutputDir the root output directory.
     * @param carName the name of the Car.
     * @param options the associated BuilderOptions object for this Car building operation.
     * @return the corresponding File object.
     */
    private static File makeOutputCarFile(File rootOutputDir, String carName, BuilderOptions options) {
        if (options.shouldGenerateCarJarSuffix) {
            return new File(rootOutputDir, carName + DOT_JAR);
        } else {
            File carFolder = new File(rootOutputDir, CAR_FOLDER_NAME);
            return new File(carFolder, carName);
        }
    }
   
    /**
     * Constructs a {@link File} for the corresponding workspace declaration to be generated for the given Car.
     * @param rootOutputDir the root output directory.
     * @param carName the name of the Car.
     * @param noCarSuffixInWorkspaceDeclName whether the generated workspace declaration should end simply with '.cws' instead of '.car.cws'.
     * @return the corresponding File object.
     */
    private static File makeOutputCorrespWorkspaceDeclFile(File rootOutputDir, String carName, boolean noCarSuffixInWorkspaceDeclName) {
        String cwsName = makeOutputCorrespWorkspaceDeclName(carName, noCarSuffixInWorkspaceDeclName);
       
        File workspaceDeclFolder = new File(rootOutputDir, WORKSPACE_DECLARATIONS_FOLDER_NAME);
        return new File(workspaceDeclFolder, cwsName);
    }

    /**
     * Constructs a name for the corresponding workspace declaration to be generated for the given Car. For example
     * everything.default.car -> everything.default.car.cws (if noCarSuffixInWorkspaceDeclName is false), OR
     * everything.default.car -> everything.default.cws     (if noCarSuffixInWorkspaceDeclName is true)
     *
     * @param carName the name of the Car.
     * @param noCarSuffixInWorkspaceDeclName whether the generated workspace declaration should end simply with '.cws' instead of '.car.cws'.
     * @return the name of the workspace declaration file.
     */
    public static String makeOutputCorrespWorkspaceDeclName(String carName, boolean noCarSuffixInWorkspaceDeclName) {
        if (noCarSuffixInWorkspaceDeclName) {
            return stripTrailingDotCAR(carName) + DOT_CWS;
        } else {
            return carName + DOT_CWS;
        }
    }
   
    /**
     * Builds a Car according to the given configuration and writes it out to the specified output file.
     * @param fileOutputConfig the configuration for building the Car.
     * @throws FileNotFoundException if the output file is not a valid location.
     * @return true if, at the end, the operation is not canceled by the user.
     * @throws IOException
     */
    public static boolean buildCar(FileOutputConfiguration fileOutputConfig) throws FileNotFoundException, IOException {
        final Configuration config = fileOutputConfig.getConfig();
       
        config.monitor.operationStarted(1, config.moduleNames.length);
        try {
            return buildCarHelper(fileOutputConfig);
        } finally {
            config.monitor.operationDone();
        }
    }

    /**
     * Builds a Car according to the given configuration and writes it out to the specified output file.
     * @param fileOutputConfig the configuration for building the Car.
     * @throws FileNotFoundException if the output file is not a valid location.
     * @return true if, at the end, the operation is not canceled by the user.
     * @throws IOException
     */
    private static boolean buildCarHelper(FileOutputConfiguration fileOutputConfig) throws FileNotFoundException, IOException {
        final Configuration config = fileOutputConfig.getConfig();
        final File outputFile = fileOutputConfig.getCarFile();
        final String carName = fileOutputConfig.getCarName();
        final boolean shouldGenerateCarJarSuffix = fileOutputConfig.shouldGenerateCarJarSuffix();
       
        // generate the corresponding cws file if requested
        final File cwsFile = fileOutputConfig.getWorkspaceDeclarationFile();
        if (cwsFile != null) {
            final String cwsName = cwsFile.getName();
            final Collection<String> additionalImportsForWorkspaceDecl = fileOutputConfig.getAdditionalImportsForWorkspaceDecl();
           
            if (shouldGenerateCarJarSuffix) {
                StringWriter writer = new StringWriter();
               
                writeCorrespondingWorkspaceDeclaration(config, carName, additionalImportsForWorkspaceDecl, writer, cwsName);
               
                byte[] cwsContents = TextEncodingUtilities.getUTF8Bytes(writer.toString());
               
                config.addAdditionalFile(WorkspaceDeclarationNullaryStore.WORKSPACE_DECLARATION_IN_JAR_BASE_FOLDER.extendFile(cwsName).getPathStringMinusSlash(), cwsContents);
               
            } else {
                FileSystemHelper.ensureDirectoryExists(cwsFile.getParentFile());
                Writer writer = new FileWriter(cwsFile);
               
                writeCorrespondingWorkspaceDeclaration(config, carName, additionalImportsForWorkspaceDecl, writer, cwsName);
            }
        }
       
        return buildCarHelper(config, carName, outputFile);
    }
   
    /**
     * Writes the corresponding workspace declaration that references the Car being built to the specified writer.
     * @param config the Car's configuration.
     * @param carName the name of the Car.
     * @param additionalImportsForWorkspaceDecl additional imports to be added to the workspace declaration.
     * @param writer the Writer to write to.
     * @param cwsName the name of the workspace declaration file to generate.
     * @throws IOException
     */
    private static void writeCorrespondingWorkspaceDeclaration(Configuration config, String carName, Collection<String> additionalImportsForWorkspaceDecl, Writer writer, String cwsName) throws IOException {
        try {
            PrintWriter printWriter = new PrintWriter(writer);
           
            String comment = MESSAGES.getString("generatedCWSFileComment", cwsName, new Date());
            String[] commentLines = comment.split("\n");
           
            for (final String element : commentLines) {
                printWriter.println(element);
            }
           
            printWriter.println();
           
            printWriter.println(config.getWorkspaceDeclarationImportLineForStandardVaultCar(carName));
           
            for (final String additionalImport : additionalImportsForWorkspaceDecl) {
                printWriter.println("import StandardVault " + additionalImport);
            }
           
            printWriter.flush();
        } finally {
            writer.close();
        }
    }
   
    ////===========================================================================================
    /// Fundamental routines for building a single Car.
    //

    /**
     * Builds a Car according to the given configuration and writes it out to the specified output file.
     * @param config the configuration for building the Car.
     * @param carName the canonical name of the Car to be built.
     * @param outputFile the output file to be written to.
     * @throws FileNotFoundException if the output file is not a valid location.
     * @return true if, at the end, the operation is not canceled by the user.
     * @throws IOException
     */
    public static boolean buildCar(Configuration config, String carName, File outputFile) throws FileNotFoundException, IOException {
        config.monitor.operationStarted(1, config.moduleNames.length);
        try {
            return buildCarHelper(config, carName, outputFile);
        } finally {
            config.monitor.operationDone();
        }
    }
   
    /**
     * Builds a Car according to the given configuration and writes it out to the specified output file.
     * @param config the configuration for building the Car.
     * @param carName the canonical name of the Car to be built.
     * @param outputFile the output file to be written to.
     * @throws FileNotFoundException if the output file is not a valid location.
     * @return true if, at the end, the operation is not canceled by the user.
     * @throws IOException
     */
    private static boolean buildCarHelper(Configuration config, String carName, File outputFile) throws FileNotFoundException, IOException {
        FileSystemHelper.ensureDirectoryExists(outputFile.getParentFile());
        FileOutputStream fos = new FileOutputStream(outputFile);
       
        boolean notCanceled = false;
       
        try {
            notCanceled = buildCarHelper(config, carName, outputFile.getName(), fos);
           
            return notCanceled;
           
        } finally {
            fos.close();
           
            // delete the incomplete Car if the operation was canceled or the operation failed with an exception
            if (!notCanceled) {
                outputFile.delete();
            }
        }
    }

    /**
     * Builds a Car according to the given configuration and writes it out to the specified output stream.
     * @param config the configuration for building the Car.
     * @param carName the name of the Car to be built.
     * @param outputStream the output stream to be written to.
     * @return true if, at the end, the operation is not canceled by the user.
     * @throws IOException
     */
    public static boolean buildCar(Configuration config, String carName, OutputStream outputStream) throws IOException {
        config.monitor.operationStarted(1, config.moduleNames.length);
        try {
            return buildCarHelper(config, carName, carName, outputStream);
        } finally {
            config.monitor.operationDone();
        }
    }
   
    /**
     * Builds a Car according to the given configuration and writes it out to the specified output stream.
     * @param config the configuration for building the Car.
     * @param carName the canonical name of the Car to be built.
     * @param fileName the file name of the Car to be built.
     * @param outputStream the output stream to be written to.
     * @return true if, at the end, the operation is not canceled by the user.
     * @throws IOException
     */
    private static boolean buildCarHelper(Configuration config, String carName, String fileName, OutputStream outputStream) throws IOException {
        // the configuration came with a canceled monitor, so we shouldn't do anything
        if (config.monitor.isCanceled()) {
            return false;
        }
       
        WorkspaceManager workspaceManager = config.workspaceManager;
        ModuleName[] moduleNames = config.moduleNames;
        Monitor monitor = config.monitor;
       
        CALWorkspace workspace = workspaceManager.getWorkspace();
       
        ProgramResourcePathMapper programResourcePathMapper = new ProgramResourcePathMapper(workspaceManager.getMachineType());
       
        BufferedOutputStream bos = new BufferedOutputStream(outputStream, 1024);
        JarOutputStream jos = new JarOutputStream(bos);
       
        try {
            // Write out a minimal manifest.
            ModulePackager.writeMinimalManifestToCar(jos, carName);
           
            // Set compression level.
            jos.setLevel(Deflater.DEFAULT_COMPRESSION);
           
            // Write out the Car marker
            ZipEntry markerEntry = new ZipEntry(CAR_MARKER_NAME);
            jos.putNextEntry(markerEntry);
            jos.closeEntry();
           
            // Write out the spec files
            for (final Map.Entry<String,CarSpec> entry : config.carSpecs.entrySet()) {
                String specName = entry.getKey();
                CarSpec carSpec = entry.getValue();
               
                ZipEntry zipEntry = new ZipEntry(Car.CAR_WORKSPACE_SPEC_FOLDER.extendFile(specName).getPathStringMinusSlash());
               
                jos.putNextEntry(zipEntry);
                try {
                    carSpec.writeTo(jos);
                } finally {
                    jos.closeEntry();
                }
            }

            // Add each module and all their resources to the Car
            monitor.carBuildingStarted(fileName, moduleNames.length);
           
            for (int i = 0; i < moduleNames.length && !monitor.isCanceled(); i++) {
                ModuleName moduleName = moduleNames[i];
                monitor.processingModule(fileName, moduleName);
               
                ModulePackager.writeModuleToJar(workspace, moduleName, jos, config.buildSourcelessModules);
                writeModuleProgramResourcesToCar(workspaceManager, moduleName, jos, programResourcePathMapper);
            }
           
            // Add the additional files to the Car.
            List<String> additionalFileRelativePaths = new ArrayList<String>();
           
            for (final Map.Entry<String, byte[]> entry : config.additionalFiles.entrySet()) {
                if (monitor.isCanceled()) {
                    break;
                }
                String relativePath = entry.getKey();
                byte[] bytes = entry.getValue();
               
                ZipEntry zipEntry = new ZipEntry(relativePath);
               
                jos.putNextEntry(zipEntry);
                try {
                    jos.write(bytes);
                } finally {
                    jos.closeEntry();
                }
               
                additionalFileRelativePaths.add(relativePath);
            }
           
            // Write out the list of additional files.
            ZipEntry additionalFileListEntry = new ZipEntry(CAR_ADDITIONAL_FILES_NAME);
           
            jos.putNextEntry(additionalFileListEntry);
            try {
                StringBuilder builder = new StringBuilder();
                for (int k = 0, n = additionalFileRelativePaths.size(); k < n; k++) {
                    if (k > 0) {
                        builder.append('\n');
                    }
                    builder.append(additionalFileRelativePaths.get(k));
                }
               
                writeStringToStream(jos, builder.toString());
               
            } finally {
                jos.closeEntry();
            }
           
            return !config.monitor.isCanceled();
           
        } finally {
            monitor.carBuildingDone(fileName);
           
            jos.flush();
            bos.flush();
           
            jos.close();
            bos.close();
        }
    }
   
    /**
     * Writes the program resources (e.g. cmi and lc files) of the specified module to the Car through the given JarOutputStream.
     *
     * @param workspaceManager the workspace manager providing the resources.
     * @param moduleName the name of the module.
     * @param jos the JarOutputStream for the Car.
     * @param programResourcePathMapper the path mapper for program resources.
     * @throws IOException
     */
    private static void writeModuleProgramResourcesToCar(WorkspaceManager workspaceManager, ModuleName moduleName, JarOutputStream jos, ProgramResourcePathMapper programResourcePathMapper) throws IOException {
       
        ResourcePath.Folder moduleFolderPath = (ResourcePath.Folder)programResourcePathMapper.getModuleResourcePath(moduleName);
        ProgramResourceLocator.Folder folderLocator = new ProgramResourceLocator.Folder(moduleName, ResourcePath.EMPTY_PATH);
       
        ProgramResourceRepository programResourceRepository = workspaceManager.getRepository();
        ProgramResourceLocator[] resourceLocators = programResourceRepository.getMembers(folderLocator);
       
        for (final ProgramResourceLocator resourceLocator : resourceLocators) {
            if (resourceLocator instanceof ProgramResourceLocator.File) {
               
                ProgramResourceLocator.File fileLocator = (ProgramResourceLocator.File)resourceLocator;
               
                String relativePath = moduleFolderPath.extendFile(resourceLocator.getName()).getPathStringMinusSlash();
               
                ZipEntry entry = new ZipEntry(relativePath);
               
                jos.putNextEntry(entry);
                InputStream inputStream = programResourceRepository.getContents(fileLocator)// this call throws IOException, and should not return null on error
                try {
                    FileSystemResourceHelper.transferData(inputStream, jos);
                } finally {
                    // We need to close the input stream explicitly or else we will be leaking file handles
                    inputStream.close();
                    jos.closeEntry();
                }
            }
        }
    }
   
    /**
     * Writes the given string to the specified stream.
     * @param outputStream
     * @param string
     * @throws IOException
     */
    private static void writeStringToStream(OutputStream outputStream, String string) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        OutputStreamWriter osw = new OutputStreamWriter(baos);
        osw.write(string);
        osw.flush();
        baos.flush();
        byte[] bytes = baos.toByteArray();
       
        outputStream.write(bytes);
    }
}
TOP

Related Classes of org.openquark.cal.services.CarBuilder

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.