/* * 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.Reflection; using System.Globalization; using System.Collections.Generic; using System.Collections.Specialized; using Apache.NMS; using Apache.NMS.Util; namespace Apache.NMS.XMS.Util { /// /// Utility class used to provide convenience methods that apply named /// property settings to objects. /// public class IntrospectionSupport { #region Manage maps of member names and URI aliases private static Dictionary nameMaps = new Dictionary(); private static readonly object nameMapsLock = new object(); /// /// Gets the member names map for the specified type. /// /// Type whose names map is requested. /// Names map for the specified type. /// /// The map is created and registered if it is not found in the /// nameMaps registry. /// public static StringDictionary GetNameMap(Type type) { StringDictionary nameMap; lock(IntrospectionSupport.nameMapsLock) { if(!IntrospectionSupport.nameMaps.TryGetValue( type, out nameMap)) { nameMap = CreateNameMap(type); IntrospectionSupport.nameMaps.Add(type, nameMap); } } return nameMap; } /// /// Creates a dictionary of public property and attribute names, /// indexed by themselves plus all URI attribute keys associated /// to them. /// /// Type whose names map is requested. /// Names map for the specified type. /// /// Applied to this property: /// /// [UriAttribute("My.Test", "MyTest")] /// public string Test /// { get { return(_test); } /// set { _test = value; } /// } /// /// the method returns a dictionary containing /// ("test" -> "Test"), ("my.test" -> "Test"), ("mytest" -> "Test"). /// Note that StringDictionary converts keys to lowercase but /// keeps values untouched. /// public static StringDictionary CreateNameMap(Type type) { StringDictionary nameMap = new StringDictionary(); BindingFlags flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance; // Process public instance self or inherited property foreach(PropertyInfo propertyInfo in type.GetProperties(flags)) { AddToNameMap(nameMap, propertyInfo); } // Process public instance self or inherited fields foreach(FieldInfo fieldInfo in type.GetFields(flags)) { AddToNameMap(nameMap, fieldInfo); } return(nameMap); } /// /// Adds a property or field name and URI attribute keys to the /// specified name map. /// /// Name map. /// Member information for the property /// or field. private static void AddToNameMap(StringDictionary nameMap, MemberInfo memberInfo) { // Add member name mapped to itself nameMap.Add(memberInfo.Name, memberInfo.Name); // For each UriAttribute custom attribute foreach(Attribute attr in memberInfo.GetCustomAttributes( typeof(UriAttributeAttribute), true)) { // For each URI attribute key foreach(string key in ((UriAttributeAttribute)attr).AttributeKeys) { // Index property name by URI attribute key if(!nameMap.ContainsKey(key)) { nameMap.Add(key, memberInfo.Name); } } } return; } #endregion #region Set properties /// /// Sets the public properties of a target object using a string map. /// This method uses .Net reflection to identify public properties of /// the target object matching the keys from the passed map. /// /// Object whose properties will be set. /// Map of key/value pairs. public static void SetProperties(object target, StringDictionary valueMap) { SetProperties(target, valueMap, GetNameMap(target.GetType())); } /// /// Sets the public properties of a target object using a string map. /// This method uses .Net reflection to access public properties of /// the target object matching the keys from the passed map. /// /// The object whose properties will be set. /// Map of key/value pairs. /// Map of key/property name pairs. public static void SetProperties(object target, StringDictionary valueMap, StringDictionary nameMap) { Tracer.DebugFormat("SetProperties called with target: {0}", target.GetType().Name); // Application of specified values is recursive. If a key does not // correspond to a member of the current target object, it is // supposed to refer to a sub-member of such a member. Since member // keys can contain dot characters, an attempt is made to find the // "longest" key corresponding to a member of the current object // (this identifies the "sub-target"), and extract the remaining // key characters as a sub-key to sub-members. // The following dictionary indexes keys to "sub-targets", and // "sub-key"/value pairs to assign to "sub-targets". Dictionary subTargetMap = null; foreach(string key in valueMap.Keys) { if(nameMap.ContainsKey(key)) { // Key refers to a member of the current target string memberName = nameMap[key]; MemberInfo member = FindMemberInfo(target, memberName); if(member == null) { // Should not happen if the nameMap was indeed created // for the current target object... throw new NMSException(string.Format( "No such property or field: {0} on class: {1}", memberName, target.GetType().Name)); } // Set value try { if(member.MemberType == MemberTypes.Property) { PropertyInfo property = (PropertyInfo)member; object value = ConvertValue(valueMap[key], property.PropertyType); property.SetValue(target, value, null); } else { FieldInfo field = (FieldInfo)member; object value = ConvertValue(valueMap[key], field.FieldType); field.SetValue(target, value); } } catch(Exception ex) { throw NMSExceptionSupport.Create( "Error while attempting to apply option.", ex); } } else { // Key does NOT refers to a member of the current target // Extract maximal member key + subkeys string memberKey = key; int dotPos = memberKey.LastIndexOf('.'); bool memberFound = false; while(!memberFound && dotPos > 0) { memberKey = memberKey.Substring(0, dotPos); if(nameMap.ContainsKey(memberKey)) { memberKey = nameMap[memberKey]; memberFound = true; } else { dotPos = memberKey.LastIndexOf('.'); } } if(!memberFound) { throw new NMSException(string.Format( "Unknown property or field: {0} on class: {1}", key, target.GetType().Name)); } // Register memberKey, subKey and value for further processing string subKey = key.Substring(dotPos + 1); StringDictionary subValueMap; if(subTargetMap == null) { subTargetMap = new Dictionary(); } if(!subTargetMap.TryGetValue(memberKey, out subValueMap)) { subValueMap = new StringDictionary(); subTargetMap.Add(memberKey, subValueMap); } // In theory, we can't have the same subkey twice, since // they were unique subkeys from another dictionary. // Therefore, no need to check for subValueMap.ContainsKey. subValueMap.Add(subKey, valueMap[key]); } } // Now process any compound assignments. if(subTargetMap != null) { foreach(string subTargetKey in subTargetMap.Keys) { MemberInfo member = FindMemberInfo(target, subTargetKey); object subTarget = GetUnderlyingObject(member, target); SetProperties(subTarget, subTargetMap[subTargetKey]); } } } /// /// Converts the specified string value to the type of the target /// member. /// private static object ConvertValue(string inputString, Type targetType) { // If the target member is an enumeration, get the enumeration // value or combined (or-ed) values object value; if(targetType.IsEnum) { if(inputString.Contains("+")) { string[] inputValues = inputString.Split('+'); FieldInfo fieldInfo = targetType.GetField(inputValues[0], BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase); if(fieldInfo == null) { throw new NMSException(string.Format( "Invalid {0} value \"{1}\"", targetType.Name, inputValues[0])); } dynamic val = fieldInfo.GetValue(null); for(int v = 1; v < inputValues.Length; v++) { fieldInfo = targetType.GetField(inputValues[v], BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase); if(fieldInfo == null) { throw new NMSException(string.Format( "Invalid {0} value \"{1}\"", targetType.Name, inputValues[v])); } val = (dynamic)val | (dynamic)fieldInfo.GetValue(null); } value = Convert.ChangeType(val, targetType); } else { FieldInfo fieldInfo = targetType.GetField(inputString, BindingFlags.Public | BindingFlags.Static | BindingFlags.IgnoreCase); if(fieldInfo == null) { throw new NMSException(string.Format( "Invalid {0} value \"{1}\"", targetType.Name, inputString)); } value = fieldInfo.GetValue(null); } } else { // Not an enumeration value = Convert.ChangeType(inputString, targetType, CultureInfo.InvariantCulture); } return value; } #endregion #region Get member information and objects /// /// Gets member information for a property or field of the target /// object. /// /// Target object. /// Property or field name. /// Retrieved member information. private static MemberInfo FindMemberInfo(object target, string name) { BindingFlags flags = BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase; Type type = target.GetType(); MemberInfo member = type.GetProperty(name, flags); if(member == null) { member = type.GetField(name, flags); } return member; } /// /// Gets object assigned to the specified property or field member of /// the target object. /// /// Member information. /// Target object. /// Retrieved object. private static object GetUnderlyingObject( MemberInfo member, object target) { object result = null; if(member.MemberType == MemberTypes.Field) { FieldInfo field = member as FieldInfo; if(field.FieldType.IsPrimitive) { throw new NMSException(string.Format( "The field given is a primitive type: {0}", member.Name)); } result = field.GetValue(target); } else { PropertyInfo property = member as PropertyInfo; MethodInfo getter = property.GetGetMethod(); if(getter == null) { throw new NMSException(string.Format( "Cannot access member: {0}", member.Name)); } result = getter.Invoke(target, null); } if(result == null) { throw new NMSException(string.Format( "Could not retrieve the value of member {0}.", member.Name)); } return result; } #endregion } }