File: System\IO\Packaging\PackagePart.cs
Web Access
Project: src\src\libraries\System.IO.Packaging\src\System.IO.Packaging.csproj (System.IO.Packaging)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
 
namespace System.IO.Packaging
{
    /// <summary>
    /// This class represents the a PackagePart within a container.
    /// This is a part of the Packaging Layer APIs
    /// </summary>
    public abstract class PackagePart
    {
        #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
        ///
        /// You should use this constructor in the rare case when you do not have
        /// the content type information related to this part and would prefer to
        /// obtain it later as required.
        ///
        /// These parts have the CompressionOption as NotCompressed by default.
        ///
        /// NOTE : If you are using this constructor from your subclass or passing a null
        /// for the content type parameter, be sure to implement the GetContentTypeCore
        /// method, as that will be called to get the content type value. This is provided
        /// to enable lazy initialization of the ContentType property.
        ///
        /// </summary>
        /// <param name="package">Package in which this part is being created</param>
        /// <param name="partUri">uri of the part</param>
        /// <exception cref="ArgumentNullException">If parameter "package" is null</exception>
        /// <exception cref="ArgumentNullException">If parameter "partUri" is null</exception>
        protected PackagePart(Package package, Uri partUri)
            : this(package, partUri, null, CompressionOption.NotCompressed)
        {
        }
 
        /// <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
        ///
        /// These parts have the CompressionOption as NotCompressed by default.
        ///
        /// NOTE : If you are using this constructor from your subclass or passing a null
        /// for the content type parameter, be sure to implement the GetContentTypeCore
        /// method, as that will be called to get the content type value. This is provided
        /// to enable lazy initialization of the ContentType property.
        ///
        /// </summary>
        /// <param name="package">Package in which this part is being created</param>
        /// <param name="partUri">uri of the part</param>
        /// <param name="contentType">Content Type of the part, can be null if the value
        /// is unknown at the time of construction. However the value has to be made
        /// available anytime the ContentType property is called. A null value only indicates
        /// that the value will be provided later. Every PackagePart must have a valid
        /// Content Type</param>
        /// <exception cref="ArgumentNullException">If parameter "package" is null</exception>
        /// <exception cref="ArgumentNullException">If parameter "partUri" is null</exception>
        /// <exception cref="ArgumentException">If parameter "partUri" does not conform to the valid partUri syntax</exception>
        protected PackagePart(Package package, Uri partUri, string? contentType)
            : this(package, partUri, contentType, CompressionOption.NotCompressed)
        {
        }
 
 
        /// <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
        ///
        /// NOTE : If you are using this constructor from your subclass or passing a null
        /// for the content type parameter, be sure to implement the GetContentTypeCore
        /// method, as that will be called to get the content type value. This is provided
        /// to enable lazy initialization of the ContentType property.
        ///
        /// </summary>
        /// <param name="package">Package in which this part is being created</param>
        /// <param name="partUri">uri of the part</param>
        /// <param name="contentType">Content Type of the part, can be null if the value
        /// is unknown at the time of construction. However the value has to be made
        /// available anytime the ContentType property is called. A null value only indicates
        /// that the value will be provided later. Every PackagePart must have a valid
        /// Content Type</param>
        /// <param name="compressionOption">compression option for this part</param>
        /// <exception cref="ArgumentNullException">If parameter "package" is null</exception>
        /// <exception cref="ArgumentNullException">If parameter "partUri" is null</exception>
        /// <exception cref="ArgumentOutOfRangeException">If CompressionOption enumeration [compressionOption] does not have one of the valid values</exception>
        /// <exception cref="ArgumentException">If parameter "partUri" does not conform to the valid partUri syntax</exception>
        protected PackagePart(Package package,
                                Uri partUri,
                                string? contentType,
                                CompressionOption compressionOption)
        {
            if (package is null)
            {
                throw new ArgumentNullException(nameof(package));
            }
            if (partUri is null)
            {
                throw new ArgumentNullException(nameof(partUri));
            }
 
            Package.ThrowIfCompressionOptionInvalid(compressionOption);
 
            _uri = PackUriHelper.ValidatePartUri(partUri);
            _container = package;
 
            if (contentType == null)
            {
                _contentType = null;
            }
            else
            {
                _contentType = new ContentType(contentType);
            }
 
            _requestedStreams = null;
            _compressionOption = compressionOption;
            _isRelationshipPart = PackUriHelper.IsRelationshipPartUri(partUri);
        }
 
        #endregion Protected Constructor
 
        #region Public Properties
 
        /// <summary>
        /// The Uri for this PackagePart. It is always relative to the Package Root
        /// The PackagePart properties can not be accessed if the parent container is closed.
        /// </summary>
        /// <value></value>
        /// <exception cref="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or disposed</exception>
        public Uri Uri
        {
            get
            {
                CheckInvalidState();
                return _uri;
            }
        }
 
        /// <summary>
        /// The Content type of the stream that is represented by this part.
        /// The PackagePart properties can not be accessed if the parent container is closed.
        /// The content type value can be provided by the underlying physical format
        /// implementation at the time of creation of the Part object ( constructor ) or
        /// We can initialize it in a lazy manner when the ContentType property is called
        /// called for the first time by calling the GetContentTypeCore method.
        /// Note: This method GetContentTypeCore() is only for lazy initialization of the Content
        /// type value and will only be called once. There is no way to change the content type of
        /// the part once it has been assigned.
        /// </summary>
        /// <value>Content Type of the Part [can never return null] </value>
        /// <exception cref="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or disposed</exception>
        /// <exception cref="InvalidOperationException">If the subclass fails to provide a non-null content type value.</exception>
        public string ContentType
        {
            get
            {
                CheckInvalidState();
                if (_contentType == null)
                {
                    //Lazy initialization for the content type
                    string contentType = GetContentTypeCore();
 
                    if (contentType == null)
                    {
                        // We have seen this bug in the past and have said that this should be
                        // treated as exception. If we get a null content type, it's an error.
                        // We want to throw this exception so that anyone sub-classing this class
                        // should not be setting the content type to null. Its like any other
                        // parameter validation. This is the only place we can validate it. We
                        // throw an ArgumentNullException, when the content type is set to null
                        // in the constructor.
                        //
                        // We cannot get rid of this exception. At most, we can change it to
                        // Debug.Assert. But then client code will see an Assert if they make
                        // a mistake and that is also not desirable.
                        //
                        // PackagePart is a public API.
                        throw new InvalidOperationException(SR.NullContentTypeProvided);
                    }
                    _contentType = new ContentType(contentType);
                }
                return _contentType.ToString();
            }
        }
 
 
        /// <summary>
        /// The parent container for this PackagePart
        /// The PackagePart properties can not be accessed if the parent container is closed.
        /// </summary>
        /// <value></value>
        /// <exception cref="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or disposed</exception>
        public Package Package
        {
            get
            {
                CheckInvalidState();
                return _container;
            }
        }
 
        /// <summary>
        /// CompressionOption class that was provided as a parameter during the original CreatePart call.
        /// The PackagePart properties can not be accessed if the parent container is closed.
        /// </summary>
        /// <exception cref="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or disposed</exception>
        public CompressionOption CompressionOption
        {
            get
            {
                CheckInvalidState();
                return _compressionOption;
            }
        }
 
        #endregion Public Properties
 
        #region Public Methods
 
        #region Content Type Method
 
        /// <summary>
        /// Custom Implementation for the GetContentType Method
        /// This method should only be implemented by those physical format implementors where
        /// the value for the content type cannot be provided at the time of construction of
        /// Part object and if calculating the content type value is a non-trivial or costly
        /// operation. The return value has to be a valid ContentType. This method will be used in
        /// real corner cases. The most common usage should be to provide the content type in the
        /// constructor.
        /// This method is only for lazy initialization of the Content type value and will only
        /// be called once. There is no way to change the content type of the part once it is
        /// assigned.
        /// </summary>
        /// <returns>Content type for the Part</returns>
        /// <exception cref="NotSupportedException">By default, this method throws a NotSupportedException. If a subclass wants to
        /// initialize the content type for a PackagePart in a lazy manner they must override this method.</exception>
        protected virtual string GetContentTypeCore()
        {
            throw new NotSupportedException(SR.GetContentTypeCoreNotImplemented);
        }
 
 
        #endregion Content Type Method
 
        #region Stream Methods
 
        /// <summary>
        /// Returns the underlying stream that is represented by this part
        /// with the default FileMode and FileAccess
        /// Note: If you are requesting a stream for a relationship part and
        /// at the same time using relationship APIs to manipulate relationships,
        /// the final persisted data will depend on which data gets flushed last.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or disposed</exception>
        /// <exception cref="IOException">If the subclass fails to provide a non-null stream object</exception>
        public Stream GetStream()
        {
            CheckInvalidState();
            return GetStream(FileMode.OpenOrCreate, _container.FileOpenAccess);
        }
 
        /// <summary>
        /// Returns the underlying stream in the specified mode and the
        /// default FileAccess
        /// Note: If you are requesting a stream for a relationship part for editing
        /// and at the same time using relationship APIs to manipulate relationships,
        /// the final persisted data will depend on which data gets flushed last.
        /// </summary>
        /// <param name="mode"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or disposed</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileMode enumeration [mode] does not have one of the valid values</exception>
        /// <exception cref="IOException">If FileAccess.Read is provided and FileMode values are any of the following -
        /// FileMode.Create, FileMode.CreateNew, FileMode.Truncate, FileMode.Append</exception>
        /// <exception cref="IOException">If the mode and access for the Package and the Stream are not compatible</exception>
        /// <exception cref="IOException">If the subclass fails to provide a non-null stream object</exception>
        public Stream GetStream(FileMode mode)
        {
            CheckInvalidState();
            return GetStream(mode, _container.FileOpenAccess);
        }
 
        /// <summary>
        /// Returns the underlying stream that is represented by this part
        /// in the specified mode with the access.
        /// Note: If you are requesting a stream for a relationship part and
        /// at the same time using relationship APIs to manipulate relationships,
        /// the final persisted data will depend on which data gets flushed last.
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="access"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or disposed</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileMode enumeration [mode] does not have one of the valid values</exception>
        /// <exception cref="ArgumentOutOfRangeException">If FileAccess enumeration [access] does not have one of the valid values</exception>
        /// <exception cref="IOException">If FileAccess.Read is provided and FileMode values are any of the following -
        /// FileMode.Create, FileMode.CreateNew, FileMode.Truncate, FileMode.Append</exception>
        /// <exception cref="IOException">If the mode and access for the Package and the Stream are not compatible</exception>
        /// <exception cref="IOException">If the subclass fails to provide a non-null stream object</exception>
        public Stream GetStream(FileMode mode, FileAccess access)
        {
            CheckInvalidState();
            ThrowIfOpenAccessModesAreIncompatible(mode, access);
 
            if (mode == FileMode.CreateNew)
                throw new ArgumentException(SR.CreateNewNotSupported);
            if (mode == FileMode.Truncate)
                throw new ArgumentException(SR.TruncateNotSupported);
 
            Stream? s = GetStreamCore(mode, access);
 
            if (s == null)
                throw new IOException(SR.NullStreamReturned);
 
            //Detect if any stream implementations are returning all three
            //properties - CanSeek, CanWrite and CanRead as false. Such a
            //stream should be pretty much useless. And as per current programming
            //practice, these properties are all false, when the stream has been
            //disposed.
            Debug.Assert(!IsStreamClosed(s));
 
            //Lazy init
            _requestedStreams ??= new List<Stream>(); //Default capacity is 4
 
            //Delete all the closed streams from the _requestedStreams list.
            //Each time a new stream is handed out, we go through the list
            //to clean up streams that were handed out and have been closed.
            //Thus those stream can be garbage collected and we will avoid
            //keeping around stream objects that have been disposed
            CleanUpRequestedStreamsList();
 
            _requestedStreams.Add(s);
 
            return s;
        }
 
        /// <summary>
        /// Custom Implementation for the GetSream Method
        /// </summary>
        /// <param name="mode"></param>
        /// <param name="access"></param>
        /// <returns></returns>
        protected abstract Stream? GetStreamCore(FileMode mode, FileAccess access);
 
        #endregion Stream Methods
 
        #region PackageRelationship Methods
        /// <summary>
        /// Adds a relationship to this PackagePart with the Target PackagePart specified as the Uri
        /// Initial and trailing spaces in the name of the PackageRelationship are trimmed.
        /// </summary>
        /// <param name="targetUri"></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="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or 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>
        /// Adds a relationship to this PackagePart with the Target PackagePart specified as the Uri
        /// Initial and trailing spaces in the name of the PackageRelationship are trimmed.
        /// </summary>
        /// <param name="targetUri"></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="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or 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)
        {
            CheckInvalidState();
            _container.ThrowIfReadOnly();
            EnsureRelationships();
            //All parameter validation is done in the following method
            return _relationships.Add(targetUri, targetMode, relationshipType, id);
        }
 
        /// <summary>
        /// Deletes a relationship from the PackagePart. 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="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or 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)
        {
            CheckInvalidState();
            _container.ThrowIfReadOnly();
 
            if (id == null)
                throw new ArgumentNullException(nameof(id));
 
            InternalRelationshipCollection.ThrowIfInvalidXsdId(id);
 
            EnsureRelationships();
            _relationships.Delete(id);
        }
 
        /// <summary>
        /// Returns a collection of all the Relationships that are
        /// owned by this PackagePart
        /// </summary>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or 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 this PackagePart
        /// The relationshipType string is compared with the type of the relationships
        /// in a case sensitive and culture ignorant manner.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or 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
            CheckInvalidState();
            _container.ThrowIfWriteOnly();
 
            if (relationshipType == null)
                throw new ArgumentNullException(nameof(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="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or 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.PackagePartRelationshipDoesNotExist);
            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="InvalidOperationException">If this part has been deleted</exception>
        /// <exception cref="InvalidOperationException">If the parent package has been closed or 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 Internal Properties
 
        internal bool IsRelationshipPart
        {
            get
            {
                return _isRelationshipPart;
            }
        }
 
        //This property can be set to indicate if the part has been deleted
        internal bool IsDeleted
        {
            get
            {
                return _deleted;
            }
            set
            {
                _deleted = value;
            }
        }
 
        //This property can be set to indicate if the part has been deleted
        internal bool IsClosed
        {
            get
            {
                return _disposed;
            }
        }
 
        /// <summary>
        /// This property returns the content type of the part
        /// as a validated strongly typed ContentType object
        /// </summary>
        internal ContentType ValidatedContentType
        {
            get
            {
                return _contentType!;
            }
        }
 
        #endregion Internal Properties
 
        #region Internal Methods
 
        //Delete all the relationships for this part
        internal void ClearRelationships()
        {
            _relationships?.Clear();
        }
 
        //Flush all the streams that are currently opened for this part and the relationships for this part
        //Note: This method is never be called on a deleted part
        internal void Flush()
        {
            Debug.Assert(_deleted != true, "PackagePart.Flush should never be called on a deleted part");
 
            if (_requestedStreams != null)
            {
                foreach (Stream s in _requestedStreams)
                {
                    // Streams in this list are never set to null, so we do not need to check for
                    // stream being null; However it could be closed by some external code. In that case
                    // this property (CanWrite) will still be accessible and we can check to see
                    // whether we can call flush or no.
                    if (s.CanWrite)
                        s.Flush();
                }
            }
 
            // Relationships for this part should have been flushed earlier in the Package.Flush method.
        }
 
        //Close all the streams that are open for this part.
        internal void Close()
        {
            if (!_disposed)
            {
                try
                {
                    if (_requestedStreams != null)
                    {
                        //Adding this extra check here to optimize delete operation
                        //Every time we delete a part we close it before deleting to
                        //ensure that its deleted in a valid state. However, we do not
                        //need to persist any changes if the part is being deleted.
                        if (!_deleted)
                        {
                            foreach (Stream s in _requestedStreams)
                            {
                                s.Dispose();
                            }
                        }
                        _requestedStreams.Clear();
                    }
 
                    // Relationships for this part should have been flushed/closed earlier in the Package.Close method.
                }
                finally
                {
                    _requestedStreams = null;
 
                    //InternalRelationshipCollection is not required any more
                    _relationships = null;
 
                    //Once the container is closed there is no way to get to the stream or any other part
                    //in the container.
                    _container = null!;
 
                    //We do not need to explicitly call GC.SuppressFinalize(this)
 
                    _disposed = true;
                }
            }
        }
 
        /// <summary>
        /// write the relationships part
        /// </summary>
        /// <remarks>
        /// </remarks>
        internal void FlushRelationships()
        {
            Debug.Assert(_deleted != true, "PackagePart.FlushRelationsips should never be called on a deleted part");
 
            // flush relationships
            if (_relationships != null && _container.FileOpenAccess != FileAccess.Read)
            {
                _relationships.Flush();
            }
        }
 
 
        internal void CloseRelationships()
        {
            if (!_deleted)
            {
                //Flush the relationships for this part.
                FlushRelationships();
            }
        }
 
        #endregion Internal Methods
 
        #region Private Methods
 
        // lazy init
        [MemberNotNull(nameof(_relationships))]
        private void EnsureRelationships()
        {
            if (_relationships == null)
            {
                // check here
                ThrowIfRelationship();
 
                // obtain the relationships from the PackageRelationship part (if available)
                _relationships = new InternalRelationshipCollection(this);
            }
        }
 
        //Make sure that the access modes for the container and the part are compatible
        private void ThrowIfOpenAccessModesAreIncompatible(FileMode mode, FileAccess access)
        {
            Package.ThrowIfFileModeInvalid(mode);
            Package.ThrowIfFileAccessInvalid(access);
 
            //Creating a part using a readonly stream.
            if (access == FileAccess.Read &&
                (mode == FileMode.Create || mode == FileMode.CreateNew || mode == FileMode.Truncate || mode == FileMode.Append))
                throw new IOException(SR.UnsupportedCombinationOfModeAccess);
 
            //Incompatible access modes between container and part stream.
            if ((_container.FileOpenAccess == FileAccess.Read && access != FileAccess.Read) ||
                (_container.FileOpenAccess == FileAccess.Write && access != FileAccess.Write))
                throw new IOException(SR.ContainerAndPartModeIncompatible);
        }
 
        //Check if the part is in an invalid state
        private void CheckInvalidState()
        {
            ThrowIfPackagePartDeleted();
            ThrowIfParentContainerClosed();
        }
 
        //If the parent container is closed then the operations on this part like getting stream make no sense
        private void ThrowIfParentContainerClosed()
        {
            if (_container == null)
                throw new InvalidOperationException(SR.ParentContainerClosed);
        }
 
        //If the part has been deleted then we throw
        private void ThrowIfPackagePartDeleted()
        {
            if (_deleted)
                throw new InvalidOperationException(SR.PackagePartDeleted);
        }
 
        // some operations are invalid if we are a relationship part
        private void ThrowIfRelationship()
        {
            if (IsRelationshipPart)
                throw new InvalidOperationException(SR.RelationshipPartsCannotHaveRelationships);
        }
 
        /// <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)
        {
            CheckInvalidState();
            _container.ThrowIfWriteOnly();
 
            if (id == null)
                throw new ArgumentNullException(nameof(id));
 
            InternalRelationshipCollection.ThrowIfInvalidXsdId(id);
 
            EnsureRelationships();
            return _relationships.GetRelationship(id);
        }
 
        /// <summary>
        /// Returns a collection of all the Relationships that are
        /// owned by this PackagePart, based on the filter string
        /// </summary>
        /// <returns></returns>
        private PackageRelationshipCollection GetRelationshipsHelper(string? filterString)
        {
            CheckInvalidState();
            _container.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);
        }
 
        //Deletes all the streams that have been closed from the _requestedStreams list.
        private void CleanUpRequestedStreamsList()
        {
            if (_requestedStreams != null)
            {
                for (int i = _requestedStreams.Count - 1; i >= 0; i--)
                {
                    if (IsStreamClosed(_requestedStreams[i]))
                        _requestedStreams.RemoveAt(i);
                }
            }
        }
 
        //Detect if the stream has been closed.
        //When a stream is closed the three flags - CanSeek, CanRead and CanWrite
        //return false. These properties do not throw ObjectDisposedException.
        //So we rely on the values of these properties to determine if a stream
        //has been closed.
        private static bool IsStreamClosed(Stream s)
        {
            return !s.CanRead && !s.CanSeek && !s.CanWrite;
        }
 
        #endregion Private Methods
 
        #region Private Members
 
        private readonly PackUriHelper.ValidatedPartUri _uri;
        private Package _container;
        private ContentType? _contentType;
        private List<Stream>? _requestedStreams;
        private InternalRelationshipCollection? _relationships;
        private readonly CompressionOption _compressionOption = CompressionOption.NotCompressed;
        private bool _disposed;
        private bool _deleted;
        private readonly bool _isRelationshipPart;
 
        #endregion Private Members
    }
}