File: Shared\System\Windows\Markup\ReflectionHelper.cs
Project: wpf\src\PresentationBuildTasks.csproj (PresentationBuildTasks)
//-----------------------------------------------------------------------
//
//  Microsoft Windows Client Platform
//  Copyright (C) Microsoft Corporation, 2006
//
//  File: TypeConverterHelper.cs
//
//  Description: Specifies that the whitespace surrounding an element should be trimmed.
//
//---------------------------------------------------------------------------
 
using System;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Runtime.CompilerServices;
using MS.Internal;
 
#if PBTCOMPILER
using MS.Utility;
namespace MS.Internal.Markup
 
#elif WINDOWS_BASE
using MS.Utility;
using MS.Internal.WindowsBase;
namespace System.Windows.Markup
#else
namespace System.Xaml
#endif
{
 
    /// <summary>
    /// Class that provides helper functions for the parser to reflect on types, properties,
    /// custom attributes and load assemblies.
    /// </summary>
    internal static class ReflectionHelper
    {
#region Type
        /// <summary>
        /// Parse and get the type of the passed in string
        /// </summary>
        internal static Type GetQualifiedType(string typeName)
        {
            // ISSUE: we only parse the assembly name and type name
            // all other Type.GetType() type fragments (version, culture info, pub key token etc) are ignored!!!
            string[] nameFrags = typeName.Split(new Char[] { ',' }, 2);
            Type type = null;
            if (nameFrags.Length == 1)
            {
                // treat it as an absolute name
                type = Type.GetType(nameFrags[0]);
            }
            else
            {
                if (nameFrags.Length != 2)
                    throw new InvalidOperationException(SR.Get(SRID.QualifiedNameHasWrongFormat, typeName));
 
                Assembly a = null;
                try
                {
                    a = LoadAssembly(nameFrags[1].TrimStart(), null);
                }
                // ifdef magic to save compiler update.
                // the fix below is for an FxCop rule about non-CLR exceptions.
                // however this rule has now been removed.
                catch (Exception e)   // Load throws generic Exceptions, so this can't be made more specific.
                {
                    if (CriticalExceptions.IsCriticalException(e))
                    {
                        throw;
                    }
                    else
                    {
                        // If we can't load the assembly, just return null (fall-through).
                        a = null;
                    }
                }
 
                if (a != null)
                {
                    try
                    {
                        type = a.GetType(nameFrags[0]);
                        // If we can't get the type, just return null (fall-through).
                    }
                    catch (ArgumentException)
                    {
                        a = null;
                    }
                    catch (System.Security.SecurityException)
                    {
                        a = null;
                    }
                }
            }
 
            return type;
        }
 
        internal static bool IsNullableType(Type type)
        {
            return (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>)));
        }
 
        internal static bool IsInternalType(Type type)
        {
            Type origType = type;
            Debug.Assert(null != type, "Type passed to IsInternalType is null");
 
            // If this is an internal nested type or a parent nested public type, walk up the declaring types.
            while (type.IsNestedAssembly || type.IsNestedFamORAssem || (origType != type && type.IsNestedPublic))
            {
                type = type.DeclaringType;
            }
 
            // If we're on a non-internal nested type, IsNotPublic & IsPublic will both return false.
            // If we were originally on a nested type and have currently reached a parent
            // top-level(non nested) type, then it must be top level internal or public type.
            return type.IsNotPublic || (origType != type && type.IsPublic);
        }
 
        /// <summary>
        /// Helper for determine if the type is a public class.
        /// </summary>
        /// <param name="type">Type to check</param>
        /// <returns>True if type is public</returns>
        internal static bool IsPublicType(Type type)
        {
            Debug.Assert(null != type, "Type passed to IsPublicType is null");
 
            // If this is a nested internal type, walk up the declaring types.
            while (type.IsNestedPublic)
            {
                type = type.DeclaringType;
            }
 
            // If we're on a non-public nested type, IsPublic will return false.
            return type.IsPublic;
        }
 
        // Since System.dll may be loaded in regular load context as well ROL context
        // we need to get the ROL type from teh real type to compare a type with at compile
        // time. At run-time, the same type can be used.
        internal static Type GetSystemType(Type type)
        {
#if PBTCOMPILER
            Assembly asmSystem = LoadAssembly("System", null);
 
            if (asmSystem != null)
            {
                type = asmSystem.GetType(type.FullName);
            }
            else
            {
                type = null;
            }
#endif
            return type;
        }
 
#if WINDOWS_BASE
        /// <summary>
        /// Get the type to use for reflection:  the custom type, if any, otherwise just the type.
        /// </summary>
        internal static Type GetReflectionType(object item)
        {
            if (item == null)
                return null;
 
            ICustomTypeProvider ictp = item as ICustomTypeProvider;
            if (ictp == null)
                return item.GetType();
            else
                return ictp.GetCustomType();
        }
#endif
        
#endregion Type
 
        #region Attributes
 
        internal static string GetTypeConverterAttributeData(Type type, out Type converterType)
        {
            bool foundTC = false;
            return GetCustomAttributeData(type, GetSystemType(typeof(TypeConverterAttribute)), true, ref foundTC, out converterType);
        }
 
        internal static string GetTypeConverterAttributeData(MemberInfo mi, out Type converterType)
        {
            return GetCustomAttributeData(mi, GetSystemType(typeof(TypeConverterAttribute)), out converterType);
        }
 
        // Given a ReflectionOnlyLoaded member, returns the value of a metadata attribute of
        // Type attrType if set on that member. Looks only for attributes that have a ctor with
        // one parameter that is of Type string or Type.
        private static string GetCustomAttributeData(MemberInfo mi, Type attrType, out Type typeValue)
        {
            IList<CustomAttributeData> list = CustomAttributeData.GetCustomAttributes(mi);
            string attrValue = GetCustomAttributeData(list, attrType, out typeValue, true, false);
            return attrValue == null ? string.Empty : attrValue;
        }
 
#if PBTCOMPILER
        // Given a ReflectionOnlyLoaded type, returns the value of a metadata attribute of
        // Type attrType if set on that type. Looks only for attributes that have a ctor with
        // one parameter that is of Type string.
        internal static string GetCustomAttributeData(Type t, Type attrType, bool allowZeroArgs)
        {
            Type typeValue = null;
            IList<CustomAttributeData> list = CustomAttributeData.GetCustomAttributes(t);
            return GetCustomAttributeData(list, attrType, out typeValue, false, allowZeroArgs);
        }
#endif
 
        // Helper that enumerates a list of CustomAttributeData obtained via ReflectionOnlyLoad, and
        // looks for a specific attribute of Type attrType. It only looks for attribiutes with a single
        // value of Type string that is passed in via a ctor. If allowTypeAlso is true, then it looks for
        // values of typeof(Type) as well.
        private static string GetCustomAttributeData(IList<CustomAttributeData> list, Type attrType, out Type typeValue, bool allowTypeAlso, bool allowZeroArgs)
        {
            typeValue = null;
            string attrValue = null;
            for (int j = 0; j < list.Count; j++)
            {
                attrValue = GetCustomAttributeData(list[j], attrType, out typeValue, allowTypeAlso, false, allowZeroArgs);
                if (attrValue != null)
                {
                    break;
                }
            }
 
            return attrValue;
        }
 
        // Special version of type-based GetCustomAttributeData that does two
        //  additional tasks:
        //  1) Retrieves the attributes even if it's defined on a base type, and
        //  2) Distinguishes between "attribute found and said null" and
        //     "no attribute found at all" via the ref bool.
        internal static string GetCustomAttributeData(Type t,
                                                      Type attrType,
                                                      bool allowTypeAlso,
                                                  ref bool attributeDataFound,
                                                  out Type typeValue)
        {
            typeValue = null;
            attributeDataFound = false;
            Type currentType = t;
            string attributeDataString = null;
            CustomAttributeData cad;
 
            while (currentType != null && !attributeDataFound)
            {
                IList<CustomAttributeData> list = CustomAttributeData.GetCustomAttributes(currentType);
 
                for (int j = 0; j < list.Count && !attributeDataFound; j++)
                {
                    cad = list[j];
 
                    if (cad.Constructor.ReflectedType == attrType)
                    {
                        attributeDataFound = true;
                        attributeDataString = GetCustomAttributeData(cad, attrType, out typeValue, allowTypeAlso, false, false);
                    }
                }
 
                if (!attributeDataFound)
                {
                    currentType = currentType.BaseType; // object.BaseType is null, used as terminating condition for the while() loop.
                }
            }
 
            return attributeDataString;
        }
 
        // Helper that inspects a specific CustomAttributeData obtained via ReflectionOnlyLoad, and
        // returns its value if the Type of the attribiutes matches the passed in attrType. It only
        // looks for attributes with no values or a single value of Type string that is passed in via
        // a ctor. If allowTypeAlso is true, then it looks for values of typeof(Type) as well in the
        // single value case. If noArgs == false and zeroArgsAllowed = true, that means 0 or 1 args
        // are permissible.
        private static string GetCustomAttributeData(CustomAttributeData cad,
                                                     Type attrType,
                                                 out Type typeValue,
                                                     bool allowTypeAlso,
                                                     bool noArgs,
                                                     bool zeroArgsAllowed)
        {
            string attrValue = null;
            typeValue = null;
 
            // get the Constructor info
            ConstructorInfo cinfo = cad.Constructor;
            if (cinfo.ReflectedType == attrType)
            {
                // typedConstructorArguments (the Attribute constructor arguments)
                // [MyAttribute("test", Name=Hello)]
                // "test" is the Constructor Argument
                IList<CustomAttributeTypedArgument> constructorArguments = cad.ConstructorArguments;
                if (constructorArguments.Count == 1 && !noArgs)
                {
                    CustomAttributeTypedArgument tca = constructorArguments[0];
                    attrValue = tca.Value as String;
                    if (attrValue == null && allowTypeAlso && tca.ArgumentType == typeof(Type))
                    {
                        typeValue = tca.Value as Type;
                        attrValue = typeValue.AssemblyQualifiedName;
                    }
 
                    if (attrValue == null)
                    {
                        throw new ArgumentException(SR.Get(SRID.ParserAttributeArgsLow, attrType.Name));
                    }
                }
                else if (constructorArguments.Count == 0)
                {
                    // zeroArgsAllowed = true for CPA for example.
                    // CPA with no args is valid and would mean that this type is overriding a base CPA
                    if (noArgs || zeroArgsAllowed)
                    {
                        attrValue = string.Empty;
                    }
                    else
                    {
                        throw new ArgumentException(SR.Get(SRID.ParserAttributeArgsLow, attrType.Name));
                    }
                }
                else
                {
                    throw new ArgumentException(SR.Get(SRID.ParserAttributeArgsHigh, attrType.Name));
                }
            }
 
            return attrValue;
        }
 
#endregion Attributes
 
#region Assembly Loading
        //
        // Clean up the cache entry for the given assembly, so that it can be reloaded for the next build cycle.
        // Usually it is called by MarkupCompiler task.
        //
        internal static void ResetCacheForAssembly(string assemblyName)
        {
            string assemblyNameLookup = assemblyName.ToUpper(CultureInfo.InvariantCulture);
#if PBTCOMPILER
            _reflectionOnlyLoadedAssembliesHash[assemblyNameLookup] = null;
#else
            _loadedAssembliesHash[assemblyNameLookup] = null;
#endif
        }
 
        internal static Assembly LoadAssembly(string assemblyName, string assemblyPath)
        {
#if PBTCOMPILER
            return ReflectionOnlyLoadAssembly(assemblyName, assemblyPath);
#else
            return LoadAssemblyHelper(assemblyName, assemblyPath);
#endif
        }
 
#if !PBTCOMPILER
        internal static Assembly GetAlreadyLoadedAssembly(string assemblyNameLookup)
        {
            return (Assembly)_loadedAssembliesHash[assemblyNameLookup];
        }
 
        // Loads the Assembly with the specified name at the specified optional location.
        //
        // assemblyName is either short name or full name.
        // assemblyPath is either full file path or null.
        //
        private static Assembly LoadAssemblyHelper(string assemblyGivenName, string assemblyPath)
        {
            AssemblyName assemblyName = new AssemblyName(assemblyGivenName);
            string assemblyShortName = assemblyName.Name;
            assemblyShortName = assemblyShortName.ToUpper(CultureInfo.InvariantCulture);
 
            // Check if the assembly has already been loaded.
            Assembly retassem = (Assembly)_loadedAssembliesHash[assemblyShortName];
 
            if (retassem != null)
            {
                if (assemblyName.Version != null)
                {
                    AssemblyName cachedName = new AssemblyName(retassem.FullName);
                    if (!AssemblyName.ReferenceMatchesDefinition(assemblyName, cachedName))
                    {
                        string request = assemblyName.ToString();
                        string found = cachedName.ToString();
                        throw new InvalidOperationException(SR.Get(SRID.ParserAssemblyLoadVersionMismatch, request, found));
                    }
                }
            }
            else
            {
                // Check if the current AppDomain has this assembly loaded for some other reason.
                // If so, then just use that assembly and don't attempt to load another copy of it.
                // Only do this if no path is provided.
                if (String.IsNullOrEmpty(assemblyPath))
                    retassem = SafeSecurityHelper.GetLoadedAssembly(assemblyName);
 
                if (retassem == null)
                {
                    if (!String.IsNullOrEmpty(assemblyPath))
                    {
 
                        // assemblyPath is set, Load the assembly from this specified place.
                        // the path must be full file path which contains directory, file name and extension.
                        Debug.Assert(!assemblyPath.EndsWith("\\", StringComparison.Ordinal), "the assembly path should be a full file path containing file extension");
 
                        // LoadFile will only override your request only if it is in the GAC
                        retassem = Assembly.LoadFile(assemblyPath);
                    }
                    //
                    // At compile time, the build task should always pass the full path of the referenced assembly, even if it
                    // comes from GAC. But below code snippet can run if parser wants to try loading an assembly w/o a path.
                    // This also makes run-time assembly load consistent with compile-time semantics.
                    else
                    {
                        try
                        {
                            retassem = Assembly.Load(assemblyGivenName);
                        }
                        catch (System.IO.FileNotFoundException)
                        {
                            // This may be a locally defined assembly that has not been created yet.
                            // To support these cases, just set a null assembly and return.  This
                            // will fail downstream if it really was an assembly miss.
                            retassem = null;
                        }
                    }
                }
 
                // Cache the assembly
                if (retassem != null)
                {
                    _loadedAssembliesHash[assemblyShortName] = retassem;
                }
            }
 
            return retassem;
        }
 
        private static Hashtable _loadedAssembliesHash = new Hashtable(8);
#else
        // returns true is sourceAssembly declares LocalAssemblyName as a friend
        internal static bool IsFriendAssembly(Assembly sourceAssembly)
        {
            bool isFriend = false;
            Type typeValue = null;
 
            string friendAssemblyName = string.Empty;
            IList<CustomAttributeData> list = CustomAttributeData.GetCustomAttributes(sourceAssembly);
 
            for (int j = 0; j < list.Count; j++)
            {
                friendAssemblyName = GetCustomAttributeData(list[j], typeof(InternalsVisibleToAttribute), out typeValue, false, false, false);
                if (friendAssemblyName != null && friendAssemblyName == LocalAssemblyName)
                {
                    isFriend = true;
                    break;
                }
            }
 
            return isFriend;
        }
 
        internal static bool IsInternalAllowedOnType(Type type)
        {
            return ((LocalAssemblyName == type.Assembly.GetName().Name) || IsFriendAssembly(type.Assembly));
        }
 
        // The local assembly that contains the baml.
        internal static string LocalAssemblyName
        {
            get { return _localAssemblyName; }
            set { _localAssemblyName = value; }
        }
 
        private static string _localAssemblyName = string.Empty;
 
        internal static bool HasAlreadyReflectionOnlyLoaded(string assemblyNameLookup)
        {
             //
             // If the cache contains an entry for the given assemblyname, and its value is not
             // null, it marks the assembly has been loaded.
             //
             // Since ResetCacheForAssembly( ) just sets "null" in the hashtable for a given assembly
             // without really removing it, it is possible that an assembly is not reloaded before this
             // method is called.
             // Such as for the local-type-ref xaml file compilation,  the cache entry for the temporary
             // assembly is reset to null, but it is not reloaded for MCPass1.
             //
             // We don't want to change the behavior of ResetCacheForAssembly( ) at this moment. (Resetting
             // the value to null without really removing the entry is helpful for the perf)
             //
 
             return (_reflectionOnlyLoadedAssembliesHash.Contains(assemblyNameLookup) && _reflectionOnlyLoadedAssembliesHash[assemblyNameLookup] != null);
        }
 
        internal static Assembly GetAlreadyReflectionOnlyLoadedAssembly(string assemblyNameLookup)
        {
             return (Assembly)_reflectionOnlyLoadedAssembliesHash[assemblyNameLookup];
        }
 
        //
        // For a given assembly name and its full path, Reflection-Only load the assembly directly
        // from the file in disk or load the file to memory and then create assembly instance from
        // memory buffer data.
        //
        private static Assembly ReflectionOnlyLoadAssembly(string assemblyGivenName, string fullpath)
        {
            Debug.Assert(String.IsNullOrEmpty(assemblyGivenName) == false, "assemblyName should not be empty.");
 
            AssemblyName assemblyName = new AssemblyName(assemblyGivenName);
            string assemblyShortName = assemblyName.Name;
            assemblyShortName = assemblyShortName.ToUpper(CultureInfo.InvariantCulture);
 
            Assembly asm = (Assembly)_reflectionOnlyLoadedAssembliesHash[assemblyShortName];
            if (asm != null)
            {
                if (assemblyName.Version != null)
                {
                    AssemblyName cachedName = new AssemblyName(asm.FullName);
                    if (!AssemblyName.ReferenceMatchesDefinition(assemblyName, cachedName))
                    {
                        string request = assemblyName.ToString();
                        string found = cachedName.ToString();
                        throw new InvalidOperationException(SR.Get(SRID.ParserAssemblyLoadVersionMismatch, request, found));
                    }
                }
            }
            // Reflection doesn't work with mscorlib (different mscorlib's that is).  We have problems if
            // the mscorlib is a dehydrated / asmMeta style reference assembly.
            // Just use the real MsCorLib we are running on.  The alternative is failure.
            else if ((String.Compare (assemblyShortName, "mscorlib", StringComparison.OrdinalIgnoreCase)==0))
            {
                Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
                for(int i=0; i<assemblies.Length; i++)
                {
                    if (String.Compare(assemblies[i].GetName().Name, assemblyShortName, StringComparison.OrdinalIgnoreCase) == 0)
                    {
                        asm = assemblies[i];
                        break;
                    }
                }
            }
            else
            {
                bool fLoadContent = false;
 
                if (_contentLoadAssembliesHash[assemblyShortName] != null)
                {
                   fLoadContent = (bool)_contentLoadAssembliesHash[assemblyShortName];
                }
 
                if (fLoadContent == true)
                {
                    byte[] asmContents;
 
                    asmContents = GetAssemblyContent(fullpath);
 
                    if (asmContents != null)
                    {
                        try
                        {
                           asm = Assembly.ReflectionOnlyLoad(asmContents);
                        }
                        catch (FileLoadException)
                        {
                           // try to get the assembly instance from the assembly cache in Appdomain.
                           //
                           // This is to fix the problem for below scenario:
                           //
                           //   If an assembly is required to load through Memory buffer,  it can always be
                           //   be loaded through reflectionOnly loading correctly in the first time.
                           //
                           //   But if the build is restarted, and want to load this assembly again, the parser
                           //   should load the content from the disk and create the assembly instance again to
                           //   make sure it always takes use of the latest conten for that assembly.
                           //
                           //   If the content of this assembly is modified in any chance, the second time ReflectionOnly
                           //   loading from memory buffer works well, but if the content of this assembly is not
                           //   changed, ReflectionOnly-Loading From content will fail and show error saying the
                           //   assembly is already loaded.  For this particular case, we should take use of the
                           //   existing assembly instance.
                           //
                           // 
 
 
 
                           Assembly[] assemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
 
                           for (int i = assemblies.Length -1; i >= 0; i--)
                           {
                               if (String.Compare(assemblies[i].GetName().Name, assemblyGivenName, StringComparison.OrdinalIgnoreCase) == 0)
                               {
                                   asm = assemblies[i];
                                   break;
                               }
                           }
                        }
                    }
                }
                else if (!String.IsNullOrEmpty(fullpath))
                {
                    asm = Assembly.ReflectionOnlyLoadFrom(fullpath);
                }
                else if (asm == null)
                {
                    // In the compiler scenario this will be encountered only when there is no path
                    // and parser needs to call this directly to attempt to load an assembly.
                    try
                    {
                         asm = Assembly.ReflectionOnlyLoad(assemblyGivenName);
                    }
                    catch (System.IO.FileNotFoundException)
                    {
                        // This may be a locally defined assembly that has not been created yet.
                        // To support these cases, just set a null assembly and return.  This
                        // will fail downstream if it really was an assembly miss.
                        asm = null;
                    }
                }
 
                if (asm != null)
                {
                    _reflectionOnlyLoadedAssembliesHash[assemblyShortName] = asm;
                }
            }
 
            return asm;
        }
 
        private static Hashtable _reflectionOnlyLoadedAssembliesHash = new Hashtable(8);
 
        //
        // Copy assembly file from disk to memory, and return the memory buffer.
        //
        internal static byte[] GetAssemblyContent(string filepath)
        {
            byte[] asmContents = null;
 
            using (FileStream fileStream = File.Open(filepath, FileMode.Open, FileAccess.Read, FileShare.Read))
            {
                // FileStream.Read does not support offsets or lengths
                // larger than int.MaxValue.
                if (fileStream.Length > int.MaxValue)
                {
                    return null;
                }
 
                int size = (int)fileStream.Length;
                asmContents = new byte[size];
                if (size > 0)
                {
                    ReliableRead(fileStream, asmContents, 0, size);
                }
 
                // With using statement, fileStream can always be disposed,
                // there is no need to put code here to explicitly dispose the
                // file stream object.
            }
 
            return asmContents;
        }
 
        //
        // set flag for the assembly to indicate that this assembly should be loaded from memory buffer
        // instead of file in disk.
        //
        // Usually it is called by MarkupCompiler task.
        //
        internal static void SetContentLoadForAssembly(string assemblyName)
        {
            string assemblyNameLookup = assemblyName.ToUpper(CultureInfo.InvariantCulture);
            _contentLoadAssembliesHash[assemblyNameLookup] = true;
        }
 
        /// <summary>
        /// Read utility that is guaranteed to return the number of bytes requested
        /// if they are available.
        /// </summary>
        /// <param name="stream">stream to read from</param>
        /// <param name="buffer">buffer to read into</param>
        /// <param name="offset">offset in buffer to write to</param>
        /// <param name="count">bytes to read</param>
        /// <returns>bytes read</returns>
        /// <remarks>Normal Stream.Read does not guarantee how many bytes it will
        /// return.  This one does.</remarks>
        private static int ReliableRead(Stream stream, byte[] buffer, int offset, int count)
        {
            /* Invariant.Assert is not available in PBT
            Invariant.Assert(stream != null);
            Invariant.Assert(buffer != null);
            Invariant.Assert(buffer.Length > 0);
            Invariant.Assert(offset >= 0);
            Invariant.Assert(count >= 0);
            Invariant.Assert(checked(offset + count<= buffer.Length));
            */
 
            // let's read the whole block into our buffer
            int totalBytesRead = 0;
            while (totalBytesRead < count)
            {
                int bytesRead = stream.Read(buffer,
                                offset + totalBytesRead,
                                count - totalBytesRead);
                if (bytesRead == 0)
                {
                    break;
                }
                totalBytesRead += bytesRead;
            }
            return totalBytesRead;
        }
 
        private static Hashtable _contentLoadAssembliesHash = new Hashtable(1);
#endif
 
#endregion Assembly Loading
    }
}