| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| using System; |
| using System.Collections.Concurrent; |
| using System.Collections.Generic; |
| using System.Reflection; |
| |
| namespace Avro.Specific |
| { |
| /// <summary> |
| /// Resolves and creates types associated with a schema and/or name. You should generally use |
| /// the shared <see cref="Instance"/> to take advantage caching. |
| /// </summary> |
| public sealed class ObjectCreator |
| { |
| /// <summary> |
| /// Shareable instance of the <see cref="ObjectCreator"/>. |
| /// </summary> |
| public static ObjectCreator Instance { get; } = new ObjectCreator(); |
| |
| /// <summary> |
| /// Static generic dictionary type used for creating new dictionary instances |
| /// </summary> |
| private readonly Type GenericMapType = typeof(Dictionary<,>); |
| |
| /// <summary> |
| /// Static generic list type used for creating new array instances |
| /// </summary> |
| private readonly Type GenericListType = typeof(List<>); |
| |
| /// <summary> |
| /// Static generic list type used for creating new IList instances |
| /// </summary> |
| private readonly Type GenericIListType = typeof(IList<>); |
| |
| /// <summary> |
| /// Static generic nullable type used for creating new nullable instances |
| /// </summary> |
| private readonly Type GenericNullableType = typeof(Nullable<>); |
| |
| private readonly ConcurrentDictionary<string, Type> typeCacheByName; |
| private readonly Assembly execAssembly; |
| private readonly Assembly entryAssembly; |
| private readonly bool diffAssembly; |
| |
| /// <summary> |
| /// Initializes a new instance of the <see cref="ObjectCreator"/> class. |
| /// </summary> |
| public ObjectCreator() |
| { |
| typeCacheByName = new ConcurrentDictionary<string, Type>(); |
| execAssembly = Assembly.GetExecutingAssembly(); |
| entryAssembly = Assembly.GetEntryAssembly(); |
| |
| // entryAssembly returns null when running from NUnit |
| diffAssembly = entryAssembly != null && execAssembly != entryAssembly; |
| } |
| |
| /// <summary> |
| /// Find the type with the given name |
| /// </summary> |
| /// <param name="name">the object type to locate</param> |
| /// <returns>the object type, or <c>null</c> if not found</returns> |
| /// <exception cref="AvroException"> |
| /// No type found matching the given name. |
| /// </exception> |
| private Type FindType(string name) |
| { |
| return typeCacheByName.GetOrAdd(name, (_) => |
| { |
| Type type = null; |
| |
| if (TryGetIListItemTypeName(name, out var itemTypeName)) |
| { |
| return GenericIListType.MakeGenericType(FindType(itemTypeName)); |
| } |
| |
| if (TryGetNullableItemTypeName(name, out itemTypeName)) |
| { |
| return GenericNullableType.MakeGenericType(FindType(itemTypeName)); |
| } |
| |
| // if entry assembly different from current assembly, try entry assembly first |
| if (diffAssembly) |
| { |
| type = entryAssembly.GetType(name); |
| } |
| |
| // try current assembly and mscorlib |
| if (type == null) |
| { |
| type = Type.GetType(name); |
| } |
| |
| // type is still not found, need to loop through all loaded assemblies |
| if (type == null) |
| { |
| foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) |
| { |
| // Loading all types from all assemblies could fail for a variety of |
| // non -fatal reasons. If we fail to load types from an assembly, continue. |
| try |
| { |
| // Change the search to look for Types by both NAME and FULLNAME |
| foreach (Type t in assembly.GetTypes()) |
| { |
| if (name == t.Name || name == t.FullName || CodeGenUtil.Instance.UnMangle(name) == t.FullName) |
| { |
| type = t; |
| break; |
| } |
| } |
| } |
| catch |
| { |
| continue; |
| } |
| } |
| } |
| |
| return type |
| ?? throw new AvroException($"Unable to find type '{name}' in all loaded " + |
| $"assemblies"); |
| }); |
| } |
| |
| private bool TryGetIListItemTypeName(string name, out string itemTypeName) |
| { |
| const string listPrefix = "IList<"; |
| const string fullListPrefix = "System.Collections.Generic.IList<"; |
| |
| if (!name.EndsWith(">", StringComparison.Ordinal)) |
| { |
| itemTypeName = null; |
| return false; |
| } |
| |
| if (name.StartsWith(fullListPrefix, StringComparison.Ordinal)) |
| { |
| itemTypeName = name.Substring( |
| fullListPrefix.Length, name.Length - fullListPrefix.Length - 1); |
| return true; |
| } |
| |
| if (name.StartsWith(listPrefix, StringComparison.Ordinal)) |
| { |
| itemTypeName = name.Substring( |
| listPrefix.Length, name.Length - listPrefix.Length - 1); |
| return true; |
| } |
| |
| itemTypeName = null; |
| return false; |
| } |
| |
| private bool TryGetNullableItemTypeName(string name, out string itemTypeName) |
| { |
| const string nullablePrefix = "Nullable<"; |
| const string fullNullablePrefix = "System.Nullable<"; |
| |
| if (name.StartsWith(fullNullablePrefix, StringComparison.Ordinal)) |
| { |
| itemTypeName = name.Substring( |
| fullNullablePrefix.Length, name.Length - fullNullablePrefix.Length - 1); |
| return true; |
| } |
| |
| if (name.StartsWith(nullablePrefix, StringComparison.Ordinal)) |
| { |
| itemTypeName = name.Substring( |
| nullablePrefix.Length, name.Length - nullablePrefix.Length - 1); |
| return true; |
| } |
| |
| itemTypeName = null; |
| return false; |
| } |
| |
| /// <summary> |
| /// Gets the type for the specified schema |
| /// </summary> |
| /// <param name="schema"></param> |
| /// <returns></returns> |
| /// <exception cref="AvroException"> |
| /// No type found matching the given name. |
| /// </exception> |
| public Type GetType(Schema schema) |
| { |
| switch(schema.Tag) { |
| case Schema.Type.Null: |
| break; |
| case Schema.Type.Boolean: |
| return typeof(bool); |
| case Schema.Type.Int: |
| return typeof(int); |
| case Schema.Type.Long: |
| return typeof(long); |
| case Schema.Type.Float: |
| return typeof(float); |
| case Schema.Type.Double: |
| return typeof(double); |
| case Schema.Type.Bytes: |
| return typeof(byte[]); |
| case Schema.Type.String: |
| return typeof(string); |
| case Schema.Type.Union: |
| { |
| if (schema is UnionSchema unSchema && unSchema.Count == 2) |
| { |
| Schema s1 = unSchema.Schemas[0]; |
| Schema s2 = unSchema.Schemas[1]; |
| |
| // Nullable ? |
| Type itemType = null; |
| if (s1.Tag == Schema.Type.Null) |
| { |
| itemType = GetType(s2); |
| } |
| else if (s2.Tag == Schema.Type.Null) |
| { |
| itemType = GetType(s1); |
| } |
| |
| if (itemType != null) |
| { |
| if (itemType.IsValueType && !itemType.IsEnum) |
| { |
| try |
| { |
| return GenericNullableType.MakeGenericType(itemType); |
| } |
| catch |
| { |
| } |
| } |
| |
| return itemType; |
| } |
| } |
| |
| return typeof(object); |
| } |
| case Schema.Type.Array: |
| { |
| ArraySchema arrSchema = schema as ArraySchema; |
| Type itemSchema = GetType(arrSchema.ItemSchema); |
| |
| return GenericListType.MakeGenericType(itemSchema); |
| } |
| case Schema.Type.Map: |
| { |
| MapSchema mapSchema = schema as MapSchema; |
| Type itemSchema = GetType(mapSchema.ValueSchema); |
| |
| return GenericMapType.MakeGenericType(typeof(string), itemSchema ); |
| } |
| case Schema.Type.Enumeration: |
| case Schema.Type.Record: |
| case Schema.Type.Fixed: |
| case Schema.Type.Error: |
| { |
| // Should all be named types |
| if (schema is NamedSchema named) |
| { |
| return FindType(named.Fullname); |
| } |
| |
| break; |
| } |
| } |
| |
| // Fallback |
| return FindType(schema.Name); |
| } |
| |
| /// <summary> |
| /// Gets the type of the specified type name |
| /// </summary> |
| /// <param name="name">name of the object to get type of</param> |
| /// <param name="schemaType">schema type for the object</param> |
| /// <returns>Type</returns> |
| /// <exception cref="AvroException"> |
| /// No type found matching the given name. |
| /// </exception> |
| public Type GetType(string name, Schema.Type schemaType) |
| { |
| Type type = FindType(name); |
| |
| if (schemaType == Schema.Type.Map) |
| { |
| type = GenericMapType.MakeGenericType(typeof(string), type); |
| } |
| else if (schemaType == Schema.Type.Array) |
| { |
| type = GenericListType.MakeGenericType(type); |
| } |
| |
| return type; |
| } |
| |
| /// <summary> |
| /// Creates new instance of the given type |
| /// </summary> |
| /// <param name="name">fully qualified name of the type</param> |
| /// <param name="schemaType">type of schema</param> |
| /// <returns>new object of the given type</returns> |
| /// <exception cref="AvroException"> |
| /// No type found matching the given name. |
| /// </exception> |
| public object New(string name, Schema.Type schemaType) |
| { |
| return Activator.CreateInstance(GetType(name, schemaType)); |
| } |
| } |
| } |