asternet/Asterisk.2013/Asterisk.NET/Helper.cs

884 lines
32 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using AsterNET.Manager;
using AsterNET.Manager.Event;
using AsterNET.Manager.Response;
namespace AsterNET
{
internal class Helper
{
private static CultureInfo defaultCulture;
#if LOGGER
private static readonly Logger logger = Logger.Instance();
#endif
#region CultureInfo
internal static CultureInfo CultureInfo
{
get
{
if (defaultCulture == null)
defaultCulture = CultureInfo.GetCultureInfo("en");
return defaultCulture;
}
}
#endregion
#region ToHexString(sbyte[])
/// <summary> The hex digits used to build a hex string representation of a byte array.</summary>
internal static readonly char[] hexChar =
{
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f'
};
/// <summary>
/// Converts a byte array to a hex string representing it. The hex digits are lower case.
/// </summary>
/// <param name="b">the byte array to convert</param>
/// <returns> the hex representation of b</returns>
internal static string ToHexString(sbyte[] b)
{
var sb = new StringBuilder(b.Length*2);
for (int i = 0; i < b.Length; i++)
{
sb.Append(hexChar[URShift((b[i] & 0xf0), 4)]);
sb.Append(hexChar[b[i] & 0x0f]);
}
return sb.ToString();
}
#endregion
#region GetInternalActionId(actionId)
internal static string GetInternalActionId(string actionId)
{
if (string.IsNullOrEmpty(actionId))
return string.Empty;
int delimiterIndex = actionId.IndexOf(Common.INTERNAL_ACTION_ID_DELIMITER);
if (delimiterIndex > 0)
return actionId.Substring(0, delimiterIndex).Trim();
return string.Empty;
}
#endregion
#region StripInternalActionId(actionId)
internal static string StripInternalActionId(string actionId)
{
if (string.IsNullOrEmpty(actionId))
return string.Empty;
int delimiterIndex = actionId.IndexOf(Common.INTERNAL_ACTION_ID_DELIMITER);
if (delimiterIndex > 0)
{
if (actionId.Length > delimiterIndex + 1)
return actionId.Substring(delimiterIndex + 1).Trim();
return actionId.Substring(0, delimiterIndex).Trim();
}
return string.Empty;
}
#endregion
#region IsTrue(string)
/// <summary>
/// Checks if a String represents true or false according to Asterisk's logic.<br />
/// The original implementation is util.c is as follows:
/// </summary>
/// <param name="s">the String to check for true.</param>
/// <returns>
/// true if s represents true,
/// false otherwise.
/// </returns>
internal static bool IsTrue(string s)
{
if (s == null || s.Length == 0)
return false;
string sx = s.ToLower(CultureInfo);
if (sx == "yes" || sx == "true" || sx == "y" || sx == "t" || sx == "1" || sx == "on")
return true;
return false;
}
#endregion
#region URShift(...)
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
internal static int URShift(int number, int bits)
{
if (number >= 0)
return number >> bits;
return (number >> bits) + (2 << ~bits);
}
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
internal static int URShift(int number, long bits)
{
return URShift(number, (int) bits);
}
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
internal static long URShift(long number, int bits)
{
if (number >= 0)
return number >> bits;
return (number >> bits) + (2L << ~bits);
}
/// <summary>
/// Performs an unsigned bitwise right shift with the specified number
/// </summary>
/// <param name="number">Number to operate on</param>
/// <param name="bits">Ammount of bits to shift</param>
/// <returns>The resulting number from the shift operation</returns>
internal static long URShift(long number, long bits)
{
return URShift(number, (int) bits);
}
#endregion
#region ToArray(ICollection c, object[] objects)
/// <summary>
/// Obtains an array containing all the elements of the collection.
/// </summary>
/// <param name="objects">The array into which the elements of the collection will be stored.</param>
/// <returns>The array containing all the elements of the collection.</returns>
internal static object[] ToArray(ICollection c, object[] objects)
{
int index = 0;
Type type = objects.GetType().GetElementType();
var objs = (object[]) Array.CreateInstance(type, c.Count);
IEnumerator e = c.GetEnumerator();
while (e.MoveNext())
objs[index++] = e.Current;
//If objects is smaller than c then do not return the new array in the parameter
if (objects.Length >= c.Count)
objs.CopyTo(objects, 0);
return objs;
}
#endregion
#region ParseVariables(Dictionary<string, string> dictionary, string variables, char[] delim)
/// <summary>
/// Parse variable(s) string to dictionary.
/// </summary>
/// <param name="dictionary"></param>
/// <param name="variables">variable(a) string</param>
/// <param name="delim">variable pairs delimiter</param>
/// <returns></returns>
internal static Dictionary<string, string> ParseVariables(Dictionary<string, string> dictionary,
string variables, char[] delim)
{
if (dictionary == null)
dictionary = new Dictionary<string, string>();
else
dictionary.Clear();
if (string.IsNullOrEmpty(variables))
return dictionary;
string[] vars = variables.Split(delim);
int idx;
string vname, vval;
foreach (var var in vars)
{
idx = var.IndexOf('=');
if (idx > 0)
{
vname = var.Substring(0, idx);
vval = var.Substring(idx + 1);
}
else
{
vname = var;
vval = string.Empty;
}
dictionary.Add(vname, vval);
}
return dictionary;
}
#endregion
#region JoinVariables(IDictionary dictionary, string delim)
/// <summary>
/// Join variables dictionary to string.
/// </summary>
/// <param name="dictionary"></param>
/// <param name="delim"></param>
/// <returns></returns>
internal static string JoinVariables(IDictionary dictionary, char[] delim, string delimKeyValue)
{
return JoinVariables(dictionary, new string(delim), delimKeyValue);
}
internal static string JoinVariables(IDictionary dictionary, string delim, string delimKeyValue)
{
if (dictionary == null)
return string.Empty;
var sb = new StringBuilder();
foreach (DictionaryEntry var in dictionary)
{
if (sb.Length > 0)
sb.Append(delim);
sb.Append(string.Concat(var.Key, delimKeyValue, var.Value));
}
return sb.ToString();
}
#endregion
#region GetMillisecondsFrom(DateTime start)
internal static long GetMillisecondsFrom(DateTime start)
{
TimeSpan ts = DateTime.Now - start;
return (long) ts.TotalMilliseconds;
}
#endregion
#region ParseString(string val)
internal static object ParseString(string val)
{
if (val == "none")
return string.Empty;
return val;
}
#endregion
#region GetGetters(class)
/// <summary>
/// Returns a Map of getter methods of the given class.<br />
/// The key of the map contains the name of the attribute that can be accessed by the getter, the
/// value the getter itself . A method is considered a getter if its name starts with "get",
/// it is declared internal and takes no arguments.
/// </summary>
/// <param name="clazz">the class to return the getters for</param>
/// <returns> a Map of attributes and their accessor methods (getters)</returns>
internal static Dictionary<string, MethodInfo> GetGetters(Type clazz)
{
string name;
string methodName;
MethodInfo method;
var accessors = new Dictionary<string, MethodInfo>();
MethodInfo[] methods = clazz.GetMethods();
for (int i = 0; i < methods.Length; i++)
{
method = methods[i];
methodName = method.Name;
// skip not "get..." methods and skip methods with != 0 parameters
if (!methodName.StartsWith("get_") || method.GetParameters().Length != 0)
continue;
name = methodName.Substring(4);
if (name.Length == 0)
continue;
accessors[name] = method;
}
return accessors;
}
#endregion
#region GetSetters(Type clazz)
/// <summary>
/// Returns a Map of setter methods of the given class.<br />
/// The key of the map contains the name of the attribute that can be accessed by the setter, the
/// value the setter itself. A method is considered a setter if its name starts with "set",
/// it is declared internal and takes no arguments.
/// </summary>
/// <param name="clazz">the class to return the setters for</param>
/// <returns> a Map of attributes and their accessor methods (setters)</returns>
internal static IDictionary GetSetters(Type clazz)
{
IDictionary accessors = new Hashtable();
MethodInfo[] methods = clazz.GetMethods();
string name;
string methodName;
MethodInfo method;
for (int i = 0; i < methods.Length; i++)
{
method = methods[i];
methodName = method.Name;
// skip not "set..." methods and skip methods with != 1 parameters
if (!methodName.StartsWith("set_") || method.GetParameters().Length != 1)
continue;
name = methodName.Substring("set_".Length).ToLower(CultureInfo);
if (name.Length == 0) continue;
accessors[name] = method;
}
return accessors;
}
#endregion
#region ToString(object obj)
/// <summary>
/// Convert object with all properties to string
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
internal static string ToString(object obj)
{
object value;
var sb = new StringBuilder(obj.GetType().Name, 1024);
sb.Append(" {");
string strValue;
IDictionary getters = GetGetters(obj.GetType());
bool notFirst = false;
var arrays = new List<MethodInfo>();
// First step - all values properties (not a list)
foreach (string name in getters.Keys)
{
var getter = (MethodInfo) getters[name];
Type propType = getter.ReturnType;
if (propType == typeof (object))
continue;
if (
!(propType == typeof (string) || propType == typeof (bool) || propType == typeof (double) ||
propType == typeof (DateTime) || propType == typeof (int) || propType == typeof (long)))
{
string propTypeName = propType.Name;
if (propTypeName.StartsWith("Dictionary") || propTypeName.StartsWith("List"))
{
arrays.Add(getter);
}
continue;
}
try
{
value = getter.Invoke(obj, new object[] {});
}
catch
{
continue;
}
if (value == null)
continue;
if (value is string)
{
strValue = (string) value;
if (strValue.Length == 0)
continue;
}
else if (value is bool)
{
strValue = ((bool) value ? "true" : "false");
}
else if (value is double)
{
var d = (double) value;
if (d == 0.0)
continue;
strValue = d.ToString();
}
else if (value is DateTime)
{
var dt = (DateTime) value;
if (dt == DateTime.MinValue)
continue;
strValue = dt.ToLongTimeString();
}
else if (value is int)
{
var i = (int) value;
if (i == 0)
continue;
strValue = i.ToString();
}
else if (value is long)
{
var l = (long) value;
if (l == 0)
continue;
strValue = l.ToString();
}
else
strValue = value.ToString();
if (notFirst)
sb.Append("; ");
notFirst = true;
sb.Append(string.Concat(getter.Name.Substring(4), ":", strValue));
}
// Second step - all lists
foreach (var getter in arrays)
{
value = null;
try
{
value = getter.Invoke(obj, new object[] {});
}
catch
{
continue;
}
if (value == null)
continue;
#region List
IList list;
if (value is IList && (list = (IList) value).Count > 0)
{
if (notFirst)
sb.Append("; ");
notFirst = true;
sb.Append(getter.Name.Substring(4));
sb.Append(":[");
bool notFirst2 = false;
foreach (var o in list)
{
if (notFirst2)
sb.Append("; ");
notFirst2 = true;
sb.Append(o);
}
sb.Append("]");
}
#endregion
#region IDictionary
if (value is IDictionary && ((IDictionary) value).Count > 0)
{
if (notFirst)
sb.Append("; ");
notFirst = true;
sb.Append(getter.Name.Substring(4));
sb.Append(":[");
bool notFirst2 = false;
foreach (var key in ((IDictionary) value).Keys)
{
object o = ((IDictionary) value)[key];
if (notFirst2)
sb.Append("; ");
notFirst2 = true;
sb.Append(string.Concat(key, ":", o));
}
sb.Append("]");
}
#endregion
}
sb.Append("}");
return sb.ToString();
}
#endregion
#region SetAttributes(object evt, IDictionary attributes)
internal static void SetAttributes(IParseSupport o, Dictionary<string, string> attributes)
{
Type dataType;
object val;
// Preparse attributes
attributes = o.ParseSpecial(attributes);
IDictionary setters = GetSetters(o.GetType());
MethodInfo setter;
foreach (var name in attributes.Keys)
{
if (name == "event")
continue;
if (name == "source")
setter = (MethodInfo) setters["src"];
else
setter = (MethodInfo) setters[stripIllegalCharacters(name)];
if (setter == null)
{
// No setter found to key, try general parser
if (!o.Parse(name, attributes[name]))
{
#if LOGGER
logger.Error("Unable to set property '" + name + "' on " + o.GetType() + ": no setter");
#endif
throw new ManagerException("Parse error key '" + name + "' on " + o.GetType());
}
}
else
{
dataType = (setter.GetParameters()[0]).ParameterType;
if (dataType == typeof (bool))
val = IsTrue(attributes[name]);
else if (dataType == typeof (string))
val = ParseString(attributes[name]);
else if (dataType == typeof (Int32))
{
Int32 v = 0;
Int32.TryParse(attributes[name], out v);
val = v;
}
else if (dataType == typeof (Int64))
{
Int64 v = 0;
Int64.TryParse(attributes[name], out v);
val = v;
}
else if (dataType == typeof (double))
{
Double v = 0.0;
Double.TryParse(attributes[name], NumberStyles.AllowDecimalPoint, Common.CultureInfoEn, out v);
val = v;
}
else if (dataType == typeof (decimal))
{
Decimal v = 0;
Decimal.TryParse(attributes[name], NumberStyles.AllowDecimalPoint, Common.CultureInfoEn, out v);
val = v;
}
else if (dataType.IsEnum)
{
try
{
val = Convert.ChangeType(Enum.Parse(dataType, attributes[name], true), dataType);
}
catch (Exception ex)
{
#if LOGGER
logger.Error(
"Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " +
o.GetType() + " to required enum type " + dataType, ex);
continue;
#else
throw new ManagerException("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required enum type " + dataType, ex);
#endif
}
}
else
{
try
{
ConstructorInfo constructor = dataType.GetConstructor(new[] {typeof (string)});
val = constructor.Invoke(new object[] {attributes[name]});
}
catch (Exception ex)
{
#if LOGGER
logger.Error(
"Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " +
o.GetType() + " to required type " + dataType, ex);
continue;
#else
throw new ManagerException("Unable to convert value '" + attributes[name] + "' of property '" + name + "' on " + o.GetType() + " to required type " + dataType, ex);
#endif
}
}
try
{
setter.Invoke(o, new[] {val});
}
catch (Exception ex)
{
#if LOGGER
logger.Error("Unable to set property '" + name + "' on " + o.GetType(), ex);
#else
throw new ManagerException("Unable to set property '" + name + "' on " + o.GetType(), ex);
#endif
}
}
}
}
#endregion
#region AddKeyValue(IDictionary list, string line)
internal static void AddKeyValue(IDictionary list, string line)
{
int delimiterIndex = line.IndexOf(":");
if (delimiterIndex > 0 && line.Length > delimiterIndex + 1)
{
string name = line.Substring(0, delimiterIndex).ToLower(CultureInfo).Trim();
string val = line.Substring(delimiterIndex + 1).Trim();
if (val == "<null>")
list[name] = null;
else
list[name] = val;
}
}
#endregion
#region stripIllegalCharacters(string s)
/// <summary>
/// Strips all illegal charaters from the given lower case string.
/// </summary>
/// <param name="s">the original string</param>
/// <returns>the string with all illegal characters stripped</returns>
private static string stripIllegalCharacters(string s)
{
char c;
bool needsStrip = false;
if (string.IsNullOrEmpty(s))
return null;
for (int i = 0; i < s.Length; i++)
{
c = s[i];
if (c >= '0' && c <= '9')
continue;
if (c >= 'a' && c <= 'z')
continue;
if (c >= 'A' && c <= 'Z')
continue;
needsStrip = true;
break;
}
if (!needsStrip)
return s;
var sb = new StringBuilder(s.Length);
for (int i = 0; i < s.Length; i++)
{
c = s[i];
if (c >= '0' && c <= '9')
sb.Append(c);
else if (c >= 'a' && c <= 'z')
sb.Append(c);
else if (c >= 'A' && c <= 'Z')
sb.Append(c);
}
return sb.ToString();
}
#endregion
#region BuildResponse(IDictionary attributes)
/// <summary>
/// Constructs an instance of ManagerResponse based on a map of attributes.
/// </summary>
/// <param name="attributes">the attributes and their values. The keys of this map must be all lower case.</param>
/// <returns>the response with the given attributes.</returns>
internal static ManagerResponse BuildResponse(Dictionary<string, string> attributes)
{
ManagerResponse response;
string responseType = attributes["response"].ToLower(CultureInfo);
// Determine type
if (responseType == "error")
response = new ManagerError();
else if (attributes.ContainsKey("challenge"))
response = new ChallengeResponse();
else if (attributes.ContainsKey("mailbox") && attributes.ContainsKey("waiting"))
response = new MailboxStatusResponse();
else if (attributes.ContainsKey("mailbox") && attributes.ContainsKey("newmessages") &&
attributes.ContainsKey("oldmessages"))
response = new MailboxCountResponse();
else if (attributes.ContainsKey("exten") && attributes.ContainsKey("context") &&
attributes.ContainsKey("hint") && attributes.ContainsKey("status"))
response = new ExtensionStateResponse();
else
response = new ManagerResponse();
SetAttributes(response, attributes);
return response;
}
#endregion
#region BuildEvent(Hashtable list, object source, IDictionary attributes)
/// <summary>
/// Builds the event based on the given map of attributes and the registered event classes.
/// </summary>
/// <param name="source">source attribute for the event</param>
/// <param name="attributes">map containing event attributes</param>
/// <returns>a concrete instance of ManagerEvent or null if no event class was registered for the event type.</returns>
internal static ManagerEvent BuildEvent(IDictionary<int, ConstructorInfo> list, ManagerConnection source,
Dictionary<string, string> attributes)
{
ManagerEvent e;
string eventType;
ConstructorInfo constructor = null;
int hash, hashEvent;
eventType = attributes["event"].ToLower(CultureInfo);
// Remove Event tail from event name (ex. JabberEvent)
if (eventType.EndsWith("event"))
eventType = eventType.Substring(0, eventType.Length - 5);
hashEvent = eventType.GetHashCode();
if (eventType == "user")
{
string userevent = attributes["userevent"].ToLower(CultureInfo);
hash = string.Concat(eventType, userevent).GetHashCode();
if (list.ContainsKey(hash))
constructor = list[hash];
else
constructor = list[hashEvent];
}
else if (list.ContainsKey(hashEvent))
constructor = list[hashEvent];
if (constructor == null)
e = new UnknownEvent(source);
else
{
try
{
e = (ManagerEvent) constructor.Invoke(new object[] {source});
}
catch (Exception ex)
{
#if LOGGER
logger.Error("Unable to create new instance of " + eventType, ex);
return null;
#else
throw ex;
#endif
}
}
SetAttributes(e, attributes);
// ResponseEvents are sent in response to a ManagerAction if the
// response contains lots of data. They include the actionId of
// the corresponding ManagerAction.
if (e is ResponseEvent)
{
var responseEvent = (ResponseEvent) e;
string actionId = responseEvent.ActionId;
if (actionId != null)
{
responseEvent.ActionId = StripInternalActionId(actionId);
responseEvent.InternalActionId = GetInternalActionId(actionId);
}
}
return e;
}
#endregion
#region RegisterBuiltinEventClasses(Hashtable list)
/// <summary>
/// Register buildin Event classes
/// </summary>
/// <param name="list"></param>
internal static void RegisterBuiltinEventClasses(Dictionary<int, ConstructorInfo> list)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Type manager = typeof (ManagerEvent);
foreach (var type in assembly.GetTypes())
if (type.IsPublic && !type.IsAbstract && manager.IsAssignableFrom(type))
RegisterEventClass(list, type);
}
#endregion
#region RegisterEventClass(Dictionary<string, ConstructorInfo> list, Type clazz)
internal static void RegisterEventClass(Dictionary<int, ConstructorInfo> list, Type clazz)
{
// Ignore all abstract classes
// Class not derived from ManagerEvent
if (clazz.IsAbstract || !typeof (ManagerEvent).IsAssignableFrom(clazz))
return;
string eventType = clazz.Name.ToLower(CultureInfo);
// Remove "event" at the end (if presents)
if (eventType.EndsWith("event"))
eventType = eventType.Substring(0, eventType.Length - 5);
// If assignable from UserEvent and no "userevent" at the start - add "userevent" to beginning
if (typeof (UserEvent).IsAssignableFrom(clazz) && !eventType.StartsWith("user"))
eventType = "user" + eventType;
int hash = eventType.GetHashCode();
if (list.ContainsKey(hash))
return;
ConstructorInfo constructor = null;
try
{
constructor = clazz.GetConstructor(new[] {typeof (ManagerConnection)});
}
catch (MethodAccessException ex)
{
throw new ArgumentException("RegisterEventClass : " + clazz + " has no usable constructor.", ex);
}
if (constructor != null && constructor.IsPublic)
list.Add(hash, constructor);
else
throw new ArgumentException("RegisterEventClass : " + clazz + " has no public default constructor");
}
#endregion
#region RegisterEventHandler(Dictionary<int, int> list, int index, Type eventType)
internal static void RegisterEventHandler(Dictionary<int, int> list, int index, Type eventType)
{
int eventHash = eventType.Name.GetHashCode();
if (list.ContainsKey(eventHash))
throw new ArgumentException("Event class already registered : " + eventType.Name);
list.Add(eventHash, index);
}
#endregion
}
}