Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Packaging: Port fix for GetParts from .NETFramework #108681

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,8 @@ public void DeletePart(Uri partUri)
/// <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>
/// <exception cref="FileFormatException">The package has a bad format.</exception>
/// <exception cref="InvalidOperationException">The part name prefix exists.</exception>
public PackagePartCollection GetParts()
{
ThrowIfObjectDisposed();
Expand All @@ -401,32 +403,63 @@ public PackagePartCollection GetParts()

PackUriHelper.ValidatedPartUri partUri;

var uriComparer = Comparer<PackUriHelper.ValidatedPartUri>.Default;
ericstj marked this conversation as resolved.
Show resolved Hide resolved

//Sorting the parts array which takes O(n log n) time.
Array.Sort(parts, Comparer<PackagePart>.Create((partA, partB) => uriComparer.Compare((PackUriHelper.ValidatedPartUri)partA.Uri, (PackUriHelper.ValidatedPartUri)partB.Uri)));

//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);
Dictionary<string, KeyValuePair<PackUriHelper.ValidatedPartUri, PackagePart>> partDictionary = new(parts.Length);
List<string> partIndex = new(parts.Length);

for (int i = 0; i < parts.Length; i++)
{
partUri = (PackUriHelper.ValidatedPartUri)parts[i].Uri;

if (seenPartUris.ContainsKey(partUri))
string normalizedPartName = partUri.NormalizedPartUriString;

if (partDictionary.ContainsKey(normalizedPartName))
{
throw new FileFormatException(SR.BadPackageFormat);
}
else
{
// Add the part to the list of URIs that we have already seen
seenPartUris.Add(partUri, parts[i]);
//since we will arrive to this line of code after the parts are already sorted
string? precedingPartName = null;

if (partIndex.Count > 0)
{
precedingPartName = (partIndex[partIndex.Count - 1]);
}

// Add the part to the dictionary
partDictionary.Add(normalizedPartName, new KeyValuePair<PackUriHelper.ValidatedPartUri, PackagePart>(partUri, parts[i]));

if (!_partList.ContainsKey(partUri))
if (precedingPartName != null
&& normalizedPartName.StartsWith(precedingPartName, StringComparison.Ordinal)
&& normalizedPartName.Length > precedingPartName.Length
&& normalizedPartName[precedingPartName.Length] == PackUriHelper.ForwardSlashChar)
{
// Add the part to the _partList if there is no prefix collision
AddIfNoPrefixCollisionDetected(partUri, parts[i]);
//Removing the invalid entry from the _partList.
partDictionary.Remove(normalizedPartName);

throw new InvalidOperationException(SR.PartNamePrefixExists);
}

//adding entry to partIndex to keep track of last element being added.
//since parts are already sorted, last element in partIndex list will point to preceeding element to the current.
partIndex.Add(partUri.NormalizedPartUriString);
}
}

//copying parts from partdictionary to partlist
CopyPartDictionaryToPartList(partDictionary, partIndex);

_partCollection = new PackagePartCollection(_partList);
}
return _partCollection;
Expand Down Expand Up @@ -1173,6 +1206,23 @@ private PackageRelationshipCollection GetRelationshipsHelper(string? filterStrin
return new PackageRelationshipCollection(_relationships, filterString);
}

private void CopyPartDictionaryToPartList(Dictionary<string, KeyValuePair<PackUriHelper.ValidatedPartUri, PackagePart>> partDictionary, List<string> partIndex)
{
//Clearing _partList before copying in new data. Reassigning the variable, assuming the previous object to be garbage collected.
//ideally addition to sortedlist takes O(n) but since we have sorted data and also we defined the size, it will take O(log n) per addition
//total time complexity for this function will be O(n log n)
_partList = new SortedList<PackUriHelper.ValidatedPartUri, PackagePart>(partDictionary.Count);

//Since partIndex is created from a sorted parts array we are sure that partIndex
//will have items in same order
foreach (var id in partIndex)
{
//retrieving object from partDictionary hashtable
var keyValue = partDictionary[id];
_partList.Add(keyValue.Key, keyValue.Value);
}
}

#endregion Private Methods

#region Private Members
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -739,7 +739,7 @@ bool IEqualityComparer<string>.Equals(string? extensionA, string? extensionB)
//with the rules for comparing/normalizing partnames.
//Refer to PackUriHelper.ValidatedPartUri.GetNormalizedPartUri method.
//Currently normalization just involves upper-casing ASCII and hence the simplification.
return extensionA.Equals(extensionB, StringComparison.InvariantCultureIgnoreCase);
return extensionA.Equals(extensionB, StringComparison.OrdinalIgnoreCase);
}

int IEqualityComparer<string>.GetHashCode(string extension)
Expand Down
Loading