// // FrameworkLookup.cs // // Author: // Mike Krüger // // Copyright (c) 2013 Xamarin Inc. (http://xamarin.com) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using ICSharpCode.NRefactory.Semantics; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; namespace ICSharpCode.NRefactory.Completion { /// /// The framework lookup provides a fast lookup where an unknow type or extension method may be defined in. /// public sealed class FrameworkLookup { /* Binary format: * [Header] * [Version] : [Major (byte)] [Minor (byte)] [Build (byte)] * [#Types (int)] * [#Methods (int)] * [#Assemblies (int)] * [AssemblyListTable] : #Assemblies x [OffsetToAssemblyLists (int)] * [TypeLookupTable] : #Types x ( [NameHash (int)] [AssemblyPtrToAssemblyListTable (ushort)] * [ExtMethodLookupTable] : #Methods x ( [NameHash (int)] [AssemblyPtrToAssemblyListTable (ushort)] * [AssemblyLists] * [#Count (byte)] * #Count x [AssemblyLookup] : [Package (string)] [FullName (string)] [Namespace (string)] */ const int headerSize = 3 + // Version 4 + // #Types 4 + // #Methods 4 // #Assembly /* + 4*/; public static readonly Version CurrentVersion = new Version (2, 0, 1); public static readonly FrameworkLookup Empty = new FrameworkLookup (); string fileName; int[] assemblyListTable; int[] typeLookupTable; int[] extLookupTable; /// /// This method tries to get a matching extension method. /// /// The extension method lookups. /// The resolve result. public IEnumerable GetExtensionMethodLookups (UnknownMemberResolveResult resolveResult) { return GetLookup (resolveResult.MemberName, extLookupTable, headerSize + assemblyListTable.Length * 4 + typeLookupTable.Length * 8); } /// /// Tries to get a type out of an unknow identifier result. /// /// The assemblies the type may be defined (if any). /// The resolve result. /// Type parameter count. /// If set to true this resolve result may be inside an attribute. public IEnumerable GetLookups (UnknownIdentifierResolveResult resolveResult, int typeParameterCount, bool isInsideAttributeType) { string name = isInsideAttributeType ? resolveResult.Identifier + "Attribute" : resolveResult.Identifier; var identifier = GetIdentifier (name, typeParameterCount); return GetLookup (identifier, typeLookupTable, headerSize + assemblyListTable.Length * 4); } /// /// Tries to get a type out of an unknow member resolve result. (In case of fully qualified names) /// /// The assemblies the type may be defined (if any). /// The resolve result. /// /// Type parameter count. /// If set to true this resolve result may be inside an attribute. public IEnumerable GetLookups (UnknownMemberResolveResult resolveResult, string fullMemberName, int typeParameterCount, bool isInsideAttributeType) { string name = isInsideAttributeType ? resolveResult.MemberName + "Attribute" : resolveResult.MemberName; var identifier = GetIdentifier (name, typeParameterCount); foreach (var lookup in GetLookup (identifier, typeLookupTable, headerSize + assemblyListTable.Length * 4)) { if (fullMemberName.StartsWith (lookup.Namespace, StringComparison.Ordinal)) yield return lookup; } } /// /// The assembly lookup determines where a type might be defined. /// It contains the assembly & the namespace. /// public struct AssemblyLookup { readonly string nspace; /// /// The namespace the requested type is in. /// public string Namespace { get { return nspace; } } readonly string fullName; /// /// Gets the full name af the assembly. /// public string FullName { get { return fullName; } } readonly string package; /// /// Gets the package the assembly is in. /// public string Package { get { return package; } } /// /// Initializes a new instance of the struct. /// /// The package name. /// The full name of the assembly. /// The namespace the type is in. internal AssemblyLookup (string package, string fullName, string nspace) { if (nspace == null) throw new ArgumentNullException ("nspace"); if (fullName == null) throw new ArgumentNullException ("fullName"); this.package = package; this.fullName = fullName; this.nspace = nspace; } public override string ToString () { return string.Format ("[AssemblyLookup: Namespace={0}, FullName={1}, Package={2}]", Namespace, FullName, Package); } public override bool Equals (object obj) { if (obj == null) return false; // if (ReferenceEquals (this, obj)) // return true; if (obj.GetType () != typeof(AssemblyLookup)) return false; var other = (AssemblyLookup)obj; return Namespace == other.Namespace && FullName == other.FullName && Package == other.Package; } public override int GetHashCode () { unchecked { return (Namespace != null ? Namespace.GetHashCode () : 0) ^ (FullName != null ? FullName.GetHashCode () : 0) ^ (Package != null ? Package.GetHashCode () : 0); } } } /// /// This method returns a new framework builder to build a new framework lookup data file. /// /// The file name of the data file. public static FrameworkBuilder Create (string fileName) { return new FrameworkBuilder (fileName); } /// /// Loads a framework lookup object from a file. May return null, if the file wasn't found or has a version mismatch. /// /// File name. public static FrameworkLookup Load (string fileName) { try { if (!File.Exists (fileName)) return null; } catch (Exception) { return null; } var result = new FrameworkLookup (); result.fileName = fileName; var fs = File.OpenRead (fileName); using (var reader = new BinaryReader (fs, Encoding.UTF8)) { var major = reader.ReadByte (); var minor = reader.ReadByte (); var build = reader.ReadByte (); var version = new Version (major, minor, build); if (version != CurrentVersion) return null; int typeLookupListCount = reader.ReadInt32 (); int extLookupListCount = reader.ReadInt32 (); int assemblyLookupCount = reader.ReadInt32 (); result.assemblyListTable = new int[assemblyLookupCount]; for (int i = 0; i < assemblyLookupCount; i++) { result.assemblyListTable[i] = reader.ReadInt32 (); } result.typeLookupTable = new int[typeLookupListCount]; for (int i = 0; i < typeLookupListCount; i++) { result.typeLookupTable [i] = reader.ReadInt32 (); // skip list offset reader.ReadInt32 (); } result.extLookupTable = new int[extLookupListCount]; for (int i = 0; i < extLookupListCount; i++) { result.extLookupTable [i] = reader.ReadInt32 (); // skip list offset reader.ReadInt32 (); } } return result; } FrameworkLookup () { } IEnumerable GetLookup (string identifier, int[] lookupTable, int tableOffset) { if (lookupTable == null) yield break; int index = Array.BinarySearch (lookupTable, GetStableHashCode (identifier)); if (index < 0) yield break; using (var reader = new BinaryReader (File.Open (fileName, FileMode.Open, FileAccess.Read, FileShare.Read), Encoding.UTF8)) { reader.BaseStream.Seek (tableOffset + index * 8 + 4, SeekOrigin.Begin); int listPtr = reader.ReadInt32 (); reader.BaseStream.Seek (listPtr, SeekOrigin.Begin); var b = reader.ReadInt32 (); var assemblies = new List (); while (b-- > 0) { var assembly = reader.ReadUInt16 (); if (assembly < 0 || assembly >= assemblyListTable.Length) throw new InvalidDataException ("Assembly lookup was " + assembly + " but only " + assemblyListTable.Length + " are known."); assemblies.Add (assembly); } foreach (var assembly in assemblies) { reader.BaseStream.Seek (assemblyListTable [assembly], SeekOrigin.Begin); var package = reader.ReadString (); var fullName = reader.ReadString (); var ns = reader.ReadString (); yield return new AssemblyLookup (package, fullName, ns); } } } /// /// Retrieves a hash code for the specified string that is stable across /// .NET upgrades. /// /// Use this method instead of the normal string.GetHashCode if the hash code /// is persisted to disk. /// static int GetStableHashCode(string text) { unchecked { int h = 0; foreach (char c in text) { h = (h << 5) - h + c; } return h; } } static string GetIdentifier (string identifier, int tc) { if (tc == 0) return identifier; return identifier + "`" + tc; } public class FrameworkBuilder : IDisposable { readonly string fileName; Dictionary> typeLookup = new Dictionary> (); Dictionary> extensionMethodLookup = new Dictionary> (); List assemblyLookups = new List (); Dictionary methodCheck = new Dictionary (); Dictionary typeCheck = new Dictionary (); internal FrameworkBuilder (string fileName) { this.fileName = fileName; } static int[] WriteTable (MemoryStream stream, Dictionary> table, out List>> list) { list = new List>> (table); list.Sort ((x, y) => x.Key.CompareTo (y.Key)); var result = new int[list.Count]; using (var bw = new BinaryWriter (stream)) { for (int i = 0; i < result.Length; i++) { result [i] = (int)stream.Length; bw.Write (list [i].Value.Count); foreach (var ii in list [i].Value) bw.Write (ii); } } return result; } #region IDisposable implementation void IDisposable.Dispose () { var typeLookupMemory = new MemoryStream (); List>> typeLookupList; var typeTable = WriteTable (typeLookupMemory, typeLookup, out typeLookupList); var extMethodLookupMemory = new MemoryStream (); List>> extMethodLookuplist; var extMethodTable = WriteTable (extMethodLookupMemory, extensionMethodLookup, out extMethodLookuplist); var assemblyLookupMemory = new MemoryStream (); var assemblyPositionTable = new int[assemblyLookups.Count]; using (var writer = new BinaryWriter (assemblyLookupMemory, Encoding.UTF8)) { for (int i = 0; i < assemblyLookups.Count; i++) { var lookup = assemblyLookups[i]; assemblyPositionTable[i] = (int)assemblyLookupMemory.Length; writer.Write (lookup.Package); writer.Write (lookup.FullName); writer.Write (lookup.Namespace); } } using (var stream = new BinaryWriter (File.OpenWrite (fileName), Encoding.UTF8)) { stream.Write ((byte)CurrentVersion.Major); stream.Write ((byte)CurrentVersion.Minor); stream.Write ((byte)CurrentVersion.Build); stream.Write (typeLookupList.Count); stream.Write (extMethodLookuplist.Count); stream.Write (assemblyLookups.Count); var typeBuffer = typeLookupMemory.ToArray (); var extMethodBuffer = extMethodLookupMemory.ToArray (); int dataOffset = headerSize + assemblyLookups.Count * 4 + typeLookupList.Count * (4 + 4) + extMethodLookuplist.Count * (4 + 4); for (int i = 0; i < assemblyLookups.Count; i++) { stream.Write ((int)(dataOffset + typeBuffer.Length + extMethodBuffer.Length + assemblyPositionTable[i])); } for (int i = 0; i < typeLookupList.Count; i++) { stream.Write (typeLookupList [i].Key); stream.Write (dataOffset + typeTable[i]); } for (int i = 0; i < extMethodLookuplist.Count; i++) { stream.Write (extMethodLookuplist [i].Key); stream.Write (dataOffset + typeBuffer.Length + extMethodTable[i]); } stream.Write (typeBuffer); stream.Write (extMethodBuffer); stream.Write (assemblyLookupMemory.ToArray ()); stream.Flush (); } } #endregion struct FrameworkLookupId { public string PackageName; public string AssemblyName; public string NameSpace; } Dictionary frameworkLookupTable = new Dictionary (); ushort GetLookup (string packageName, string assemblyName, string ns) { var id = new FrameworkLookupId { PackageName = packageName, AssemblyName = assemblyName, NameSpace = ns }; ushort value; if (frameworkLookupTable.TryGetValue (id, out value)) return value; var result = new AssemblyLookup (packageName, assemblyName, ns); assemblyLookups.Add (result); var index = assemblyLookups.Count - 1; if (index > ushort.MaxValue) throw new InvalidOperationException ("Assembly lookup list overflow > " + ushort.MaxValue + " assemblies."); frameworkLookupTable.Add (id, (ushort)index); return (ushort)index; } bool AddToTable (string packageName, string assemblyName, Dictionary> table, Dictionary checkTable, string id, string ns) { List list; var hash = GetStableHashCode (id); if (!table.TryGetValue (hash, out list)) { list = new List (); table [hash] = list; } else { string existingString; if (checkTable.TryGetValue (hash, out existingString)) { if (existingString != id) throw new InvalidOperationException ("Duplicate hash for " + existingString + " and "+ id); } else { checkTable.Add (hash, id); } } var assemblyLookup = GetLookup (packageName, assemblyName, ns); if (!list.Any (a => a.Equals (assemblyLookup))) { list.Add (assemblyLookup); return true; } return false; } /// /// Add a type to the framework lookup. /// /// The package the assembly of the type is defined (can be null). /// The full assembly name the type is defined (needs to be != null). /// The type definition (needs to be != null). public void AddLookup (string packageName, string fullAssemblyName, IUnresolvedTypeDefinition type) { if (fullAssemblyName == null) throw new ArgumentNullException ("fullAssemblyName"); if (type == null) throw new ArgumentNullException ("type"); var id = GetIdentifier (type.Name, type.TypeParameters.Count); if (AddToTable (packageName, fullAssemblyName, typeLookup, typeCheck, id, type.Namespace)) { if (type.IsSealed || type.IsStatic) { foreach (var method in type.Methods) { var m = method as DefaultUnresolvedMethod; if (m == null || !m.IsExtensionMethod) continue; AddToTable (packageName, fullAssemblyName, extensionMethodLookup, methodCheck, method.Name, method.DeclaringTypeDefinition.Namespace); } } } } } } }