#region Apache License // // 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. // #endregion using System; using System.Collections; using System.Globalization; using System.Reflection; using System.Text; using log4net.Core; using log4net.Util.TypeConverters; namespace log4net.Util { /// <summary> /// A convenience class to convert property values to specific types. /// </summary> /// <remarks> /// <para> /// Utility functions for converting types and parsing values. /// </para> /// </remarks> /// <author>Nicko Cadell</author> /// <author>Gert Driesen</author> public sealed class OptionConverter { #region Private Instance Constructors /// <summary> /// Initializes a new instance of the <see cref="OptionConverter" /> class. /// </summary> /// <remarks> /// <para> /// Uses a private access modifier to prevent instantiation of this class. /// </para> /// </remarks> private OptionConverter() { } #endregion Private Instance Constructors #region Public Static Methods // /// <summary> // /// Concatenates two string arrays. // /// </summary> // /// <param name="l">Left array.</param> // /// <param name="r">Right array.</param> // /// <returns>Array containing both left and right arrays.</returns> // public static string[] ConcatenateArrays(string[] l, string[] r) // { // return (string[])ConcatenateArrays(l, r); // } // /// <summary> // /// Concatenates two arrays. // /// </summary> // /// <param name="l">Left array</param> // /// <param name="r">Right array</param> // /// <returns>Array containing both left and right arrays.</returns> // public static Array ConcatenateArrays(Array l, Array r) // { // if (l == null) // { // throw new ArgumentNullException("l"); // } // if (r == null) // { // throw new ArgumentNullException("r"); // } // // int len = l.Length + r.Length; // Array a = Array.CreateInstance(l.GetType(), len); // // Array.Copy(l, 0, a, 0, l.Length); // Array.Copy(r, 0, a, l.Length, r.Length); // // return a; // } // /// <summary> // /// Converts string escape characters back to their correct values. // /// </summary> // /// <param name="s">String to convert.</param> // /// <returns>Converted result.</returns> // public static string ConvertSpecialChars(string s) // { // if (s == null) // { // throw new ArgumentNullException("s"); // } // char c; // int len = s.Length; // StringBuilder buf = new StringBuilder(len); // // int i = 0; // while(i < len) // { // c = s[i++]; // if (c == '\\') // { // c = s[i++]; // if (c == 'n') c = '\n'; // else if (c == 'r') c = '\r'; // else if (c == 't') c = '\t'; // else if (c == 'f') c = '\f'; // else if (c == '\b') c = '\b'; // else if (c == '\"') c = '\"'; // else if (c == '\'') c = '\''; // else if (c == '\\') c = '\\'; // } // buf.Append(c); // } // return buf.ToString(); // } /// <summary> /// Converts a string to a <see cref="bool" /> value. /// </summary> /// <param name="argValue">String to convert.</param> /// <param name="defaultValue">The default value.</param> /// <returns>The <see cref="bool" /> value of <paramref name="argValue" />.</returns> /// <remarks> /// <para> /// If <paramref name="argValue"/> is "true", then <c>true</c> is returned. /// If <paramref name="argValue"/> is "false", then <c>false</c> is returned. /// Otherwise, <paramref name="defaultValue"/> is returned. /// </para> /// </remarks> public static bool ToBoolean(string argValue, bool defaultValue) { if (argValue != null && argValue.Length > 0) { try { return bool.Parse(argValue); } catch(Exception e) { LogLog.Error(declaringType, "[" + argValue + "] is not in proper bool form.", e); } } return defaultValue; } // /// <summary> // /// Converts a string to an integer. // /// </summary> // /// <param name="argValue">String to convert.</param> // /// <param name="defaultValue">The default value.</param> // /// <returns>The <see cref="int" /> value of <paramref name="argValue" />.</returns> // /// <remarks> // /// <para> // /// <paramref name="defaultValue"/> is returned when <paramref name="argValue"/> // /// cannot be converted to a <see cref="int" /> value. // /// </para> // /// </remarks> // public static int ToInt(string argValue, int defaultValue) // { // if (argValue != null) // { // string s = argValue.Trim(); // try // { // return int.Parse(s, NumberFormatInfo.InvariantInfo); // } // catch (Exception e) // { // LogLog.Error(declaringType, "OptionConverter: [" + s + "] is not in proper int form.", e); // } // } // return defaultValue; // } /// <summary> /// Parses a file size into a number. /// </summary> /// <param name="argValue">String to parse.</param> /// <param name="defaultValue">The default value.</param> /// <returns>The <see cref="long" /> value of <paramref name="argValue" />.</returns> /// <remarks> /// <para> /// Parses a file size of the form: number[KB|MB|GB] into a /// long value. It is scaled with the appropriate multiplier. /// </para> /// <para> /// <paramref name="defaultValue"/> is returned when <paramref name="argValue"/> /// cannot be converted to a <see cref="long" /> value. /// </para> /// </remarks> public static long ToFileSize(string argValue, long defaultValue) { if (argValue == null) { return defaultValue; } string s = argValue.Trim().ToUpper(CultureInfo.InvariantCulture); long multiplier = 1; int index; if ((index = s.IndexOf("KB")) != -1) { multiplier = 1024; s = s.Substring(0, index); } else if ((index = s.IndexOf("MB")) != -1) { multiplier = 1024 * 1024; s = s.Substring(0, index); } else if ((index = s.IndexOf("GB")) != -1) { multiplier = 1024 * 1024 * 1024; s = s.Substring(0, index); } if (s != null) { // Try again to remove whitespace between the number and the size specifier s = s.Trim(); long longVal; if (SystemInfo.TryParse(s, out longVal)) { return longVal * multiplier; } else { LogLog.Error(declaringType, "OptionConverter: ["+ s +"] is not in the correct file size syntax."); } } return defaultValue; } /// <summary> /// Converts a string to an object. /// </summary> /// <param name="target">The target type to convert to.</param> /// <param name="txt">The string to convert to an object.</param> /// <returns> /// The object converted from a string or <c>null</c> when the /// conversion failed. /// </returns> /// <remarks> /// <para> /// Converts a string to an object. Uses the converter registry to try /// to convert the string value into the specified target type. /// </para> /// </remarks> public static object ConvertStringTo(Type target, string txt) { if (target == null) { throw new ArgumentNullException("target"); } // If we want a string we already have the correct type if (typeof(string) == target || typeof(object) == target) { return txt; } // First lets try to find a type converter IConvertFrom typeConverter = ConverterRegistry.GetConvertFrom(target); if (typeConverter != null && typeConverter.CanConvertFrom(typeof(string))) { // Found appropriate converter return typeConverter.ConvertFrom(txt); } else { #if NETSTANDARD1_3 if (target.GetTypeInfo().IsEnum) #else if (target.IsEnum) #endif { // Target type is an enum. // Use the Enum.Parse(EnumType, string) method to get the enum value return ParseEnum(target, txt, true); } else { // We essentially make a guess that to convert from a string // to an arbitrary type T there will be a static method defined on type T called Parse // that will take an argument of type string. i.e. T.Parse(string)->T we call this // method to convert the string to the type required by the property. System.Reflection.MethodInfo meth = target.GetMethod("Parse", new Type[] {typeof(string)}); if (meth != null) { // Call the Parse method #if NETSTANDARD1_3 return meth.Invoke(target, new[] { txt }); #else return meth.Invoke(null, BindingFlags.InvokeMethod, null, new object[] {txt}, CultureInfo.InvariantCulture); #endif } else { // No Parse() method found. } } } return null; } // /// <summary> // /// Looks up the <see cref="IConvertFrom"/> for the target type. // /// </summary> // /// <param name="target">The type to lookup the converter for.</param> // /// <returns>The converter for the specified type.</returns> // public static IConvertFrom GetTypeConverter(Type target) // { // IConvertFrom converter = ConverterRegistry.GetConverter(target); // if (converter == null) // { // throw new InvalidOperationException("No type converter defined for [" + target + "]"); // } // return converter; // } /// <summary> /// Checks if there is an appropriate type conversion from the source type to the target type. /// </summary> /// <param name="sourceType">The type to convert from.</param> /// <param name="targetType">The type to convert to.</param> /// <returns><c>true</c> if there is a conversion from the source type to the target type.</returns> /// <remarks> /// Checks if there is an appropriate type conversion from the source type to the target type. /// <para> /// </para> /// </remarks> public static bool CanConvertTypeTo(Type sourceType, Type targetType) { if (sourceType == null || targetType == null) { return false; } // Check if we can assign directly from the source type to the target type if (targetType.IsAssignableFrom(sourceType)) { return true; } // Look for a To converter IConvertTo tcSource = ConverterRegistry.GetConvertTo(sourceType, targetType); if (tcSource != null) { if (tcSource.CanConvertTo(targetType)) { return true; } } // Look for a From converter IConvertFrom tcTarget = ConverterRegistry.GetConvertFrom(targetType); if (tcTarget != null) { if (tcTarget.CanConvertFrom(sourceType)) { return true; } } return false; } /// <summary> /// Converts an object to the target type. /// </summary> /// <param name="sourceInstance">The object to convert to the target type.</param> /// <param name="targetType">The type to convert to.</param> /// <returns>The converted object.</returns> /// <remarks> /// <para> /// Converts an object to the target type. /// </para> /// </remarks> public static object ConvertTypeTo(object sourceInstance, Type targetType) { Type sourceType = sourceInstance.GetType(); // Check if we can assign directly from the source type to the target type if (targetType.IsAssignableFrom(sourceType)) { return sourceInstance; } // Look for a TO converter IConvertTo tcSource = ConverterRegistry.GetConvertTo(sourceType, targetType); if (tcSource != null) { if (tcSource.CanConvertTo(targetType)) { return tcSource.ConvertTo(sourceInstance, targetType); } } // Look for a FROM converter IConvertFrom tcTarget = ConverterRegistry.GetConvertFrom(targetType); if (tcTarget != null) { if (tcTarget.CanConvertFrom(sourceType)) { return tcTarget.ConvertFrom(sourceInstance); } } throw new ArgumentException("Cannot convert source object [" + sourceInstance.ToString() + "] to target type [" + targetType.Name + "]", "sourceInstance"); } // /// <summary> // /// Finds the value corresponding to <paramref name="key"/> in // /// <paramref name="props"/> and then perform variable substitution // /// on the found value. // /// </summary> // /// <param name="key">The key to lookup.</param> // /// <param name="props">The association to use for lookups.</param> // /// <returns>The substituted result.</returns> // public static string FindAndSubst(string key, System.Collections.IDictionary props) // { // if (props == null) // { // throw new ArgumentNullException("props"); // } // // string v = props[key] as string; // if (v == null) // { // return null; // } // // try // { // return SubstituteVariables(v, props); // } // catch(Exception e) // { // LogLog.Error(declaringType, "OptionConverter: Bad option value [" + v + "].", e); // return v; // } // } /// <summary> /// Instantiates an object given a class name. /// </summary> /// <param name="className">The fully qualified class name of the object to instantiate.</param> /// <param name="superClass">The class to which the new object should belong.</param> /// <param name="defaultValue">The object to return in case of non-fulfillment.</param> /// <returns> /// An instance of the <paramref name="className"/> or <paramref name="defaultValue"/> /// if the object could not be instantiated. /// </returns> /// <remarks> /// <para> /// Checks that the <paramref name="className"/> is a subclass of /// <paramref name="superClass"/>. If that test fails or the object could /// not be instantiated, then <paramref name="defaultValue"/> is returned. /// </para> /// </remarks> public static object InstantiateByClassName(string className, Type superClass, object defaultValue) { if (className != null) { try { #if NETSTANDARD1_3 Type classObj = SystemInfo.GetTypeFromString(superClass.GetTypeInfo().Assembly, className, true, true); #else Type classObj = SystemInfo.GetTypeFromString(className, true, true); #endif if (!superClass.IsAssignableFrom(classObj)) { LogLog.Error(declaringType, "OptionConverter: A [" + className + "] object is not assignable to a [" + superClass.FullName + "] variable."); return defaultValue; } return Activator.CreateInstance(classObj); } catch (Exception e) { LogLog.Error(declaringType, "Could not instantiate class [" + className + "].", e); } } return defaultValue; } /// <summary> /// Performs variable substitution in string <paramref name="value"/> from the /// values of keys found in <paramref name="props"/>. /// </summary> /// <param name="value">The string on which variable substitution is performed.</param> /// <param name="props">The dictionary to use to lookup variables.</param> /// <returns>The result of the substitutions.</returns> /// <remarks> /// <para> /// The variable substitution delimiters are <b>${</b> and <b>}</b>. /// </para> /// <para> /// For example, if props contains <c>key=value</c>, then the call /// </para> /// <para> /// <code lang="C#"> /// string s = OptionConverter.SubstituteVariables("Value of key is ${key}."); /// </code> /// </para> /// <para> /// will set the variable <c>s</c> to "Value of key is value.". /// </para> /// <para> /// If no value could be found for the specified key, then substitution /// defaults to an empty string. /// </para> /// <para> /// For example, if system properties contains no value for the key /// "nonExistentKey", then the call /// </para> /// <para> /// <code lang="C#"> /// string s = OptionConverter.SubstituteVariables("Value of nonExistentKey is [${nonExistentKey}]"); /// </code> /// </para> /// <para> /// will set <s>s</s> to "Value of nonExistentKey is []". /// </para> /// <para> /// An Exception is thrown if <paramref name="value"/> contains a start /// delimiter "${" which is not balanced by a stop delimiter "}". /// </para> /// </remarks> public static string SubstituteVariables(string value, System.Collections.IDictionary props) { StringBuilder buf = new StringBuilder(); int i = 0; int j, k; while(true) { j = value.IndexOf(DELIM_START, i); if (j == -1) { if (i == 0) { return value; } else { buf.Append(value.Substring(i, value.Length - i)); return buf.ToString(); } } else { buf.Append(value.Substring(i, j - i)); k = value.IndexOf(DELIM_STOP, j); if (k == -1) { throw new LogException("[" + value + "] has no closing brace. Opening brace at position [" + j + "]"); } else { j += DELIM_START_LEN; string key = value.Substring(j, k - j); string replacement = props[key] as string; if (replacement != null) { buf.Append(replacement); } i = k + DELIM_STOP_LEN; } } } } #endregion Public Static Methods #region Private Static Methods /// <summary> /// Converts the string representation of the name or numeric value of one or /// more enumerated constants to an equivalent enumerated object. /// </summary> /// <param name="enumType">The type to convert to.</param> /// <param name="value">The enum string value.</param> /// <param name="ignoreCase">If <c>true</c>, ignore case; otherwise, regard case.</param> /// <returns>An object of type <paramref name="enumType" /> whose value is represented by <paramref name="value" />.</returns> private static object ParseEnum(System.Type enumType, string value, bool ignoreCase) { #if !NETCF return Enum.Parse(enumType, value, ignoreCase); #else FieldInfo[] fields = enumType.GetFields(BindingFlags.Public | BindingFlags.Static); string[] names = value.Split(new char[] {','}); for (int i = 0; i < names.Length; ++i) { names[i] = names [i].Trim(); } long retVal = 0; try { // Attempt to convert to numeric type return Enum.ToObject(enumType, Convert.ChangeType(value, typeof(long), CultureInfo.InvariantCulture)); } catch {} foreach (string name in names) { bool found = false; foreach(FieldInfo field in fields) { if (String.Compare(name, field.Name, ignoreCase) == 0) { retVal |= ((IConvertible) field.GetValue(null)).ToInt64(CultureInfo.InvariantCulture); found = true; break; } } if (!found) { throw new ArgumentException("Failed to lookup member [" + name + "] from Enum type [" + enumType.Name + "]"); } } return Enum.ToObject(enumType, retVal); #endif } #endregion Private Static Methods #region Private Static Fields /// <summary> /// The fully qualified type of the OptionConverter class. /// </summary> /// <remarks> /// Used by the internal logger to record the Type of the /// log message. /// </remarks> private readonly static Type declaringType = typeof(OptionConverter); private const string DELIM_START = "${"; private const char DELIM_STOP = '}'; private const int DELIM_START_LEN = 2; private const int DELIM_STOP_LEN = 1; #endregion Private Static Fields } }