summaryrefslogtreecommitdiff
path: root/src/SMAPI/Framework
diff options
context:
space:
mode:
authorJesse Plamondon-Willard <github@jplamondonw.com>2018-07-08 13:58:37 -0400
committerJesse Plamondon-Willard <github@jplamondonw.com>2018-07-08 13:58:37 -0400
commit0079110870e4944e734be507ede91e7b0b655df6 (patch)
treefd2e333c9c0e49b1138fadc75afed2510dc102fd /src/SMAPI/Framework
parentbefeafd31d7a3351cb138c210b26f126716d05f0 (diff)
downloadSMAPI-0079110870e4944e734be507ede91e7b0b655df6.tar.gz
SMAPI-0079110870e4944e734be507ede91e7b0b655df6.tar.bz2
SMAPI-0079110870e4944e734be507ede91e7b0b655df6.zip
encapsulate type reference comparison
Diffstat (limited to 'src/SMAPI/Framework')
-rw-r--r--src/SMAPI/Framework/ModLoading/RewriteHelper.cs188
-rw-r--r--src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs209
2 files changed, 220 insertions, 177 deletions
diff --git a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs
index f8684cde..2f79809c 100644
--- a/src/SMAPI/Framework/ModLoading/RewriteHelper.cs
+++ b/src/SMAPI/Framework/ModLoading/RewriteHelper.cs
@@ -1,8 +1,6 @@
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Reflection;
-using System.Text.RegularExpressions;
using Mono.Cecil;
using Mono.Cecil.Cil;
@@ -14,8 +12,8 @@ namespace StardewModdingAPI.Framework.ModLoading
/*********
** Properties
*********/
- /// <summary>A pattern matching type name substrings to strip for display.</summary>
- private static readonly Regex StripTypeNamePattern = new Regex(@"`\d+(?=<)", RegexOptions.Compiled);
+ /// <summary>The comparer which heuristically compares type definitions.</summary>
+ private static readonly TypeReferenceComparer TypeDefinitionComparer = new TypeReferenceComparer();
/*********
@@ -68,6 +66,15 @@ namespace StardewModdingAPI.Framework.ModLoading
return true;
}
+ /// <summary>Determine whether two type IDs look like the same type, accounting for placeholder values such as !0.</summary>
+ /// <param name="typeA">The type ID to compare.</param>
+ /// <param name="typeB">The other type ID to compare.</param>
+ /// <returns>true if the type IDs look like the same type, false if not.</returns>
+ public static bool LooksLikeSameType(TypeReference typeA, TypeReference typeB)
+ {
+ return RewriteHelper.TypeDefinitionComparer.Equals(typeA, typeB);
+ }
+
/// <summary>Get whether a method definition matches the signature expected by a method reference.</summary>
/// <param name="definition">The method definition.</param>
/// <param name="reference">The method reference.</param>
@@ -99,178 +106,5 @@ namespace StardewModdingAPI.Framework.ModLoading
.GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public)
.Any(method => RewriteHelper.HasMatchingSignature(method, reference));
}
-
- /// <summary>Determine whether this type ID has a placeholder such as !0.</summary>
- /// <param name="typeID">The type to check.</param>
- /// <returns>true if the type ID contains a placeholder, false if not.</returns>
- public static bool HasPlaceholder(string typeID)
- {
- return typeID.Contains("!0");
- }
-
- /// <summary> returns whether this type ID is a placeholder, i.e., it begins with "!".</summary>
- /// <param name="symbol">The symbol to validate.</param>
- /// <returns>true if the symbol is a placeholder, false if not</returns>
- public static bool IsPlaceholder(string symbol)
- {
- return symbol.StartsWith("!");
- }
-
- /// <summary>Determine whether two type IDs look like the same type, accounting for placeholder values such as !0.</summary>
- /// <param name="a">The type ID to compare.</param>
- /// <param name="b">The other type ID to compare.</param>
- /// <returns>true if the type IDs look like the same type, false if not.</returns>
- public static bool LooksLikeSameType(TypeReference a, TypeReference b)
- {
- string typeA = RewriteHelper.GetComparableTypeID(a);
- string typeB = RewriteHelper.GetComparableTypeID(b);
-
- string placeholderType = "", actualType = "";
-
- if (RewriteHelper.HasPlaceholder(typeA))
- {
- placeholderType = typeA;
- actualType = typeB;
- }
- else if (RewriteHelper.HasPlaceholder(typeB))
- {
- placeholderType = typeB;
- actualType = typeA;
- }
- else
- return typeA == typeB;
-
- return RewriteHelper.PlaceholderTypeValidates(placeholderType, actualType);
- }
-
- /// <summary>Get a unique string representation of a type.</summary>
- /// <param name="type">The type reference.</param>
- private static string GetComparableTypeID(TypeReference type)
- {
- return RewriteHelper.StripTypeNamePattern.Replace(type.FullName, "");
- }
-
- protected class SymbolLocation
- {
- public string symbol;
- public int depth;
-
- public SymbolLocation(string symbol, int depth)
- {
- this.symbol = symbol;
- this.depth = depth;
- }
- }
-
- private static List<char> symbolBoundaries = new List<char> { '<', '>', ',' };
-
- /// <summary> Traverses and parses out symbols from a type which does not contain placeholder values.</summary>
- /// <param name="type">The type to traverse.</param>
- /// <param name="typeSymbols">A List in which to store the parsed symbols.</param>
- private static void TraverseActualType(string type, List<SymbolLocation> typeSymbols)
- {
- int depth = 0;
- string symbol = "";
-
- foreach (char c in type)
- {
- if (RewriteHelper.symbolBoundaries.Contains(c))
- {
- typeSymbols.Add(new SymbolLocation(symbol, depth));
- symbol = "";
- switch (c)
- {
- case '<':
- depth++;
- break;
- case '>':
- depth--;
- break;
- default:
- break;
- }
- }
- else
- symbol += c;
- }
- }
-
- /// <summary> Determines whether two symbols in a type ID match, accounting for placeholders such as !0.</summary>
- /// <param name="symbolA">A symbol in a typename which contains placeholders.</param>
- /// <param name="symbolB">A symbol in a typename which does not contain placeholders.</param>
- /// <param name="placeholderMap">A dictionary containing a mapping of placeholders to concrete types.</param>
- /// <returns>true if the symbols match, false if not.</returns>
- private static bool SymbolsMatch(SymbolLocation symbolA, SymbolLocation symbolB, Dictionary<string, string> placeholderMap)
- {
- if (symbolA.depth != symbolB.depth)
- return false;
-
- if (!RewriteHelper.IsPlaceholder(symbolA.symbol))
- {
- return symbolA.symbol == symbolB.symbol;
- }
-
- if (placeholderMap.ContainsKey(symbolA.symbol))
- {
- return placeholderMap[symbolA.symbol] == symbolB.symbol;
- }
-
- placeholderMap[symbolA.symbol] = symbolB.symbol;
-
- return true;
- }
-
- /// <summary> Determines whether a type which has placeholders correctly resolves to the concrete type provided. </summary>
- /// <param name="type">A type containing placeholders such as !0.</param>
- /// <param name="typeSymbols">The list of symbols extracted from the concrete type.</param>
- /// <returns>true if the type resolves correctly, false if not.</returns>
- private static bool PlaceholderTypeResolvesToActualType(string type, List<SymbolLocation> typeSymbols)
- {
- Dictionary<string, string> placeholderMap = new Dictionary<string, string>();
-
- int depth = 0, symbolCount = 0;
- string symbol = "";
-
- foreach (char c in type)
- {
- if (symbolBoundaries.Contains(c))
- {
- bool match = RewriteHelper.SymbolsMatch(new SymbolLocation(symbol, depth), typeSymbols[symbolCount], placeholderMap);
- if (typeSymbols.Count <= symbolCount ||
- !match)
- return false;
-
- symbolCount++;
- symbol = "";
- switch (c)
- {
- case '<':
- depth++;
- break;
- case '>':
- depth--;
- break;
- default:
- break;
- }
- }
- else
- symbol += c;
- }
-
- return true;
- }
-
- /// <summary>Determines whether a type with placeholders in it matches a type without placeholders.</summary>
- /// <param name="placeholderType">The type with placeholders in it.</param>
- /// <param name="actualType">The type without placeholders.</param>
- /// <returns>true if the placeholder type can resolve to the actual type, false if not.</returns>
- private static bool PlaceholderTypeValidates(string placeholderType, string actualType)
- {
- List<SymbolLocation> typeSymbols = new List<SymbolLocation>();
-
- RewriteHelper.TraverseActualType(actualType, typeSymbols);
- return PlaceholderTypeResolvesToActualType(placeholderType, typeSymbols);
- }
}
}
diff --git a/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs
new file mode 100644
index 00000000..8d128b37
--- /dev/null
+++ b/src/SMAPI/Framework/ModLoading/TypeReferenceComparer.cs
@@ -0,0 +1,209 @@
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+using Mono.Cecil;
+
+namespace StardewModdingAPI.Framework.ModLoading
+{
+ /// <summary>Performs heuristic equality checks for <see cref="TypeReference"/> instances.</summary>
+ internal class TypeReferenceComparer : IEqualityComparer<TypeReference>
+ {
+ /*********
+ ** Properties
+ *********/
+ /// <summary>A pattern matching type name substrings to strip for display.</summary>
+ private readonly Regex StripTypeNamePattern = new Regex(@"`\d+(?=<)", RegexOptions.Compiled);
+
+ private List<char> symbolBoundaries = new List<char> { '<', '>', ',' };
+
+
+ /*********
+ ** Public methods
+ *********/
+ /// <summary>Get whether the specified objects are equal.</summary>
+ /// <param name="a">The first object to compare.</param>
+ /// <param name="b">The second object to compare.</param>
+ public bool Equals(TypeReference a, TypeReference b)
+ {
+ string typeA = this.GetComparableTypeID(a);
+ string typeB = this.GetComparableTypeID(b);
+
+ string placeholderType = "", actualType = "";
+
+ if (this.HasPlaceholder(typeA))
+ {
+ placeholderType = typeA;
+ actualType = typeB;
+ }
+ else if (this.HasPlaceholder(typeB))
+ {
+ placeholderType = typeB;
+ actualType = typeA;
+ }
+ else
+ return typeA == typeB;
+
+ return this.PlaceholderTypeValidates(placeholderType, actualType);
+ }
+
+ /// <summary>Get a hash code for the specified object.</summary>
+ /// <param name="obj">The object for which a hash code is to be returned.</param>
+ /// <exception cref="T:System.ArgumentNullException">The object type is a reference type and <paramref name="obj" /> is null.</exception>
+ public int GetHashCode(TypeReference obj)
+ {
+ return obj.GetHashCode();
+ }
+
+
+ /*********
+ ** Private methods
+ *********/
+ /// <summary>Get a unique string representation of a type.</summary>
+ /// <param name="type">The type reference.</param>
+ private string GetComparableTypeID(TypeReference type)
+ {
+ return this.StripTypeNamePattern.Replace(type.FullName, "");
+ }
+
+ /// <summary>Determine whether this type ID has a placeholder such as !0.</summary>
+ /// <param name="typeID">The type to check.</param>
+ /// <returns>true if the type ID contains a placeholder, false if not.</returns>
+ private bool HasPlaceholder(string typeID)
+ {
+ return typeID.Contains("!0");
+ }
+
+ /// <summary> returns whether this type ID is a placeholder, i.e., it begins with "!".</summary>
+ /// <param name="symbol">The symbol to validate.</param>
+ /// <returns>true if the symbol is a placeholder, false if not</returns>
+ private bool IsPlaceholder(string symbol)
+ {
+ return symbol.StartsWith("!");
+ }
+
+ /// <summary> Traverses and parses out symbols from a type which does not contain placeholder values.</summary>
+ /// <param name="type">The type to traverse.</param>
+ /// <param name="typeSymbols">A List in which to store the parsed symbols.</param>
+ private void TraverseActualType(string type, List<SymbolLocation> typeSymbols)
+ {
+ int depth = 0;
+ string symbol = "";
+
+ foreach (char c in type)
+ {
+ if (this.symbolBoundaries.Contains(c))
+ {
+ typeSymbols.Add(new SymbolLocation(symbol, depth));
+ symbol = "";
+ switch (c)
+ {
+ case '<':
+ depth++;
+ break;
+ case '>':
+ depth--;
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ symbol += c;
+ }
+ }
+
+ /// <summary> Determines whether two symbols in a type ID match, accounting for placeholders such as !0.</summary>
+ /// <param name="symbolA">A symbol in a typename which contains placeholders.</param>
+ /// <param name="symbolB">A symbol in a typename which does not contain placeholders.</param>
+ /// <param name="placeholderMap">A dictionary containing a mapping of placeholders to concrete types.</param>
+ /// <returns>true if the symbols match, false if not.</returns>
+ private bool SymbolsMatch(SymbolLocation symbolA, SymbolLocation symbolB, Dictionary<string, string> placeholderMap)
+ {
+ if (symbolA.depth != symbolB.depth)
+ return false;
+
+ if (!this.IsPlaceholder(symbolA.symbol))
+ {
+ return symbolA.symbol == symbolB.symbol;
+ }
+
+ if (placeholderMap.ContainsKey(symbolA.symbol))
+ {
+ return placeholderMap[symbolA.symbol] == symbolB.symbol;
+ }
+
+ placeholderMap[symbolA.symbol] = symbolB.symbol;
+
+ return true;
+ }
+
+ /// <summary> Determines whether a type which has placeholders correctly resolves to the concrete type provided. </summary>
+ /// <param name="type">A type containing placeholders such as !0.</param>
+ /// <param name="typeSymbols">The list of symbols extracted from the concrete type.</param>
+ /// <returns>true if the type resolves correctly, false if not.</returns>
+ private bool PlaceholderTypeResolvesToActualType(string type, List<SymbolLocation> typeSymbols)
+ {
+ Dictionary<string, string> placeholderMap = new Dictionary<string, string>();
+
+ int depth = 0, symbolCount = 0;
+ string symbol = "";
+
+ foreach (char c in type)
+ {
+ if (this.symbolBoundaries.Contains(c))
+ {
+ bool match = this.SymbolsMatch(new SymbolLocation(symbol, depth), typeSymbols[symbolCount], placeholderMap);
+ if (typeSymbols.Count <= symbolCount ||
+ !match)
+ return false;
+
+ symbolCount++;
+ symbol = "";
+ switch (c)
+ {
+ case '<':
+ depth++;
+ break;
+ case '>':
+ depth--;
+ break;
+ default:
+ break;
+ }
+ }
+ else
+ symbol += c;
+ }
+
+ return true;
+ }
+
+ /// <summary>Determines whether a type with placeholders in it matches a type without placeholders.</summary>
+ /// <param name="placeholderType">The type with placeholders in it.</param>
+ /// <param name="actualType">The type without placeholders.</param>
+ /// <returns>true if the placeholder type can resolve to the actual type, false if not.</returns>
+ private bool PlaceholderTypeValidates(string placeholderType, string actualType)
+ {
+ List<SymbolLocation> typeSymbols = new List<SymbolLocation>();
+
+ this.TraverseActualType(actualType, typeSymbols);
+ return PlaceholderTypeResolvesToActualType(placeholderType, typeSymbols);
+ }
+
+
+
+ /*********
+ ** Inner classes
+ *********/
+ protected class SymbolLocation
+ {
+ public string symbol;
+ public int depth;
+
+ public SymbolLocation(string symbol, int depth)
+ {
+ this.symbol = symbol;
+ this.depth = depth;
+ }
+ }
+ }
+}