/**
* 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
*
* http://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.Generic;
using System.Text;
using System.Reflection;
using System.Reflection.Emit;
namespace Avro.Specific
{
public sealed class ObjectCreator
{
private static readonly ObjectCreator instance = new ObjectCreator();
public static ObjectCreator Instance { get { return instance; } }
///
/// Static generic dictionary type used for creating new dictionary instances
///
private Type GenericMapType = typeof(Dictionary<,>);
///
/// Static generic list type used for creating new array instances
///
private Type GenericListType = typeof(List<>);
///
/// Static generic nullable type used for creating new nullable instances
///
private Type GenericNullableType = typeof(Nullable<>);
private readonly Assembly execAssembly;
private readonly Assembly entryAssembly;
private readonly bool diffAssembly;
public delegate object CtorDelegate();
private Type ctorType = typeof(CtorDelegate);
Dictionary ctors;
private ObjectCreator()
{
execAssembly = System.Reflection.Assembly.GetExecutingAssembly();
entryAssembly = System.Reflection.Assembly.GetEntryAssembly();
if (entryAssembly != null && execAssembly != entryAssembly) // entryAssembly returns null when running from NUnit
diffAssembly = true;
GenericMapType = typeof(Dictionary<,>);
GenericListType = typeof(List<>);
ctors = new Dictionary();
}
public struct NameCtorKey : IEquatable
{
public string name { get; private set; }
public Schema.Type type { get; private set; }
public NameCtorKey(string value1, Schema.Type value2)
: this()
{
name = value1;
type = value2;
}
public bool Equals(NameCtorKey other)
{
return Equals(other.name, name) && other.type == type;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
return false;
if (obj.GetType() != typeof(NameCtorKey))
return false;
return Equals((NameCtorKey)obj);
}
public override int GetHashCode()
{
unchecked
{
return ((name != null ? name.GetHashCode() : 0) * 397) ^ type.GetHashCode();
}
}
public static bool operator ==(NameCtorKey left, NameCtorKey right)
{
return left.Equals(right);
}
public static bool operator !=(NameCtorKey left, NameCtorKey right)
{
return !left.Equals(right);
}
}
///
/// Find the type with the given name
///
/// the object type to locate
/// whether or not to throw an error if the type wasn't found
/// the object type, or null if not found
private Type FindType(string name,bool throwError)
{
Type type;
// Modify provided type to ensure it can be discovered.
// This is mainly for Generics, and Nullables.
name = name.Replace("Nullable", "Nullable`1");
name = name.Replace("IList", "System.Collections.Generic.IList`1");
name = name.Replace("<", "[");
name = name.Replace(">", "]");
if (diffAssembly)
{
// entry assembly different from current assembly, try entry assembly first
type = entryAssembly.GetType(name);
if (type == null) // now try current assembly and mscorlib
type = Type.GetType(name);
}
else
type = Type.GetType(name);
Type[] types;
if (type == null) // type is still not found, need to loop through all loaded assemblies
{
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
{
// Fix for Mono 3.0.10
if (assembly.FullName.StartsWith("MonoDevelop.NUnit"))
continue;
types = assembly.GetTypes();
// Change the search to look for Types by both NAME and FULLNAME
foreach (Type t in types)
{
if (name == t.Name || name == t.FullName) type = t;
}
if (type != null)
break;
}
}
if (null == type && throwError)
{
throw new AvroException("Unable to find type " + name + " in all loaded assemblies");
}
return type;
}
///
/// Gets the type for the specified schema
///
///
///
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:
{
UnionSchema unSchema = schema as UnionSchema;
if (null != 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 (null != itemType )
{
if (itemType.IsValueType && !itemType.IsEnum)
{
try
{
return GenericNullableType.MakeGenericType(new [] {itemType});
}
catch (Exception) { }
}
return itemType;
}
}
return typeof(object);
}
case Schema.Type.Array: {
ArraySchema arrSchema = schema as ArraySchema;
Type itemSchema = GetType(arrSchema.ItemSchema);
return GenericListType.MakeGenericType(new [] {itemSchema}); }
case Schema.Type.Map: {
MapSchema mapSchema = schema as MapSchema;
Type itemSchema = GetType(mapSchema.ValueSchema);
return GenericMapType.MakeGenericType(new [] { typeof(string), itemSchema }); }
case Schema.Type.Enumeration:
case Schema.Type.Record:
case Schema.Type.Fixed:
case Schema.Type.Error: {
// Should all be named types
var named = schema as NamedSchema;
if(null!=named) {
return FindType(named.Fullname,true);
}
break; }
}
// Fallback
return FindType(schema.Name,true);
}
///
/// Gets the type of the specified type name
///
/// name of the object to get type of
/// schema type for the object
/// Type
public Type GetType(string name, Schema.Type schemaType)
{
Type type = FindType(name, true);
if (schemaType == Schema.Type.Map)
{
type = GenericMapType.MakeGenericType(new[] { typeof(string), type });
}
else if (schemaType == Schema.Type.Array)
{
type = GenericListType.MakeGenericType(new [] {type});
}
return type;
}
///
/// Gets the default constructor for the specified type
///
/// name of object for the type
/// schema type for the object
/// type of the object
/// Default constructor for the type
public CtorDelegate GetConstructor(string name, Schema.Type schemaType, Type type)
{
ConstructorInfo ctorInfo = type.GetConstructor(Type.EmptyTypes);
if (ctorInfo == null)
throw new AvroException("Class " + name + " has no default constructor");
DynamicMethod dynMethod = new DynamicMethod("DM$OBJ_FACTORY_" + name, typeof(object), null, type, true);
ILGenerator ilGen = dynMethod.GetILGenerator();
ilGen.Emit(OpCodes.Nop);
ilGen.Emit(OpCodes.Newobj, ctorInfo);
ilGen.Emit(OpCodes.Ret);
return (CtorDelegate)dynMethod.CreateDelegate(ctorType);
}
///
/// Creates new instance of the given type
///
/// fully qualified name of the type
/// type of schema
/// new object of the given type
public object New(string name, Schema.Type schemaType)
{
NameCtorKey key = new NameCtorKey(name, schemaType);
CtorDelegate ctor;
lock(ctors)
{
if (!ctors.TryGetValue(key, out ctor))
{
Type type = GetType(name, schemaType);
ctor = GetConstructor(name, schemaType, type);
ctors.Add(key, ctor);
}
}
return ctor();
}
}
}