File: Base\System\IO\Packaging\Package.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//-----------------------------------------------------------------------------
//
// <copyright file="Package.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// Description:
//  This is a base abstract class for Package. This is a part of the
//  Packaging Layer.
//
// History:
//  01/03/2004: SarjanaS: Initial creation. [Stubs only]
//  03/01/2004: SarjanaS: Implemented the functionality for all the members.
//  03/17/2004: BruceMac: Initial implementation or PackageRelationship methods
//
//-----------------------------------------------------------------------------
 
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;   // For SortedList<>
using System.Windows;               // For Exception strings - SRID
using System.Diagnostics;           // For Debug.Assert
using MS.Internal.IO.Packaging;
using MS.Internal.WindowsBase;      // for [FriendAccessAllowed]
 
using MS.Internal;                  // For Invariant.Assert
using MS.Utility;
 
namespace System.IO.Packaging
{
    /// <summary>
    /// Abstract Base class for the Package.
    /// This is a part of the Packaging Layer APIs
    /// </summary>
    public abstract class Package : IDisposable
    {
 
        //------------------------------------------------------
        //
        //  Public Constructors
        //
        //------------------------------------------------------
 
        #region Protected Constructor
 
        /// <summary>
        /// Protected constructor for the abstract Base class.
        /// This is the current contract between the subclass and the base class
        /// If we decide some registration mechanism then this might change
        /// </summary>
        /// <param name="openFileAccess"></param>
        /// <exception cref="ArgumentOutOfRangeException">If FileAccess enumeration does not have one of the valid values</exception>
        protected Package(FileAccess openFileAccess)
            : this(openFileAccess, false /* not streaming by default */)
        {
        }
 
        /// <summary>
        /// Protected constructor for the abstract Base class.
        /// This is the current contract between the subclass and the base class
        /// If we decide some registration mechanism then this might change
        /// </summary>
        /// <param name="openFileAccess"></param>
        /// <param name="streaming">Whether the package is being opened for streaming.</param>
        /// <exception cref="ArgumentOutOfRangeException">If FileAccess enumeration does not have one of the valid values</exception>
        protected Package(FileAccess openFileAccess, bool streaming)
        {
            ThrowIfFileAccessInvalid(openFileAccess);
 
            _openFileAccess      = openFileAccess;
 
            //PackUriHelper.ValidatedPartUri implements the IComparable interface.
            _partList            = new SortedList<PackUriHelper.ValidatedPartUri, PackagePart>(); // initial default is zero
            _partCollection      = null;
            _disposed            = false;
            _inStreamingCreation = (openFileAccess == FileAccess.Write && streaming);
        }
 
        #endregion Protected Constructor
 
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
 
        #region Public Properties
 
        /// <summary>
        /// Gets the FileAccess with which the package was opened. This is a read only property.
        /// This property gets set when the package is opened.
        /// </summary>
        /// <value>FileAccess</value>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        public FileAccess FileOpenAccess
        {
            get
            {
                ThrowIfObjectDisposed();
                return _openFileAccess;
            }
        }
 
        /// <summary>
        /// The package properties are a subset of the standard OLE property sets
        /// SummaryInformation and DocumentSummaryInformation, and include such properties
        /// as Title and Subject.
        /// </summary>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        public PackageProperties PackageProperties
        {
            get
            {
                ThrowIfObjectDisposed();
 
                if (_packageProperties == null)
                    _packageProperties = new PartBasedPackageProperties(this);
                return _packageProperties;
            }
        }
 
        #endregion Public Properties
 
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
 
        #region Public Methods
 
        #region OpenOnFileMethods
 
        /// <summary>
        /// Opens a package at the specified Path. This method calls the overload which accepts all the parameters
        /// with the following defaults -
        /// FileMode - FileMode.OpenOrCreate,
        /// FileAccess - FileAccess.ReadWrite
        /// FileShare  - FileShare.None
        /// </summary>
        /// <param name="path">Path to the package</param>
        /// <returns>Package</returns>
        /// <exception cref="ArgumentNullException">If path parameter is null</exception>
        public static Package Open(string path)
        {
            return Open(path, _defaultFileMode, _defaultFileAccess, _defaultFileShare);
        }
 
        /// <summary>
        /// Opens a package at the specified Path in the given mode. This method calls the overload which
        /// accepts all the parameters with the following defaults -
        /// FileAccess - FileAccess.ReadWrite
        /// FileShare  - FileShare.None
        /// </summary>
        /// <param name="path">Path to the package</param>
        /// <param name="packageMode">FileMode in which the package should be opened</param>
        /// <returns>Package</returns>
        /// <exception cref="ArgumentNullException">If path parameter is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileMode enumeration [packageMode] does not have one of the valid values</exception>
        public static Package Open(string path, FileMode packageMode)
        {
            return Open(path, packageMode, _defaultFileAccess, _defaultFileShare);
        }
 
        /// <summary>
        /// Opens a package at the specified Path in the given mode with the specified access. This method calls
        /// the overload which accepts all the parameters with the following defaults -
        /// FileShare  - FileShare.None
        /// </summary>
        /// <param name="path">Path to the package</param>
        /// <param name="packageMode">FileMode in which the package should be opened</param>
        /// <param name="packageAccess">FileAccess with which the package should be opened</param>
        /// <returns>Package</returns>
        /// <exception cref="ArgumentNullException">If path parameter is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileMode enumeration [packageMode] does not have one of the valid values</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileAccess enumeration [packageAccess] does not have one of the valid values</exception>
        public static Package Open(string path, FileMode packageMode, FileAccess packageAccess)
        {
            return Open(path, packageMode, packageAccess, _defaultFileShare);
        }
 
        /// <summary>
        /// Opens the package with the specified parameters.
        ///
        /// Note:-
        /// Since currently there is no plan to implement a generic registration mechanism, this method
        /// has some hard coded knowledge about the sub classes. This might change later if we come up
        /// with a registration mechanism.
        /// There is no caching mechanism in case the FileShare is specified to ReadWrite/Write.
        /// We do not have any refresh mechanism, to make sure that the cached parts reflect the actual parts,
        /// in the case where there might be more than one processes writing to the same underlying package.
        /// </summary>
        /// <param name="path">Path to the package</param>
        /// <param name="packageMode">FileMode in which the package should be opened</param>
        /// <param name="packageAccess">FileAccess with which the package should be opened</param>
        /// <param name="packageShare">FileShare with which the package is opened.</param>
        /// <returns>Package</returns>
        /// <exception>InvalidArgumentException - If the combination of the FileMode,
        /// FileAccess and FileShare parameters is not meaningful.</exception>
        /// <exception cref="ArgumentNullException">If path parameter is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileMode enumeration [packageMode] does not have one of the valid values</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileAccess enumeration [packageAccess] does not have one of the valid values</exception>
        public static Package Open(string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare)
        {
            return Open(path, packageMode, packageAccess, packageShare, false /* not in streaming mode */);
        }
 
        #endregion OpenOnFileMethods
 
        #region OpenOnStreamMethods
 
        /// <summary>
        /// Open a package on this stream. This method calls the overload which accepts all the parameters
        /// with the following defaults -
        /// FileMode - FileMode.Open
        /// FileAccess - FileAccess.Read
        /// </summary>
        /// <param name="stream">Stream on which the package is to be opened</param>
        /// <returns>Package</returns>
        /// <exception cref="ArgumentNullException">If stream parameter is null</exception>
        /// <exception cref="IOException">If package to be created should have readwrite/read access and underlying stream is write only</exception>
        /// <exception cref="IOException">If package to be created should have readwrite/write access and underlying stream is read only</exception>
        public static Package Open(Stream stream)
        {
            return Open(stream, _defaultStreamMode, _defaultStreamAccess);
        }
 
        /// <summary>
        /// Open a package on this stream. This method calls the overload which accepts all the parameters
        /// with the following defaults -
        /// FileAccess - FileAccess.ReadWrite
        /// </summary>
        /// <param name="stream">Stream on which the package is to be opened</param>
        /// <param name="packageMode">FileMode in which the package should be opened.</param>
        /// <returns>Package</returns>
        /// <exception cref="ArgumentNullException">If stream parameter is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileMode enumeration [packageMode] does not have one of the valid values</exception>
        /// <exception cref="IOException">If package to be created should have readwrite/read access and underlying stream is write only</exception>
        /// <exception cref="IOException">If package to be created should have readwrite/write access and underlying stream is read only</exception>
        public static Package Open(Stream stream, FileMode packageMode)
        {
            //If the user is providing a FileMode, in all the modes, except FileMode.Open,
            //its most likely that the user intends to write to the stream.
            return Open(stream, packageMode, _defaultFileAccess);
        }
 
        /// <summary>
        /// Opens a package on this stream. The package is opened in the specified mode and with the access
        /// specified.
        /// </summary>
        /// <param name="stream">Stream on which the package is created</param>
        /// <param name="packageMode">FileMode in which the package is to be opened</param>
        /// <param name="packageAccess">FileAccess on the package that is opened</param>
        /// <returns>Package</returns>
        /// <exception cref="ArgumentNullException">If stream parameter is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileMode enumeration [packageMode] does not have one of the valid values</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileAccess enumeration [packageAccess] does not have one of the valid values</exception>
        /// <exception cref="IOException">If package to be created should have readwrite/read access and underlying stream is write only</exception>
        /// <exception cref="IOException">If package to be created should have readwrite/write access and underlying stream is read only</exception>
        public static Package Open(Stream stream, FileMode packageMode, FileAccess packageAccess)
        {
            return Open(stream, packageMode, packageAccess, false /* not in streaming mode */);
        }
        #endregion OpenOnStreamMethods
 
        #region PackagePart Methods
 
        /// <summary>
        /// Creates a new part in the package. An empty stream corresponding to this part will be created in the
        /// package. If a part with the specified uri already exists then we throw an exception.
        /// This methods will call the CreatePartCore method which will create the actual PackagePart in the package.
        /// </summary>
        /// <param name="partUri">Uri of the PackagePart that is to be added</param>
        /// <param name="contentType">ContentType of the stream to be added</param>
        /// <returns></returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is readonly, it cannot be modified</exception>
        /// <exception cref="ArgumentNullException">If partUri parameter is null</exception>
        /// <exception cref="ArgumentNullException">If contentType parameter is null</exception>
        /// <exception cref="ArgumentException">If partUri parameter does not conform to the valid partUri syntax</exception>
        /// <exception cref="InvalidOperationException">If a PackagePart with the given partUri already exists in the Package</exception>
        public PackagePart CreatePart(Uri partUri, string contentType)
        {
            return CreatePart(partUri, contentType, CompressionOption.NotCompressed);
        }
 
        /// <summary>
        /// Creates a new part in the package. An empty stream corresponding to this part will be created in the
        /// package. If a part with the specified uri already exists then we throw an exception.
        /// This methods will call the CreatePartCore method which will create the actual PackagePart in the package.
        /// </summary>
        /// <param name="partUri">Uri of the PackagePart that is to be added</param>
        /// <param name="contentType">ContentType of the stream to be added</param>
        /// <param name="compressionOption">CompressionOption  describing compression configuration
        /// for the new part. This compression apply only to the part, it doesn't affect relationship parts or related parts.
        /// This parameter is optional. </param>
        /// <returns></returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is readonly, it cannot be modified</exception>
        /// <exception cref="ArgumentNullException">If partUri parameter is null</exception>
        /// <exception cref="ArgumentNullException">If contentType parameter is null</exception>
        /// <exception cref="ArgumentException">If partUri parameter does not conform to the valid partUri syntax</exception>
        /// <exception cref="ArgumentOutOfRangeException">If CompressionOption enumeration [compressionOption] does not have one of the valid values</exception>
        /// <exception cref="InvalidOperationException">If a PackagePart with the given partUri already exists in the Package</exception>
        public PackagePart CreatePart(Uri partUri,
                            string contentType,
                            CompressionOption compressionOption)
        {
            ThrowIfObjectDisposed();
            ThrowIfReadOnly();
 
            if (partUri == null)
                throw new ArgumentNullException("partUri");
 
            if (contentType == null)
                throw new ArgumentNullException("contentType");
 
            ThrowIfCompressionOptionInvalid(compressionOption);
 
            PackUriHelper.ValidatedPartUri validatedPartUri = PackUriHelper.ValidatePartUri(partUri);
 
            if (_partList.ContainsKey(validatedPartUri))
                throw new InvalidOperationException(SR.Get(SRID.PartAlreadyExists));
 
            // Add the part to the _partList if there is no prefix collision
            // Note: This is the only place where we pass a null to this method for the part and if the
            // methods returns successfully then we replace the null with an actual part.
            AddIfNoPrefixCollisionDetected(validatedPartUri, null /* since we don't have a part yet */);
 
            PackagePart addedPart = CreatePartCore(validatedPartUri,
                                                            contentType,
                                                            compressionOption);
 
            //Set the entry for this Uri with the actual part
            _partList[validatedPartUri] = addedPart;
 
            return addedPart;
        }
 
 
 
        /// <summary>
        /// Returns a part that already exists in the package. If the part
        /// Corresponding to the URI does not exist in the package then an exception is
        /// thrown. The method calls the GetPartCore method which actually fetches the part.
        /// </summary>
        /// <param name="partUri"></param>
        /// <returns></returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is write only, information cannot be retrieved from it</exception>
        /// <exception cref="ArgumentNullException">If partUri parameter is null</exception>
        /// <exception cref="ArgumentException">If partUri parameter does not conform to the valid partUri syntax</exception>
        /// <exception cref="InvalidOperationException">If the requested part does not exists in the Package</exception>
        public PackagePart GetPart(Uri partUri)
        {
            PackagePart returnedPart = GetPartHelper(partUri);
            if (returnedPart == null)
                throw new InvalidOperationException(SR.Get(SRID.PartDoesNotExist));
            else
                return returnedPart;
        }
 
 
        /// <summary>
        /// This is a convenient method to check whether a given part exists in the
        /// package. This will have a default implementation that will try to retrieve
        /// the part and then if successful, it will return true.
        /// If the custom file format has an easier way to do this, they can override this method
        /// to get this information in a more efficient way.
        /// </summary>
        /// <param name="partUri"></param>
        /// <returns></returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is write only, information cannot be retrieved from it</exception>
        /// <exception cref="ArgumentNullException">If partUri parameter is null</exception>
        /// <exception cref="ArgumentException">If partUri parameter does not conform to the valid partUri syntax</exception>
        public virtual bool PartExists(Uri partUri)
        {
            return (GetPartHelper(partUri) != null);
        }
 
 
        /// <summary>
        /// This method will do all the house keeping required when a part is deleted
        /// Then the DeletePartCore method will be called which will have the actual logic to
        /// do the work specific to the underlying file format and will actually delete the
        /// stream corresponding to this part. This method does not throw if the specified
        /// part does not exist. This is in conformance with the FileInfo.Delete call.
        /// </summary>
        /// <param name="partUri"></param>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is readonly, it cannot be modified</exception>
        /// <exception cref="ArgumentNullException">If partUri parameter is null</exception>
        /// <exception cref="ArgumentException">If partUri parameter does not conform to the valid partUri syntax</exception>
        public void DeletePart(Uri partUri)
        {
            ThrowIfObjectDisposed();
            ThrowIfReadOnly();
            ThrowIfInStreamingCreation("DeletePart");
 
            if (partUri == null)
                throw new ArgumentNullException("partUri");
 
            PackUriHelper.ValidatedPartUri validatedPartUri = (PackUriHelper.ValidatedPartUri)PackUriHelper.ValidatePartUri(partUri);
 
            if (_partList.ContainsKey(validatedPartUri))
            {
                //This will get the actual casing of the part that
                //is stored in the partList which is equivalent to the
                //partUri provided by the user
                validatedPartUri = (PackUriHelper.ValidatedPartUri)_partList[validatedPartUri].Uri;
                _partList[validatedPartUri].IsDeleted = true;
                _partList[validatedPartUri].Close();
 
                //Call the Subclass to delete the part
 
                //!!Important Note: The order of this call is important as one of the
                //sub-classes - ZipPackage relies upon the abstract layer to be
                //able to provide the ZipPackagePart in order to do the proper
                //clean up and delete operation.
                //The dependency is in ZipPackagePart.DeletePartCore method.
                //Ideally we would have liked to avoid this kind of a restriction
                //but due to the current class interfaces and data structure ownerships
                //between these objects, it tough to re-design at this point.
                DeletePartCore(validatedPartUri);
 
                //Finally remove it from the list of parts in the cache
                _partList.Remove(validatedPartUri);
            }
            else
                //If the part is not in memory we still call the underlying layer
                //to delete the part if it exists
                DeletePartCore(validatedPartUri);
 
            if (PackUriHelper.IsRelationshipPartUri(validatedPartUri))
            {
                //We clear the in-memory data structure corresponding to that relationship part
                //This will ensure that the intention of the user to delete the part, is respected.
                //And thus we will not try to recreate it just in case there was some data in the
                //memory structure.
 
                Uri owningPartUri = PackUriHelper.GetSourcePartUriFromRelationshipPartUri(validatedPartUri);
                //Package-level relationships in /_rels/.rels
                if (Uri.Compare(owningPartUri, PackUriHelper.PackageRootUri, UriComponents.SerializationInfoString, UriFormat.UriEscaped, StringComparison.Ordinal)==0)
                {
                    //Clear any data in memory
                    this.ClearRelationships();
                }
                else
                {
                    //Clear any data in memory
                    if (this.PartExists(owningPartUri))
                    {
                        PackagePart owningPart = this.GetPart(owningPartUri);
                        owningPart.ClearRelationships();
                    }
                }
            }
            else
            {
                // remove any relationship part
                DeletePart(PackUriHelper.GetRelationshipPartUri(validatedPartUri));
            }
        }
 
        /// <summary>
        /// This returns a collection of all the Parts within the package.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is writeonly, no information can be retrieved from it</exception>
        public PackagePartCollection GetParts()
        {
            ThrowIfObjectDisposed();
            ThrowIfWriteOnly();
 
            //Ideally we should decide whether we should query the underlying layer for parts based on the
            //FileShare enum. But since we do not have that information, currently the design is to just
            //query the underlying layer once.
            //Note:
            //Currently the incremental behavior for GetPart method is not consistent with the GetParts method
            //which just queries the underlying layer once.
            if (_partCollection == null)
            {
                PackagePart[] parts = GetPartsCore();
 
                //making sure that we get a valid array
                Debug.Assert((parts != null),
                    "Subclass is expected to return an array [an empty one if there are no parts] as a result of GetPartsCore method call. ");
 
                PackUriHelper.ValidatedPartUri partUri;
 
                //We need this dictionary to detect any collisions that might be present in the
                //list of parts that was given to us from the underlying physical layer, as more than one
                //partnames can be mapped to the same normalized part.
                //Note: We cannot use the _partList member variable, as that gets updated incrementally and so its
                //not possible to find the collisions using that list.
                //PackUriHelper.ValidatedPartUri implements the IComparable interface.
                Dictionary<PackUriHelper.ValidatedPartUri, PackagePart> seenPartUris = new Dictionary<PackUriHelper.ValidatedPartUri, PackagePart>(parts.Length);
 
                for (int i = 0; i < parts.Length; i++)
                {
                    partUri = (PackUriHelper.ValidatedPartUri)parts[i].Uri;
 
                    if (seenPartUris.ContainsKey(partUri))
                        throw new FileFormatException(SR.Get(SRID.BadPackageFormat));
                    else
                    {
                        // Add the part to the list of URIs that we have already seen
                        seenPartUris.Add(partUri, parts[i]);
 
                        if (!_partList.ContainsKey(partUri))
                        {
                            // Add the part to the _partList if there is no prefix collision
                            AddIfNoPrefixCollisionDetected(partUri, parts[i]);
                        }
                    }
                }
                _partCollection = new PackagePartCollection(_partList);
            }
            return _partCollection;
        }
 
        #endregion PackagePart Methods
 
        #region IDisposable Methods
 
        /// <summary>
        /// Member of the IDisposable interface. This method will clean up all the resources.
        /// It calls the Flush method to make sure that all the changes made get persisted.
        /// Note - subclasses should only override Dispose(bool) if they have resources to release.
        /// See the Design Guidelines for the Dispose() pattern.
        /// </summary>
        void IDisposable.Dispose()
        {
            if (!_disposed)
            {
                try
                {
                    // put our house in order before involving the subclass
 
                    // close core properties
                    // This method will write out the core properties to the stream
                    // In non-streaming mode - These will get flushed to the disk as a part of the DoFlush operation
                    if (_packageProperties != null)
                        _packageProperties.Close();
 
                    // flush relationships
                    if (InStreamingCreation)
                        ClosePackageRelationships();
                    else
                        FlushRelationships();
 
                    //Write out the Relationship XML for the parts
                    //These streams will get flushed in the DoClose operation.
                    DoOperationOnEachPart(DoCloseRelationshipsXml);
 
                    // Close all the parts that are currently open
                    DoOperationOnEachPart(DoClose);
 
                    // start the dispose chain
                    Dispose(true);
                }
                finally
                {
                    // do this no matter what (handles case of poorly behaving subclass that doesn't call back into Dispose(bool)
                    _disposed = true;
                }
 
                //Since all the resources we care about are freed at this point.
                GC.SuppressFinalize(this);
            }
        }
 
        #endregion IDisposable Methods
 
        #region Other Methods
 
        /// <summary>
        /// Closes the package and all the underlying parts and relationships.
        /// Calls the Dispose Method, since they have the same semantics
        /// </summary>
        public void Close()
        {
            ((IDisposable)this).Dispose();
        }
 
        /// <summary>
        /// Flushes the contents of the parts and the relationships to the package.
        /// This method will call the FlushCore method which will do the actual flushing of contents.
        /// </summary>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is readonly, it cannot be modified</exception>
        public void Flush()
        {
            ThrowIfObjectDisposed();
            ThrowIfReadOnly();
 
            // Flush core properties (in streaming production, has to be done before parts get flushed).
            // Write core properties (in streaming production, has to be done before parts get flushed).
            // This call will write out the xml for the core properties to the stream
            // In non-streaming mode - These properties will get flushed to disk as a part of the DoFlush operation
            if (_packageProperties != null)
                _packageProperties.Flush();
 
            // Write package relationships XML to the relationship part stream.
            // These will get flushed to disk as a part of the DoFlush operation
            if (InStreamingCreation)
                FlushPackageRelationships(); // Create a piece.
            else
                FlushRelationships(); // Flush into .rels part.
 
            //Write out the Relationship XML for the parts
            //These streams will get flushed in the DoFlush operation.
            DoOperationOnEachPart(DoWriteRelationshipsXml);
 
            // Flush all the parts that are currently open.
            // This will flush part relationships.
            DoOperationOnEachPart(DoFlush);
 
            FlushCore();
        }
 
        #endregion Other Methods
 
        #region PackageRelationship Methods
 
        /// <summary>
        /// Creates a relationship at the Package level with the Target PackagePart specified as the Uri
        /// </summary>
        /// <param name="targetUri">Target's URI</param>
        /// <param name="targetMode">Enumeration indicating the base uri for the target uri</param>
        /// <param name="relationshipType">PackageRelationship type, having uri like syntax that is used to
        /// uniquely identify the role of the relationship</param>
        /// <returns></returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is readonly, it cannot be modified</exception>
        /// <exception cref="ArgumentNullException">If parameter "targetUri" is null</exception>
        /// <exception cref="ArgumentNullException">If parameter "relationshipType" is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If parameter "targetMode" enumeration does not have a valid value</exception>
        /// <exception cref="ArgumentException">If TargetMode is TargetMode.Internal and the targetUri is an absolute Uri </exception>
        /// <exception cref="ArgumentException">If relationship is being targeted to a relationship part</exception>
        public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType)
        {
            return CreateRelationship(targetUri, targetMode, relationshipType, null);
        }
 
        /// <summary>
        /// Creates a relationship at the Package level with the Target PackagePart specified as the Uri
        /// </summary>
        /// <param name="targetUri">Target's URI</param>
        /// <param name="targetMode">Enumeration indicating the base uri for the target uri</param>
        /// <param name="relationshipType">PackageRelationship type, having uri like syntax that is used to
        /// uniquely identify the role of the relationship</param>
        /// <param name="id">String that conforms to the xsd:ID datatype. Unique across the source's
        /// relationships. Null is OK (ID will be generated). An empty string is an invalid XML ID.</param>
        /// <returns></returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is readonly, it cannot be modified</exception>
        /// <exception cref="ArgumentNullException">If parameter "targetUri" is null</exception>
        /// <exception cref="ArgumentNullException">If parameter "relationshipType" is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If parameter "targetMode" enumeration does not have a valid value</exception>
        /// <exception cref="ArgumentException">If TargetMode is TargetMode.Internal and the targetUri is an absolute Uri </exception>
        /// <exception cref="ArgumentException">If relationship is being targeted to a relationship part</exception>
        /// <exception cref="System.Xml.XmlException">If parameter "id" is not a valid Xsd Id</exception>
        /// <exception cref="System.Xml.XmlException">If an id is provided in the method, and its not unique</exception>
        public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType, String id)
        {
            ThrowIfObjectDisposed();
            ThrowIfReadOnly();
            EnsureRelationships();
            //All parameter validation is done in the following call
            return _relationships.Add(targetUri, targetMode, relationshipType, id);
        }
 
        /// <summary>
        /// Deletes a relationship from the Package. This is done based on the
        /// relationship's ID. The target PackagePart is not affected by this operation.
        /// </summary>
        /// <param name="id">The ID of the relationship to delete. An invalid ID will not
        /// throw an exception, but nothing will be deleted.</param>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is readonly, it cannot be modified</exception>
        /// <exception cref="ArgumentNullException">If parameter "id" is null</exception>
        /// <exception cref="System.Xml.XmlException">If parameter "id" is not a valid Xsd Id</exception>
        public void DeleteRelationship(String id)
        {
            ThrowIfObjectDisposed();
            ThrowIfReadOnly();
            ThrowIfInStreamingCreation("DeleteRelationship");
 
            if (id == null)
                throw new ArgumentNullException("id");
 
            InternalRelationshipCollection.ThrowIfInvalidXsdId(id);
 
            EnsureRelationships();
            _relationships.Delete(id);
        }
 
        /// <summary>
        /// Returns a collection of all the Relationships that are
        /// owned by the package
        /// </summary>
        /// <returns></returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is write only, no information can be retrieved from it</exception>
        public PackageRelationshipCollection GetRelationships()
        {
            //All the validations for dispose and file access are done in the
            //GetRelationshipsHelper method.
            return GetRelationshipsHelper(null);
        }
 
        /// <summary>
        /// Returns a collection of filtered Relationships that are
        /// owned by the package
        /// The filter string is compared with the type of the relationships
        /// in a case sensitive and culture ignorant manner.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is write only, no information can be retrieved from it</exception>
        /// <exception cref="ArgumentNullException">If parameter "relationshipType" is null</exception>
        /// <exception cref="ArgumentException">If parameter "relationshipType" is an empty string</exception>
        public PackageRelationshipCollection GetRelationshipsByType(string relationshipType)
        {
            //These checks are made in the GetRelationshipsHelper as well, but we make them
            //here as we need to perform parameter validation
            ThrowIfObjectDisposed();
            ThrowIfWriteOnly();
 
            if (relationshipType == null)
                throw new ArgumentNullException("relationshipType");
 
            InternalRelationshipCollection.ThrowIfInvalidRelationshipType(relationshipType);
 
            return GetRelationshipsHelper(relationshipType);
        }
 
        /// <summary>
        /// Retrieve a relationship per ID.
        /// </summary>
        /// <param name="id">The relationship ID.</param>
        /// <returns>The relationship with ID 'id' or throw an exception if not found.</returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is write only, no information can be retrieved from it</exception>
        /// <exception cref="ArgumentNullException">If parameter "id" is null</exception>
        /// <exception cref="System.Xml.XmlException">If parameter "id" is not a valid Xsd Id</exception>
        /// <exception cref="InvalidOperationException">If the requested relationship does not exist in the Package</exception>
        public PackageRelationship GetRelationship(string id)
        {
            //All the validations for dispose and file access are done in the
            //GetRelationshipHelper method.
 
            PackageRelationship returnedRelationship = GetRelationshipHelper(id);
            if (returnedRelationship == null)
                throw new InvalidOperationException(SR.Get(SRID.PackageRelationshipDoesNotExist));
            else
                return returnedRelationship;
        }
 
        /// <summary>
        /// Returns whether there is a relationship with the specified ID.
        /// </summary>
        /// <param name="id">The relationship ID.</param>
        /// <returns>true iff a relationship with ID 'id' is defined on this source.</returns>
        /// <exception cref="ObjectDisposedException">If this Package object has been disposed</exception>
        /// <exception cref="IOException">If the package is write only, no information can be retrieved from it</exception>
        /// <exception cref="ArgumentNullException">If parameter "id" is null</exception>
        /// <exception cref="System.Xml.XmlException">If parameter "id" is not a valid Xsd Id</exception>
        public bool RelationshipExists(string id)
        {
            //All the validations for dispose and file access are done in the
            //GetRelationshipHelper method.
 
            return (GetRelationshipHelper(id) != null);
        }
 
        #endregion PackageRelationship Methods
 
        #endregion Public Methods
 
        #region Protected Abstract Methods
 
        /// <summary>
        /// This method is for custom implementation corresponding to the underlying file format.
        /// This method will actually add a new part to the package. An empty part should be
        /// created as a result of this call.
        /// </summary>
        /// <param name="partUri"></param>
        /// <param name="contentType"></param>
        /// <param name="compressionOption"></param>
        /// <returns></returns>
        protected abstract PackagePart CreatePartCore(Uri partUri,
                                                            string contentType,
                                                            CompressionOption compressionOption);
 
        /// <summary>
        /// This method is for custom implementation corresponding to the underlying file format.
        /// This method will actually return the part after reading the actual physical bits.
        /// If the PackagePart does not exists in the underlying package then this method should return a null.
        /// This method must not throw an exception if a part does not exist.
        /// </summary>
        /// <param name="partUri"></param>
        /// <returns></returns>
        protected abstract PackagePart GetPartCore(Uri partUri);
 
        /// <summary>
        /// This method is for custom implementation corresponding to the underlying file format.
        /// This method will actually delete the part from the underlying package.
        /// This method should not throw if the specified part does not exist.
        /// This is in conformance with the FileInfo.Delete call.
        /// </summary>
        /// <param name="partUri"></param>
        protected abstract void DeletePartCore(Uri partUri);
 
        /// <summary>
        /// This method is for custom implementation corresponding to the underlying file format.
        /// This is the method that knows how to get the actual parts. If there are no parts,
        /// this method should return an empty array.
        /// </summary>
        /// <returns></returns>
        protected abstract PackagePart[] GetPartsCore();
 
        /// <summary>
        /// This method is for custom implementation corresponding to the underlying file format.
        /// This method should be used to dispose the resources that are specific to the file format.
        /// Also everything should be flushed to the disc before closing the package.
        /// </summary>
        /// <remarks>Subclasses that manage non-memory resources should override this method and free these resources.
        /// Any override should be careful to always call base.Dispose(disposing) to ensure orderly cleanup.</remarks>
        protected virtual void Dispose(bool disposing)
        {
            if (!_disposed && disposing)
            {
                if (_partList != null)
                {
                    _partList.Clear();
                }
 
                if (_packageProperties != null)
                {
                    _packageProperties.Dispose();
                    _packageProperties = null;
                }
 
                //release objects
                _partList = null;
                _partCollection = null;
                _relationships = null;
 
                _disposed = true;
            }
        }
 
        /// <summary>
        /// This method is for custom implementation corresponding to the underlying file format.
        /// This method flushes the contents of the package to the disc.
        /// </summary>
        protected abstract void FlushCore();
 
        #endregion Protected Abstract Methods
 
        //------------------------------------------------------
        //
        //  Internal Constructors
        //
        //------------------------------------------------------
        // None
        //------------------------------------------------------
 
        //------------------------------------------------------
        //
        //   Internal Properties
        //
        //------------------------------------------------------
 
        #region Internal Properties
 
        /// <summary>
        /// true iff the package was opened for streaming.
        /// </summary>
        internal bool InStreamingCreation
        {
            get
            {
                return _inStreamingCreation;
            }
        }
 
        #endregion Internal Properties
 
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
 
        #region Internal Methods
        // Some operations are not supported while producing a package in streaming mode.
        internal void ThrowIfInStreamingCreation(string methodName)
        {
            if (_inStreamingCreation)
                throw new IOException(SR.Get(SRID.OperationIsNotSupportedInStreamingProduction, methodName));
        }
 
        // Some operations are supported only while producing a package in streaming mode.
        internal void ThrowIfNotInStreamingCreation(string methodName)
        {
            if (!InStreamingCreation)
                throw new IOException(SR.Get(SRID.MethodAvailableOnlyInStreamingCreation, methodName));
        }
 
        //If the container is readonly then we cannot add/delete to it
        internal void ThrowIfReadOnly()
        {
            if (_openFileAccess == FileAccess.Read)
                throw new IOException(SR.Get(SRID.CannotModifyReadOnlyContainer));
        }
 
        // If the container is writeonly, parts cannot be retrieved from it
        internal void ThrowIfWriteOnly()
        {
            if (_openFileAccess == FileAccess.Write)
                throw new IOException(SR.Get(SRID.CannotRetrievePartsOfWriteOnlyContainer));
        }
 
        // return true to continue
        internal delegate bool PartOperation(PackagePart p);
 
        internal static void ThrowIfFileModeInvalid(FileMode mode)
        {
            //We do the enum check as suggested by the following condition for performance reasons.
            if (mode < FileMode.CreateNew || mode > FileMode.Append)
                throw new ArgumentOutOfRangeException("mode");
        }
 
        internal static void ThrowIfFileAccessInvalid(FileAccess access)
        {
            //We do the enum check as suggested by the following condition for performance reasons.
            if (access < FileAccess.Read || access > FileAccess.ReadWrite)
                throw new ArgumentOutOfRangeException("access");
        }
 
        internal static void ThrowIfCompressionOptionInvalid(CompressionOption compressionOption)
        {
            //We do the enum check as suggested by the following condition for performance reasons.
            if (compressionOption < CompressionOption.NotCompressed || compressionOption > CompressionOption.SuperFast)
                throw new ArgumentOutOfRangeException("compressionOption");
        }
 
        #region Write-time streaming API
 
        /// <summary>
        /// This method gives the possibility of opening a package in streaming mode.
        /// When 'streaming' is true, the only allowed file modes are Create and CreateNew,
        /// the only allowed file access is Write and the only allowed FileShare values are
        /// Null and Read.
        /// </summary>
        /// <param name="path">Path to the package.</param>
        /// <param name="packageMode">FileMode in which the package should be opened.</param>
        /// <param name="packageAccess">FileAccess with which the package should be opened.</param>
        /// <param name="packageShare">FileShare with which the package is opened.</param>
        /// <param name="streaming">Whether to allow the creation of part pieces while enforcing write-once access.</param>
        /// <returns>Package</returns>
        /// <exception cref="ArgumentNullException">If path parameter is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileAccess enumeration [packageAccess] does not have one of the valid values</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileMode enumeration [packageMode] does not have one of the valid values</exception>
        internal static Package Open(
            string path,
            FileMode packageMode,
            FileAccess packageAccess,
            FileShare packageShare,
            bool streaming)
        {
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXOpenPackageBegin);
 
            Package package = null;
            try
            {
                if (path == null)
                    throw new ArgumentNullException("path");
 
                ThrowIfFileModeInvalid(packageMode);
                ThrowIfFileAccessInvalid(packageAccess);
 
                ValidateStreamingAccess(packageMode, packageAccess, packageShare, streaming);
 
                //Note: FileShare enum is not being verfied at this stage, as we do not interpret the flag in this
                //code at all and just pass it on to the next layer, where the necessary validation can be
                //performed. Also, there is no meaningful way to check this parameter at this layer, as the
                //FileShare enumeration is a set of flags and flags/Bit-fields can be combined using a
                //bitwise OR operation to create different values, and validity of these values is specific to
                //the actual physical implementation.
 
                //Verify if this is valid for filenames
                FileInfo packageFileInfo = new FileInfo(path);
 
                try
                {
                    package = new ZipPackage(packageFileInfo.FullName, packageMode, packageAccess, packageShare, streaming);
 
                    if (!package._inStreamingCreation) // No read operation in streaming production.
                    {
                        //We need to get all the parts if any exists from the underlying file
                        //so that we have the names in the Normalized form in our in-memory
                        //data structures.
                        //Note: If ever this call is removed, each individual call to GetPartCore,
                        //may result in undefined behavior as the underlying ZipArchive, maintains the
                        //files list as being case-sensitive.
                        if (package.FileOpenAccess == FileAccess.ReadWrite || package.FileOpenAccess == FileAccess.Read)
                            package.GetParts();
                    }
                }
                catch
                {
                    if (package != null)
                    {
                        package.Close();
                    }
 
                    throw;
                }
            }
            finally
            {
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXOpenPackageEnd);
            }
            return package;
        }
 
        /// <summary>
        /// This method gives the possibility of opening a package in streaming mode.
        /// When 'streaming' is true, the only allowed file modes are Create and CreateNew,
        /// and the only allowed file access is Write.
        /// </summary>
        /// <param name="stream">Stream on which the package is created</param>
        /// <param name="packageMode">FileMode in which the package is to be opened</param>
        /// <param name="packageAccess">FileAccess on the package that is opened</param>
        /// <param name="streaming">Whether to allow the creation of part pieces while enforcing write-once access.</param>
        /// <returns>Package</returns>
        /// <exception cref="ArgumentNullException">If stream parameter is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileMode enumeration [packageMode] does not have one of the valid values</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileAccess enumeration [packageAccess] does not have one of the valid values</exception>
        /// <exception cref="IOException">If package to be created should have readwrite/read access and underlying stream is write only</exception>
        /// <exception cref="IOException">If package to be created should have readwrite/write access and underlying stream is read only</exception>
        [FriendAccessAllowed]
        internal static Package Open(Stream stream, FileMode packageMode, FileAccess packageAccess, bool streaming)
        {
            EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXOpenPackageBegin);
            Package package = null;
            try
            {
                if (stream == null)
                    throw new ArgumentNullException("stream");
 
                ValidateStreamingAccess(packageMode, packageAccess, null /* no FileShare info */, streaming);
 
                //FileMode and FileAccess Enums are validated in the following call
                Stream ensuredStream = ValidateModeAndAccess(stream, packageMode, packageAccess);
 
 
                try
                {
                    // Today the Open(Stream) method is purely used for streams of Zip file format as
                    // that is the default underlying file format mapper implemented.
                    package = new ZipPackage(ensuredStream, packageMode, packageAccess, streaming);
 
                    if (!package._inStreamingCreation) // No read operation in streaming production.
                    {
                        //We need to get all the parts if any exists from the underlying file
                        //so that we have the names in the Normalized form in our in-memory
                        //data structures.
                        //Note: If ever this call is removed, each individual call to GetPartCore,
                        //may result in undefined behavior as the underlying ZipArchive, maintains the
                        //files list as being case-sensitive.
                        if (package.FileOpenAccess == FileAccess.ReadWrite || package.FileOpenAccess == FileAccess.Read)
                            package.GetParts();
                    }
                }
                catch
                {
                    if (package != null)
                    {
                        package.Close();
                    }
 
                    throw;
                }
            }
            finally
            {
                EventTrace.EasyTraceEvent(EventTrace.Keyword.KeywordXPS, EventTrace.Event.WClientDRXOpenPackageEnd);
            }
 
            return package;
        }
 
        /// <summary>
        /// Write a nonterminal piece for /_rels/.rels.
        /// </summary>
        internal void FlushPackageRelationships()
        {
            ThrowIfNotInStreamingCreation("FlushPackageRelationships");
 
            if (_relationships == null)
                return; // nothing to flush
 
            _relationships.Flush();
        }
 
        /// <summary>
        /// Write a terminal piece for /_rels/.rels.
        /// </summary>
        internal void ClosePackageRelationships()
        {
            ThrowIfNotInStreamingCreation("ClosePackageRelationships");
 
            if (_relationships == null)
                return; // no relationship part
 
            _relationships.CloseInStreamingCreationMode();
        }
 
        #endregion Write-time streaming API
 
        #endregion Internal Methods
 
        //------------------------------------------------------
        //
        //  Internal Events
        //
        //------------------------------------------------------
        // None
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
 
        #region Private Methods
 
 
        // This method is only when new part is added to the Package object.
        // This method will throw an exception if the name of the part being added is a
        // prefix of the name of an existing part.
        // Example - Say the following parts exist in the package
        // 1. /abc.xaml
        // 2. /xyz/pqr/a.jpg
        // As an example - Adding any of the following parts will throw an exception -
        // 1. /abc.xaml/new.xaml
        // 2. /xyz/pqr
        private void AddIfNoPrefixCollisionDetected(PackUriHelper.ValidatedPartUri partUri, PackagePart part)
        {
 
            //Add the Normalized Uri to the sorted _partList tentatively to see where it will get inserted
            _partList.Add(partUri, part);
 
            //Get the index of the entry at which this part was added
            int index = _partList.IndexOfKey(partUri);
 
            Invariant.Assert(index >= 0, "Given uri must be present in the dictionary");
 
            string normalizedPartName = partUri.NormalizedPartUriString;
            string precedingPartName = null;
            string followingPartName = null;
 
            if (index > 0)
            {
                precedingPartName = _partList.Keys[index - 1].NormalizedPartUriString;
            }
 
            if (index < _partList.Count - 1)
            {
                followingPartName = _partList.Keys[index + 1].NormalizedPartUriString;
            }
 
            if ((precedingPartName != null
                && normalizedPartName.StartsWith(precedingPartName, StringComparison.Ordinal)
                && normalizedPartName.Length > precedingPartName.Length
                && normalizedPartName[precedingPartName.Length] == PackUriHelper.ForwardSlashChar) ||
                (followingPartName != null
                && followingPartName.StartsWith(normalizedPartName, StringComparison.Ordinal)
                && followingPartName.Length > normalizedPartName.Length
                && followingPartName[normalizedPartName.Length] == PackUriHelper.ForwardSlashChar))
            {
                //Removing the invalid entry from the _partList.
                _partList.Remove(partUri);
 
                throw new InvalidOperationException(SR.Get(SRID.PartNamePrefixExists));
            }
        }
 
        // Test consistency of file opening parameters with the value of 'streaming', and record
        // whether the streaming mode is for consumption or production.
        // 
        private static void ValidateStreamingAccess(
            FileMode packageMode,
            FileAccess packageAccess,
            Nullable<FileShare> packageShare,
            bool streaming)
        {
            if (streaming)
            {
                if (packageMode == FileMode.Create || packageMode == FileMode.CreateNew)
                {
                    if (packageAccess != FileAccess.Write)
                        throw new IOException(SR.Get(SRID.StreamingPackageProductionImpliesWriteOnlyAccess));
                    if (   packageShare != null
                        && packageShare != FileShare.Read && packageShare != FileShare.None)
                        throw new IOException(SR.Get(SRID.StreamingPackageProductionRequiresSingleWriter));
                }
                else
                {
                    // Blanket exception pending design of streaming consumption.
                    throw new NotSupportedException(SR.Get(SRID.StreamingModeNotSupportedForConsumption));
                }
            }
        }
 
 
        //Checking if the mode and access parameters are compatible with the provided stream.
        private static Stream ValidateModeAndAccess(Stream s, FileMode mode, FileAccess access)
        {
            ThrowIfFileModeInvalid(mode);
            ThrowIfFileAccessInvalid(access);
 
            //asking for more permissions than the underlying stream.
            // Stream cannot write, but package to be created should have write access
            if (!s.CanWrite && (access == FileAccess.ReadWrite || access == FileAccess.Write))
                throw new IOException(SR.Get(SRID.IncompatibleModeOrAccess));
 
            //asking for more permissions than the underlying stream.
            // Stream cannot read, but the package to be created should have read access
            if (!s.CanRead && (access == FileAccess.ReadWrite || access == FileAccess.Read))
                throw new IOException(SR.Get(SRID.IncompatibleModeOrAccess));
 
            //asking for less restricted access to the underlying stream
            //stream is ReadWrite but the package is either readonly, or writeonly
            if ((s.CanRead && s.CanWrite) && (access == FileAccess.Read || access == FileAccess.Write))
            {
                return new RestrictedStream(s, access);
            }
            else
                return s;
        }
 
        //Throw if the object is in a disposed state
        private void ThrowIfObjectDisposed()
        {
            if (_disposed == true)
                throw new ObjectDisposedException(null, SR.Get(SRID.ObjectDisposed));
        }
 
        private void EnsureRelationships()
        {
            // once per package
            if (_relationships == null)
            {
                _relationships = new InternalRelationshipCollection(this);
            }
        }
 
        //Delete All Package-level Relationships
        private void ClearRelationships()
        {
            if(_relationships!=null)
                _relationships.Clear();
        }
 
        //Flush the relationships at package level
        private void FlushRelationships()
        {
            // flush relationships
            if (_relationships != null && _openFileAccess != FileAccess.Read)
            {
                _relationships.Flush();
            }
        }
 
        //We do the close or the flush operation per part
        private void DoOperationOnEachPart(PartOperation operation)
        {
            //foreach (PackagePart p in _partList.Values)
            //    p.Close();  - this throws
            // Make local copy of part names to prevent exception during enumeration when
            // a new relationship part gets created (flushing relationships can cause part creation).
            // This code throws in such a case:
            //
            //            foreach (PackagePart p in _partList.Values)
            //                p.Flush();
            //
            if (_partList.Count > 0)
            {
                int partCount = 0;
                PackUriHelper.ValidatedPartUri[] partKeys = new PackUriHelper.ValidatedPartUri[_partList.Keys.Count];
 
                foreach (PackUriHelper.ValidatedPartUri uri in _partList.Keys)
                {
                    partKeys[partCount++] = uri;
                }
 
                // this throws an exception in certain cases (when a part has been deleted)
                //
                //     _partList.Keys.CopyTo(keys, 0);
 
                for (int i = 0; i < _partList.Keys.Count; i++)
                {
                    // Some of these may disappear during above close because the list contains "relationship parts"
                    // and these are removed if their parts' relationship collection is empty
                    // This fails:
                    //                _partList[keys[i]].Flush();
 
                    PackagePart p;
                    if (_partList.TryGetValue(partKeys[i], out p))
                    {
                        if (!operation(p))
                            break;
                    }
                }
            }
        }
 
        //We needed to separate the rels parts from the other parts
        //because if a rels part for a part occured earlier than the part itself in the array,
        //the rels part would be closed and then when close the part and try to persist the relationships
        //for the particular part, it would throw an exception
        private bool DoClose(PackagePart p)
        {
            if (!p.IsClosed)
            {
                if (PackUriHelper.IsRelationshipPartUri(p.Uri) && PackUriHelper.ComparePartUri(p.Uri, PackageRelationship.ContainerRelationshipPartName) != 0)
                {
                    //First we close the source part.
                    //Note - we can safely do this as DoClose is being called on all parts. So ultimately we will end up
                    //closing the source part as well.
                    //This logic only takes care of out of order parts.
                    PackUriHelper.ValidatedPartUri owningPartUri =
                        (PackUriHelper.ValidatedPartUri)PackUriHelper.GetSourcePartUriFromRelationshipPartUri(p.Uri);
                    //If the source part for this rels part exists then we close it.
                    PackagePart sourcePart;
                    if (_partList.TryGetValue(owningPartUri, out sourcePart))
                        sourcePart.Close();
                }
                p.Close();
            }
            return true;
        }
 
        private bool DoFlush(PackagePart p)
        {
            p.Flush();
            return true;
        }
 
        private bool DoWriteRelationshipsXml(PackagePart p)
        {
            if (!p.IsRelationshipPart)
            {
                p.FlushRelationships();
            }
            return true;
        }
 
        private bool DoCloseRelationshipsXml(PackagePart p)
        {
            if (!p.IsRelationshipPart)
            {
                p.CloseRelationships();
            }
            return true;
        }
 
        private PackagePart GetPartHelper(Uri partUri)
        {
            ThrowIfObjectDisposed();
            ThrowIfWriteOnly();
 
            if (partUri == null)
                throw new ArgumentNullException("partUri");
 
            PackUriHelper.ValidatedPartUri validatePartUri = PackUriHelper.ValidatePartUri(partUri);
 
            if (_partList.ContainsKey(validatePartUri))
                return _partList[validatePartUri];
            else
            {
                //Ideally we should decide whether we should query the underlying layer for the part based on the
                //FileShare enum. But since we do not have that information, currently the design is to always
                //ask the underlying layer, this allows for incremental access to the package.
                //Note:
                //Currently this incremental behavior for GetPart is not consistent with the GetParts method
                //which just queries the underlying layer once.
                PackagePart returnedPart = GetPartCore(validatePartUri);
 
                if (returnedPart != null)
                {
                    // Add the part to the _partList if there is no prefix collision
                    AddIfNoPrefixCollisionDetected(validatePartUri, returnedPart);
                }
 
                return returnedPart;
            }
        }
 
        /// <summary>
        /// Retrieve a relationship per ID.
        /// </summary>
        /// <param name="id">The relationship ID.</param>
        /// <returns>The relationship with ID 'id' or null if not found.</returns>
        private PackageRelationship GetRelationshipHelper(string id)
        {
            ThrowIfObjectDisposed();
            ThrowIfWriteOnly();
 
            if (id == null)
                throw new ArgumentNullException("id");
 
            InternalRelationshipCollection.ThrowIfInvalidXsdId(id);
 
            EnsureRelationships();
            return _relationships.GetRelationship(id);
 
        }
 
        /// <summary>
        /// Returns a collection of all the Relationships that are
        /// owned by the package based on the filter string.
        /// </summary>
        /// <returns></returns>
        private PackageRelationshipCollection GetRelationshipsHelper(string filterString)
        {
            ThrowIfObjectDisposed();
            ThrowIfWriteOnly();
            EnsureRelationships();
 
            //Internally null is used to indicate that no filter string was specified and
            //and all the relationships should be returned.
            return new PackageRelationshipCollection(_relationships, filterString);
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
 
        #region Private Members
 
        // Default values for the Package.Open method overloads
        private static readonly FileMode        _defaultFileMode            = FileMode.OpenOrCreate;
        private static readonly FileAccess      _defaultFileAccess          = FileAccess.ReadWrite;
        private static readonly FileShare       _defaultFileShare           = FileShare.None;
 
        private static readonly FileMode        _defaultStreamMode          = FileMode.Open;
        private static readonly FileAccess      _defaultStreamAccess        = FileAccess.Read;
 
        private bool                            _inStreamingCreation;       // false by default
        private FileAccess                      _openFileAccess;
        private bool                            _disposed;
        private SortedList<PackUriHelper.ValidatedPartUri, PackagePart> _partList;
        private PackagePartCollection           _partCollection;
        private InternalRelationshipCollection  _relationships;
        private PartBasedPackageProperties      _packageProperties;
 
 
        #endregion Private Members
 
        //------------------------------------------------------
        //
        //  Private Class
        //
        //------------------------------------------------------
 
        #region Private Class: Restricted Stream
 
        /// <summary>
        /// This implementation of Stream class is a simple wrapper to restrict the
        /// read or write access to the underlying stream, as per user request.
        /// No validation for the stream method calls is done in this wrapper, the calls
        /// are passed onto the underlying stream object, which should do the
        /// validation as required.
        /// </summary>
        private sealed class RestrictedStream : Stream
        {
            #region Constructor
 
            /// <summary>
            /// Constructor
            /// </summary>
            /// <param name="stream"></param>
            /// <param name="access"></param>
            internal RestrictedStream(Stream stream, FileAccess access)
            {
                if (stream == null)
                    throw new ArgumentNullException("stream");
 
                //Verifying if the FileAccess enum is valid
                //This constructor will never be called with FileAccess.ReadWrite
                Debug.Assert(access==FileAccess.Read || access == FileAccess.Write,
                    "The constructor of this private class is expected to be called with FileAccess.Read or FileAccess.Write");
 
                _stream = stream;
 
                if (access == FileAccess.Read)
                {
                    _canRead = true;
                    _canWrite = false;
                }
                else
                if (access == FileAccess.Write)
                {
                    _canRead = false;
                    _canWrite = true;
                }
            }
 
            #endregion Constructor
 
            #region Properties
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            /// <value>Bool, true if the stream can be read from, else false</value>
            public override bool CanRead
            {
                get
                {
                    if(!_disposed)
                        return _canRead;
                    else
                        return false;
                }
            }
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            /// <value>Bool, true if the stream can be seeked, else false</value>
            public override bool CanSeek
            {
                get
                {
                    if(!_disposed)
                        return _stream.CanSeek;
                    else
                        return false;
                }
            }
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            /// <value>Bool, true if the stream can be written to, else false</value>
            public override bool CanWrite
            {
                get
                {
                    if(!_disposed)
                        return _canWrite;
                    else
                        return false;
                }
            }
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            /// <value>Long value indicating the length of the stream</value>
            public override long Length
            {
                get
                {
                    ThrowIfStreamDisposed();
                    return _stream.Length;
                }
            }
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            /// <value>Long value indicating the current position in the stream</value>
            public override long Position
            {
                get
                {
                    ThrowIfStreamDisposed();
                    return _stream.Position;
                }
                set
                {
                    ThrowIfStreamDisposed();
                    _stream.Position = value;
                }
            }
 
            #endregion Properties
 
            #region Methods
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            /// <param name="offset">only zero is supported</param>
            /// <param name="origin">only SeekOrigin.Begin is supported</param>
            /// <returns>zero</returns>
            public override long Seek(long offset, SeekOrigin origin)
            {
                ThrowIfStreamDisposed();
                return _stream.Seek(offset, origin);
            }
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            /// <param name="newLength"></param>
            public override void SetLength(long newLength)
            {
                ThrowIfStreamDisposed();
                if (_canWrite)
                    _stream.SetLength(newLength);
                else
                    throw new NotSupportedException(SR.Get(SRID.ReadOnlyStream));
            }
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            /// <param name="buffer"></param>
            /// <param name="offset"></param>
            /// <param name="count"></param>
            /// <returns></returns>
            /// <remarks>
            /// The standard Stream.Read semantics, and in particular the restoration of the current
            /// position in case of an exception, is implemented by the underlying stream.
            /// </remarks>
            public override int Read(byte[] buffer, int offset, int count)
            {
                ThrowIfStreamDisposed();
                if (_canRead)
                    return _stream.Read(buffer, offset, count);
                else
                    throw new NotSupportedException(SR.Get(SRID.WriteOnlyStream));
            }
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            /// <param name="buf"></param>
            /// <param name="offset"></param>
            /// <param name="count"></param>
            public override void Write(byte[] buf, int offset, int count)
            {
                ThrowIfStreamDisposed();
                if (_canWrite)
                    _stream.Write(buf, offset, count);
                else
                    throw new NotSupportedException(SR.Get(SRID.ReadOnlyStream));
            }
 
            /// <summary>
            /// Member of the abstract Stream class
            /// </summary>
            public override void Flush()
            {
                ThrowIfStreamDisposed();
                if (_canWrite)
                    _stream.Flush();
            }
            #endregion Methods
 
            //------------------------------------------------------
            //
            //  Protected Methods
            //
            //------------------------------------------------------
            /// <summary>
            /// Dispose(bool)
            /// </summary>
            /// <param name="disposing"></param>
            protected override void Dispose(bool disposing)
            {
                try
                {
                    if (disposing)
                    {
                        if (!_disposed)
                        {
                            _stream.Close();
                        }
                    }
                }
                finally
                {
                    _disposed = true;
                    base.Dispose(disposing);
                }
            }
 
 
            #region Private Methods
 
            private void ThrowIfStreamDisposed()
            {
                if (_disposed)
                    throw new ObjectDisposedException(null, SR.Get(SRID.StreamObjectDisposed));
            }
 
            #endregion Private Methods
 
            #region Private Variables
 
            private Stream _stream;
            private bool _canRead;
            private bool _canWrite;
            private bool _disposed;
 
            #endregion Private Variables
 
        }
 
        #endregion Private Class: Restricted Stream
    }
}