C# Client Library
A C# Client Library for the AnalyzeRe REST API
Loading...
Searching...
No Matches
TypeResolver.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Concurrent;
3using System.Collections.Generic;
4using System.Diagnostics;
5using System.Linq;
6using System.Reflection;
7
9
10namespace AnalyzeRe.Utilities
11{
13 public static class TypeResolver
14 {
15 #region All App Domain Types
16 // Used for caching results of resolveReferenceType method (which uses assembly level reflection) to improve performance.
17 private static readonly List<Type> CachedAppDomainTypes = new List<Type>();
18 // Used to determine if the cache is populated without locking
19 private static volatile bool _isCachedAppDomainTypesPopulated = false;
20
23 public static List<Type> GetAppDomainTypes()
24 {
25 if (_isCachedAppDomainTypesPopulated)
26 return CachedAppDomainTypes;
27 // Lock to ensure multiple threads don't try to create the cache.
28 lock (CachedAppDomainTypes)
29 {
30 // If another thread populated the cache since we asked for the lock, return
31 if (_isCachedAppDomainTypesPopulated)
32 return CachedAppDomainTypes;
33
34 // Populate a cache of assembly types so that we don't have to do this (expensive) cache again.
35 CachedAppDomainTypes.AddRange(AppDomain.CurrentDomain.GetAssemblies().SelectMany(assembly =>
36 {
37 try { return assembly.GetTypes(); }
38 catch (ReflectionTypeLoadException ex)
39 {
40 foreach (Exception inner in ex.LoaderExceptions)
41 Debug.WriteLine("TypeResolver could not locate assembly: " +
42 $"{assembly.FullName}. Message: {inner.Message}");
43 return new Type[] { };
44 }
45 }));
46
47 _isCachedAppDomainTypesPopulated = true;
48 return CachedAppDomainTypes;
49 }
50 }
51 #endregion All App Domain Types
52
53 #region All App Domain AnalyzeRe IAPI Types
54 // Subset of above cached items that includes only IAPIType deriving objects, since these
55 // are requested most frequently by the deserializer - we want to access them first.
56 private static readonly List<Type> CachedIAPITypes = new List<Type>();
57 // Used to determine if the cache is populated without locking
58 private static volatile bool _isCachedIAPITypesPopulated = false;
59
62 public static List<Type> GetAPITypes()
63 {
64 if (_isCachedIAPITypesPopulated)
65 return CachedAppDomainTypes;
66 // Lock to ensure multiple threads don't try to create the cache.
67 lock (CachedIAPITypes)
68 {
69 // If another thread populated the cache since we asked for the lock, return
70 if (_isCachedIAPITypesPopulated)
71 return CachedAppDomainTypes;
72
73 CachedIAPITypes.AddRange(
74 GetAppDomainTypes().Where(t => typeof(APIType).IsAssignableFrom(t)));
75
76 _isCachedIAPITypesPopulated = true;
77 return CachedIAPITypes;
78 }
79 }
80 #endregion All App Domain AnalyzeRe IAPI Types
81
82 #region API Type Names for APIType classes
84 private static readonly ConcurrentDictionary<Type, string> CachedAPITypeNames =
85 new ConcurrentDictionary<Type, string>();
86
92 public static string GetTypeNameForAPIType(Type type)
93 {
94 if (type == null)
95 throw new ArgumentNullException(nameof(type), "Could not determine the declaring type.");
96 return CachedAPITypeNames.GetOrAdd(type, GetTypeNameForAPIType_OnAdd);
97 }
98
99 // Helper method for populating the above cache.
100 private static string GetTypeNameForAPIType_OnAdd(Type type)
101 {
103 Attribute.GetCustomAttribute(type, typeof(APITypeAliasAttribute));
104 return overridingType != null ? overridingType.TypeName : type.Name;
105 }
106 #endregion API Type Names for APIType classes
107
108 #region ResolveTypeByName
109 // Cache of type name and base type pairs that have been mapped to a resolved type.
110 private static readonly ConcurrentDictionary<Tuple<string, Type>, Type> CachedResolvedTypes =
111 new ConcurrentDictionary<Tuple<string, Type>, Type>();
112
121 public static bool ResolveTypeByName(string typeName, out Type resolvedType, Type requiredBaseType = null)
122 {
123 // Look up the value in the cache, and return it if it is there.
124 Tuple<string, Type> key = new Tuple<string, Type>(typeName, requiredBaseType);
125 resolvedType = CachedResolvedTypes.GetOrAdd(key, ResolveTypeByName_OnAdd);
126 return resolvedType != null;
127 }
128
129 // Helper method for populating the above cache.
130 private static Type ResolveTypeByName_OnAdd(Tuple<string, Type> parameters)
131 {
132 string typeName = parameters.Item1;
133 Type requiredBaseType = parameters.Item2;
134
135 // Loop over the cached list of APIType types and see if there's a match
136 foreach (Type classAPIType in GetAPITypes())
137 if (GetTypeNameForAPIType(classAPIType) == typeName &&
138 (requiredBaseType == null || requiredBaseType.IsAssignableFrom(classAPIType)))
139 return classAPIType;
140 // Broaden the search and loop over the cached list of all types and see if there is a matching type.
141 foreach (Type classAPIType in GetAppDomainTypes())
142 if (classAPIType.Name == typeName &&
143 (requiredBaseType == null || requiredBaseType.IsAssignableFrom(classAPIType)))
144 return classAPIType;
145 // If the above is unsuccessful, we do not recognize this server type.
146 Debug.WriteLine("Warning: Unrecognized reference to type \"" + typeName +
147 "\". Is the server using a polymorphic sub-type the client doesn't know about?");
148 return null;
149 }
150 #endregion ResolveTypeByName
151
152 #region ResolveBaseTypeByURL
154 private static readonly ConcurrentDictionary<string, Type> CachedAPIResourceCollections =
155 new ConcurrentDictionary<string, Type>();
156 // Used to determine if the cache is populated without locking
157 private static volatile bool _isCachedAPIResourceCollectionsPopulated = false;
158
162 public static Type ResolveBaseTypeByURL(string href)
163 {
164 return ResolveBaseTypeByCollectionName(API.GetCollectionNameFromResourceHref(href));
165 }
166
170 public static Type ResolveBaseTypeByCollectionName(string collection)
171 {
172 // Prevent more than one thread from trying to populate the cache, but
173 // allow immediate use of the cache if it's flagged as having been populated
174 if (!_isCachedAPIResourceCollectionsPopulated)
175 lock (CachedAPIResourceCollections)
176 {
177 // Ensure another thread hasn't populated the cache since we asked for the lock
178 if (!_isCachedAPIResourceCollectionsPopulated)
179 {
180 IEnumerable<Type> toScrape = GetAPITypes().Where(apiType =>
181 typeof(IAPIResource).IsAssignableFrom(apiType));
182 // Scrape these APITypes for their collection names and cache them.
183 foreach (Type apiType in toScrape)
184 {
185 string collectionName = API.GetCollectionName(apiType, out Type baseType, true);
186 // Ignore classes that have not inherited a collectionName
187 if (collectionName == null)
188 {
189 Debug.WriteLine("No collection for type: " + apiType.NiceTypeName());
190 continue;
191 }
192#if DEBUG
193 if (API.RequestLogging)
194 {
195 Debug.WriteLine($"Resolved collection for type: {apiType.NiceTypeName()} " +
196 $"({collectionName}) Declaring type: {baseType.NiceTypeName()}");
197 }
198#endif
199 if (!CachedAPIResourceCollections.ContainsKey(collectionName))
200 CachedAPIResourceCollections.TryAdd(collectionName, baseType);
201 }
202 _isCachedAPIResourceCollectionsPopulated = true;
203 }
204 }
205 CachedAPIResourceCollections.TryGetValue(collection, out Type result);
206 return result;
207 }
208 #endregion ResolveBaseTypeByURL
209
210 #region Utilities
211 // Cache of type name and base type pairs that have been mapped to a resolved type.
212 private static readonly ConcurrentDictionary<Type, List<Type>> CachedInstantiableSubtypes =
213 new ConcurrentDictionary<Type, List<Type>>();
214
219 public static List<Type> GetAllInstantiableSubtypes(Type type)
220 {
221 return CachedInstantiableSubtypes.GetOrAdd(type, T =>
222 {
223 List<Type> appDomainTypes = GetAppDomainTypes();
224
225 List<Type> subTypes;
226 if (T.IsGenericType && T.GetGenericArguments().Length == 1)
227 {
228 Type genericTypeDefinition = T.GetGenericTypeDefinition();
229 Type genericArgumentType = T.GetGenericArguments()[0];
230
231 if (genericTypeDefinition.IsInterface || genericTypeDefinition.IsAbstract || genericArgumentType.IsGenericParameter)
232 {
233 // Brutal Hack: If the generic type definition isn't instantiable, I appear to be grabbing the first type I can find that
234 // IS instantiable and implements this generic abstract class or interface
235 genericTypeDefinition = appDomainTypes
236 .Where(tsub => tsub.IsGenericType && !tsub.IsInterface && !tsub.IsAbstract &&
237 tsub.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == genericTypeDefinition))
238 .OrderBy(elem => Guid.NewGuid()).FirstOrDefault();
239 }
240 if (genericTypeDefinition == null)
241 {
242 if (genericArgumentType.IsGenericParameter)
243 {
244 genericTypeDefinition = T.GetGenericTypeDefinition();
245 if (genericArgumentType.GetGenericParameterConstraints().Length > 0) // Maybe the first generic parameter constraint can serve as an instantiable sub-type?
246 genericArgumentType = genericArgumentType.GetGenericParameterConstraints()[0];
247 else // No constraint means it's an object.
248 genericArgumentType = typeof(object);
249 }
250 else
251 throw new Exception("Could not find a type that implements the generic interface or abstract class: " + T.GetGenericTypeDefinition().NiceTypeName());
252 }
253 subTypes = GetAllInstantiableSubtypes(genericArgumentType)
254 .Select(innerT => genericTypeDefinition.MakeGenericTypeFast(innerT)).ToList();
255 }
256 else if (!T.IsGenericType && T != typeof(object))
257 {
258 List<Type> discovered = new List<Type>();
259 appDomainTypes.Where(tsub => T.IsAssignableFrom(tsub) && tsub.IsClass &&
260 !tsub.IsInterface && !tsub.IsAbstract).ToList().ForEach(subtype =>
261 {
262 if (subtype.ContainsGenericParameters)
263 {
264 try { discovered.AddRange(GetAllInstantiableSubtypes(subtype)); }
265 // If this sub-type ends up not being covariantly valid, don't sweat it
266 // ReSharper disable once EmptyGeneralCatchClause
267 catch (Exception) { }
268 }
269 else
270 discovered.Add(subtype);
271 });
272 subTypes = discovered;
273 }
274 else if (T == typeof(object))
275 subTypes = new List<Type> { typeof(object) };
276 else
277 throw new Exception("Not sure how to create a new instance of type: " + T.NiceTypeName());
278
279 return subTypes;
280 });
281 }
282
288 public static Type GetGenericCovariantBase(Type T)
289 {
290 Type genericTypeDefinition;
291 Type[] genericTypeArguments = null;
292 // If this type is not a generic type definition, get the generic type definition,
293 // and also remember the generic arguments in use for this specific type.
294 if (T.IsGenericTypeDefinition)
295 genericTypeDefinition = T;
296 else
297 {
298 genericTypeDefinition = T.GetGenericTypeDefinition();
299 genericTypeArguments = T.GetGenericArguments();
300 }
301 // If this generic type is not an interface, look for the generic interface
302 Type selectedInterface;
303 if (T.IsInterface)
304 selectedInterface = T;
305 else
306 {
307 IEnumerable<Type> baseInterfaces = genericTypeDefinition.GetInterfaces();
308 selectedInterface = baseInterfaces.Last();
309 // If any other interfaces are assignable to the selected interface, select them
310 // instead since they are more derived.
311 foreach (Type currentInterface in baseInterfaces)
312 if (selectedInterface.IsAssignableFrom(currentInterface))
313 selectedInterface = currentInterface;
314 }
315 // If the selected interface has no generic parameters, we can return it directly
316 if (!selectedInterface.ContainsGenericParameters)
317 return selectedInterface;
318 // Get the generic type definition for the selected interface
319 if (!selectedInterface.IsGenericTypeDefinition)
320 selectedInterface = selectedInterface.GetGenericTypeDefinition();
321 // If the supplied type already specified generic type arguments, use those,
322 // Otherwise use the generic parameter constraints.
323 if (genericTypeArguments == null)
324 {
325 genericTypeArguments = selectedInterface.GetGenericArguments()
326 .Select(genericArgument => genericArgument.GetGenericParameterConstraints()[0])
327 .ToArray();
328 }
329 Type covariantInterface = genericTypeArguments.Length == 1 ?
330 selectedInterface.MakeGenericTypeFast(genericTypeArguments[0]) :
331 selectedInterface.MakeGenericType(genericTypeArguments);
332 return covariantInterface;
333 }
334
335 #region Fast MakeGenericType
336 private static readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, Type>>
337 GenericSubtypeCaches = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, Type>>();
338
343 public static Type MakeGenericTypeFast(this Type generic_base, Type type_parameter)
344 {
345 ConcurrentDictionary<Type, Type> cache = GenericSubtypeCaches.GetOrAdd(generic_base,
346 type => new ConcurrentDictionary<Type, Type>());
347 return cache.GetOrAdd(type_parameter, type => generic_base.MakeGenericType(type));
348 }
349 #endregion Fast MakeGenericType
350
351 #region GetEnumerableType
352 private static readonly ConcurrentDictionary<Type, Type>
353 EnumberableTypeCache = new ConcurrentDictionary<Type, Type>();
354
358 public static Type GetEnumerableType(Type type)
359 {
360 return EnumberableTypeCache.GetOrAdd(type, t => t.GetInterfaces()
361 .FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
362 ?.GetGenericArguments().Single());
363 }
364 #endregion GetEnumerableType
365
366 #region ImplementsInterface
367 private static readonly ConcurrentDictionary<Tuple<Type, Type>, bool>
368 ImplementsInterfaceCache = new ConcurrentDictionary<Tuple<Type, Type>, bool>();
369
371 public static bool ImplementsInterface(this Type type, Type interfaceType)
372 {
373 Tuple<Type, Type> key = Tuple.Create(type, interfaceType);
374 return ImplementsInterfaceCache.GetOrAdd(key,
375 k => k.Item1.GetInterfaces().Any(i => i == interfaceType ||
376 i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType));
377 }
378 #endregion GetEnumerableType
379 #endregion Utilities
380 }
381}
Base class used by all types and resources.
Definition APIType.cs:8
API methods / requests made available to the user.
static string GetCollectionName(Type requestType)
Gets the collection name for the given APIResource type, whether it's instantiable or not.
static string GetCollectionNameFromResourceHref(string href)
Return the collection name implied by the href for a given resource assuming the URL is in the format...
Specifies the name of the type on the API if it differs from the class name - used for serializing an...
Utility for resolving types given only the types name (Useful for parsing ambiguous JSON objects).
static List< Type > GetAPITypes()
Returns a list of all APIType types in the current domain assemblies.
static Type ResolveBaseTypeByCollectionName(string collection)
Get the base type of object referenced by the supplied server URL.
static Type ResolveBaseTypeByURL(string href)
Get the base type of object referenced by the supplied server URL.
static List< Type > GetAllInstantiableSubtypes(Type type)
Attempts to create a list of types in the current application domain's assemblies derived from the gi...
static Type GetEnumerableType(Type type)
If the specified type implements IEnumerable<T>, returns the generic type argument....
static string GetTypeNameForAPIType(Type type)
Gets the type name from the overriding type alias attribute or class name based using the supplied ty...
static bool ImplementsInterface(this Type type, Type interfaceType)
Returns true if the specified type implements the interface.
static bool ResolveTypeByName(string typeName, out Type resolvedType, Type requiredBaseType=null)
Clever method that checks all assemblies in the application being compiled for the type referenced by...
static Type GetGenericCovariantBase(Type T)
Returns the most strongly typed covariant base type which is assignable from the specified type T.
static Type MakeGenericTypeFast(this Type generic_base, Type type_parameter)
Invokes a call to MakeGenericType with a backing cache.
static List< Type > GetAppDomainTypes()
Returns a list of all types in the current domain assemblies.
Interface for Base class used by all resources.