// dnlib: See LICENSE.txt for more info using System; using System.Collections.Generic; namespace dnlib.DotNet { /// /// Flags used by /// [Flags] public enum AssemblyNameComparerFlags { /// /// Compare assembly simple name /// Name = 1, /// /// Compare assembly version /// Version = 2, /// /// Compare assembly public key token /// PublicKeyToken = 4, /// /// Compare assembly culture /// Culture = 8, /// /// Compare content type /// ContentType = 0x10, /// /// Compare assembly simple name, version, public key token, locale and content type /// All = Name | Version | PublicKeyToken | Culture | ContentType, } /// /// Compares two assembly names /// public readonly struct AssemblyNameComparer : IEqualityComparer { /// /// Compares the name, version, public key token, culture and content type /// public static readonly AssemblyNameComparer CompareAll = new AssemblyNameComparer(AssemblyNameComparerFlags.All); /// /// Compares only the name and the public key token /// public static readonly AssemblyNameComparer NameAndPublicKeyTokenOnly = new AssemblyNameComparer(AssemblyNameComparerFlags.Name | AssemblyNameComparerFlags.PublicKeyToken); /// /// Compares only the name /// public static readonly AssemblyNameComparer NameOnly = new AssemblyNameComparer(AssemblyNameComparerFlags.Name); readonly AssemblyNameComparerFlags flags; /// /// Gets the bit /// public bool CompareName => (flags & AssemblyNameComparerFlags.Name) != 0; /// /// Gets the bit /// public bool CompareVersion => (flags & AssemblyNameComparerFlags.Version) != 0; /// /// Gets the bit /// public bool ComparePublicKeyToken => (flags & AssemblyNameComparerFlags.PublicKeyToken) != 0; /// /// Gets the bit /// public bool CompareCulture => (flags & AssemblyNameComparerFlags.Culture) != 0; /// /// Gets the bit /// public bool CompareContentType => (flags & AssemblyNameComparerFlags.ContentType) != 0; /// /// Constructor /// /// Comparison flags public AssemblyNameComparer(AssemblyNameComparerFlags flags) => this.flags = flags; /// /// Compares two assembly names /// /// First /// Second /// < 0 if a < b, 0 if a == b, > 0 if a > b public int CompareTo(IAssembly a, IAssembly b) { if (a == b) return 0; if (a is null) return -1; if (b is null) return 1; int v; if (CompareName && (v = UTF8String.CaseInsensitiveCompareTo(a.Name, b.Name)) != 0) return v; if (CompareVersion && (v = Utils.CompareTo(a.Version, b.Version)) != 0) return v; if (ComparePublicKeyToken && (v = PublicKeyBase.TokenCompareTo(a.PublicKeyOrToken, b.PublicKeyOrToken)) != 0) return v; if (CompareCulture && (v = Utils.LocaleCompareTo(a.Culture, b.Culture)) != 0) return v; if (CompareContentType && (v = a.ContentType.CompareTo(b.ContentType)) != 0) return v; return 0; } /// /// Compares two assembly names /// /// First /// Second /// true if equal, false otherwise public bool Equals(IAssembly a, IAssembly b) => CompareTo(a, b) == 0; /// /// Figures out which of two assembly names is closer to another assembly name /// /// Requested assembly name /// First /// Second /// -1 if both are equally close, 0 if is closest, 1 if /// is closest public int CompareClosest(IAssembly requested, IAssembly a, IAssembly b) { if (a == b) return 0; if (a is null) return !CompareName ? 1 : UTF8String.CaseInsensitiveEquals(requested.Name, b.Name) ? 1 : 0; if (b is null) return !CompareName ? 0 : UTF8String.CaseInsensitiveEquals(requested.Name, a.Name) ? 0 : 1; // Compare the most important parts first: // 1. Assembly simple name // 2. Public key token // 3. Version // 4. Locale // 5. Content type if (CompareName) { // If the name only matches one of a or b, return that one. bool na = UTF8String.CaseInsensitiveEquals(requested.Name, a.Name); bool nb = UTF8String.CaseInsensitiveEquals(requested.Name, b.Name); if (na && !nb) return 0; if (!na && nb) return 1; if (!na && !nb) return -1; } if (ComparePublicKeyToken) { bool pa, pb; if (PublicKeyBase.IsNullOrEmpty2(requested.PublicKeyOrToken)) { // If one of them has a pkt but the other one hasn't, return the one with // no pkt. pa = PublicKeyBase.IsNullOrEmpty2(a.PublicKeyOrToken); pb = PublicKeyBase.IsNullOrEmpty2(b.PublicKeyOrToken); } else { // If one of them has the correct pkt, but the other one has an incorrect // pkt, return the one with the correct pkt. pa = PublicKeyBase.TokenEquals(requested.PublicKeyOrToken, a.PublicKeyOrToken); pb = PublicKeyBase.TokenEquals(requested.PublicKeyOrToken, b.PublicKeyOrToken); } if (pa && !pb) return 0; if (!pa && pb) return 1; } if (CompareVersion && !Utils.Equals(a.Version, b.Version)) { var rv = Utils.CreateVersionWithNoUndefinedValues(requested.Version); if (rv == new Version(0, 0, 0, 0)) rv = new Version(ushort.MaxValue, ushort.MaxValue, ushort.MaxValue, ushort.MaxValue); int va = Utils.CompareTo(a.Version, rv); int vb = Utils.CompareTo(b.Version, rv); if (va == 0) return 0; // vb != 0 so return 0 if (vb == 0) return 1; // va != 0 so return 1 if (va > 0 && vb < 0) return 0; if (va < 0 && vb > 0) return 1; // Now either both a and b's version > req version or both are < req version if (va > 0) { // a.Version and b.Version > req.Version. Pick the one that is closest. return Utils.CompareTo(a.Version, b.Version) < 0 ? 0 : 1; } else { // a.Version and b.Version < req.Version. Pick the one that is closest. return Utils.CompareTo(a.Version, b.Version) > 0 ? 0 : 1; } } if (CompareCulture) { bool la = Utils.LocaleEquals(requested.Culture, a.Culture); bool lb = Utils.LocaleEquals(requested.Culture, b.Culture); if (la && !lb) return 0; if (!la && lb) return 1; } if (CompareContentType) { bool ca = requested.ContentType == a.ContentType; bool cb = requested.ContentType == b.ContentType; if (ca && !cb) return 0; if (!ca && cb) return 1; } return -1; } /// /// Gets the hash code of an assembly name /// /// Assembly name /// The hash code public int GetHashCode(IAssembly a) { if (a is null) return 0; int hash = 0; if (CompareName) hash += UTF8String.GetHashCode(a.Name); if (CompareVersion) hash += Utils.CreateVersionWithNoUndefinedValues(a.Version).GetHashCode(); if (ComparePublicKeyToken) hash += PublicKeyBase.GetHashCodeToken(a.PublicKeyOrToken); if (CompareCulture) hash += Utils.GetHashCodeLocale(a.Culture); if (CompareContentType) hash += (int)a.ContentType; return hash; } } }