// Copyright 2004-2008 Castle Project - http://www.castleproject.org/
//
// Licensed 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.Collections.Generic;
namespace Castle.DynamicProxy
{
using System;
using System.Collections;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Resources;
using System.Threading;
using Castle.DynamicProxy.Generators;
using System.Runtime.Serialization.Formatters.Binary;
///
/// Summary description for ModuleScope.
///
public class ModuleScope
{
///
/// The default file name used when the assembly is saved using .
///
public static readonly String DEFAULT_FILE_NAME = "CastleDynProxy2.dll";
///
/// The default assembly (simple) name used for the assemblies generated by a instance.
///
public static readonly String DEFAULT_ASSEMBLY_NAME = "DynamicProxyGenAssembly2";
private ModuleBuilder moduleBuilderWithStrongName = null;
private ModuleBuilder moduleBuilder = null;
// The names to use for the generated assemblies and the paths (including the names) of their manifest modules
private readonly string strongAssemblyName;
private readonly string weakAssemblyName;
private readonly string strongModulePath;
private readonly string weakModulePath;
// Keeps track of generated types
private readonly Hashtable typeCache = Hashtable.Synchronized(new Hashtable());
// Users of ModuleScope should use this lock when accessing the cache
private readonly ReaderWriterLock readerWriterLock = new ReaderWriterLock();
// Used to lock the module builder creation
private readonly object _lockobj = new object();
// Specified whether the generated assemblies are intended to be saved
private readonly bool savePhysicalAssembly;
///
/// Initializes a new instance of the class; assemblies created by this instance will not be saved.
///
public ModuleScope() : this(false)
{
}
///
/// Initializes a new instance of the class, allowing to specify whether the assemblies generated by this instance
/// should be saved.
///
/// If set to true saves the generated module.
public ModuleScope(bool savePhysicalAssembly)
: this(savePhysicalAssembly, DEFAULT_ASSEMBLY_NAME, DEFAULT_FILE_NAME, DEFAULT_ASSEMBLY_NAME, DEFAULT_FILE_NAME)
{
}
///
/// Initializes a new instance of the class, allowing to specify whether the assemblies generated by this instance
/// should be saved and what simple names are to be assigned to them.
///
/// If set to true saves the generated module.
/// The simple name of the strong-named assembly generated by this .
/// The path and file name of the manifest module of the strong-named assembly generated by this .
/// The simple name of the weak-named assembly generated by this .
/// The path and file name of the manifest module of the weak-named assembly generated by this .
public ModuleScope(bool savePhysicalAssembly, string strongAssemblyName, string strongModulePath,
string weakAssemblyName, string weakModulePath)
{
this.savePhysicalAssembly = savePhysicalAssembly;
this.strongAssemblyName = strongAssemblyName;
this.strongModulePath = strongModulePath;
this.weakAssemblyName = weakAssemblyName;
this.weakModulePath = weakModulePath;
}
///
/// Users of this should use this lock when accessing the cache.
///
public ReaderWriterLock RWLock
{
get { return readerWriterLock; }
}
///
/// Returns a type from this scope's type cache, or null if the key cannot be found.
///
/// The key to be looked up in the cache.
/// The type from this scope's type cache matching the key, or null if the key cannot be found
public Type GetFromCache(CacheKey key)
{
// no lock needed, typeCache is synchronized
return (Type) typeCache[key];
}
///
/// Registers a type in this scope's type cache.
///
/// The key to be associated with the type.
/// The type to be stored in the cache.
public void RegisterInCache(CacheKey key, Type type)
{
// no lock needed, typeCache is synchronized
typeCache[key] = type;
}
///
/// Gets the key pair used to sign the strong-named assembly generated by this .
///
///
public static byte[] GetKeyPair()
{
byte[] keyPair;
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Castle.DynamicProxy.DynProxy.snk"))
{
if (stream == null)
throw new MissingManifestResourceException(
"Should have a Castle.DynamicProxy.DynProxy.snk as an embedded resource, so Dynamic Proxy could sign generated assembly");
int length = (int) stream.Length;
keyPair = new byte[length];
stream.Read(keyPair, 0, length);
}
return keyPair;
}
///
/// Gets the strong-named module generated by this scope, or if none has yet been generated.
///
/// The strong-named module generated by this scope, or if none has yet been generated.
public ModuleBuilder StrongNamedModule
{
get
{
lock (_lockobj)
{
return moduleBuilderWithStrongName;
}
}
}
///
/// Gets the file name of the strongly named module generated by this scope.
///
/// The file name of the strongly named module generated by this scope.
public string StrongNamedModuleName
{
get { return Path.GetFileName(strongModulePath); }
}
///
/// Gets the directory where the strongly named module generated by this scope will be saved, or if the current directory
/// is used.
///
/// The directory where the strongly named module generated by this scope will be saved when is called
/// (if this scope was created to save modules).
public string StrongNamedModuleDirectory
{
get
{
string directory = Path.GetDirectoryName(strongModulePath);
if (directory == "")
return null;
else
return directory;
}
}
///
/// Gets the weak-named module generated by this scope, or if none has yet been generated.
///
/// The weak-named module generated by this scope, or if none has yet been generated.
public ModuleBuilder WeakNamedModule
{
get
{
lock (_lockobj)
{
return moduleBuilder;
}
}
}
///
/// Gets the file name of the weakly named module generated by this scope.
///
/// The file name of the weakly named module generated by this scope.
public string WeakNamedModuleName
{
get { return Path.GetFileName(weakModulePath); }
}
///
/// Gets the directory where the weakly named module generated by this scope will be saved, or if the current directory
/// is used.
///
/// The directory where the weakly named module generated by this scope will be saved when is called
/// (if this scope was created to save modules).
public string WeakNamedModuleDirectory
{
get
{
string directory = Path.GetDirectoryName(weakModulePath);
if (directory == "")
return null;
else
return directory;
}
}
///
/// Gets the specified module generated by this scope, creating a new one if none has yet been generated.
///
/// If set to true, a strong-named module is returned; otherwise, a weak-named module is returned.
/// A strong-named or weak-named module generated by this scope, as specified by the parameter.
public ModuleBuilder ObtainDynamicModule(bool isStrongNamed)
{
lock (_lockobj)
{
if (isStrongNamed)
return ObtainDynamicModuleWithStrongName();
else
return ObtainDynamicModuleWithWeakName();
}
}
///
/// Gets the strong-named module generated by this scope, creating a new one if none has yet been generated.
///
/// A strong-named module generated by this scope.
public ModuleBuilder ObtainDynamicModuleWithStrongName()
{
lock (_lockobj)
{
if (moduleBuilderWithStrongName == null)
{
moduleBuilderWithStrongName = CreateModule(true);
}
return moduleBuilderWithStrongName;
}
}
///
/// Gets the weak-named module generated by this scope, creating a new one if none has yet been generated.
///
/// A weak-named module generated by this scope.
public ModuleBuilder ObtainDynamicModuleWithWeakName()
{
lock (_lockobj)
{
if (moduleBuilder == null)
{
moduleBuilder = CreateModule(false);
}
return moduleBuilder;
}
}
private ModuleBuilder CreateModule(bool signStrongName)
{
AssemblyName assemblyName = GetAssemblyName(signStrongName);
string moduleName = signStrongName ? StrongNamedModuleName : WeakNamedModuleName;
string moduleDirectory = signStrongName ? StrongNamedModuleDirectory : WeakNamedModuleDirectory;
if (savePhysicalAssembly)
{
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName, AssemblyBuilderAccess.RunAndSave, moduleDirectory);
return assemblyBuilder.DefineDynamicModule(moduleName, moduleName, true);
}
else
{
AssemblyBuilder assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
assemblyName,
AssemblyBuilderAccess.Run);
return assemblyBuilder.DefineDynamicModule(moduleName, true);
}
}
private AssemblyName GetAssemblyName(bool signStrongName)
{
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = signStrongName ? strongAssemblyName : weakAssemblyName;
if (signStrongName)
{
byte[] keyPairStream = GetKeyPair();
if (keyPairStream != null)
{
assemblyName.KeyPair = new StrongNameKeyPair(keyPairStream);
}
}
return assemblyName;
}
///
/// Saves the generated assembly with the name and directory information given when this instance was created (or with
/// the and current directory if none was given).
///
///
///
/// This method stores the generated assembly in the directory passed as part of the module information specified when this instance was
/// constructed (if any, else the current directory is used). If both a strong-named and a weak-named assembly
/// have been generated, it will throw an exception; in this case, use the overload.
///
///
/// If this was created without indicating that the assembly should be saved, this method does nothing.
///
/// Both a strong-named and a weak-named assembly have been generated.
/// The path of the generated assembly file, or null if no file has been generated.
public string SaveAssembly()
{
if (!savePhysicalAssembly)
return null;
if (StrongNamedModule != null && WeakNamedModule != null)
throw new InvalidOperationException("Both a strong-named and a weak-named assembly have been generated.");
else if (StrongNamedModule != null)
return SaveAssembly(true);
else if (WeakNamedModule != null)
return SaveAssembly(false);
else
return null;
}
///
/// Saves the specified generated assembly with the name and directory information given when this instance was created
/// (or with the and current directory if none was given).
///
/// True if the generated assembly with a strong name should be saved (see );
/// false if the generated assembly without a strong name should be saved (see .
///
///
/// This method stores the specified generated assembly in the directory passed as part of the module information specified when this instance was
/// constructed (if any, else the current directory is used).
///
///
/// If this was created without indicating that the assembly should be saved, this method does nothing.
///
///
/// No assembly has been generated that matches the parameter.
///
/// The path of the generated assembly file, or null if no file has been generated.
public string SaveAssembly(bool strongNamed)
{
if (!savePhysicalAssembly)
return null;
AssemblyBuilder assemblyBuilder;
string assemblyFileName;
string assemblyFilePath;
if (strongNamed)
{
if (StrongNamedModule == null)
throw new InvalidOperationException("No strong-named assembly has been generated.");
else
{
assemblyBuilder = (AssemblyBuilder) StrongNamedModule.Assembly;
assemblyFileName = StrongNamedModuleName;
assemblyFilePath = StrongNamedModule.FullyQualifiedName;
}
}
else
{
if (WeakNamedModule == null)
throw new InvalidOperationException("No weak-named assembly has been generated.");
else
{
assemblyBuilder = (AssemblyBuilder) WeakNamedModule.Assembly;
assemblyFileName = WeakNamedModuleName;
assemblyFilePath = WeakNamedModule.FullyQualifiedName;
}
}
if (File.Exists(assemblyFilePath))
File.Delete(assemblyFilePath);
AddCacheMappings(assemblyBuilder);
assemblyBuilder.Save(assemblyFileName);
return assemblyFilePath;
}
private void AddCacheMappings(AssemblyBuilder builder)
{
Dictionary mappings;
lock (typeCache.SyncRoot)
{
mappings = new Dictionary();
foreach (DictionaryEntry cacheEntry in typeCache)
mappings.Add((CacheKey) cacheEntry.Key, ((Type) cacheEntry.Value).FullName);
}
CacheMappingsAttribute.ApplyTo(builder, mappings);
}
///
/// Loads the generated types from the given assembly into this 's cache.
///
/// The assembly to load types from. This assembly must have been saved via or
/// , or it must have the manually applied.
///
/// This method can be used to load previously generated and persisted proxy types from disk into this scope's type cache, eg. in order
/// to avoid the performance hit associated with proxy generation.
///
public void LoadAssemblyIntoCache(Assembly assembly)
{
if (assembly == null)
throw new ArgumentNullException("assembly");
CacheMappingsAttribute[] cacheMappings =
(CacheMappingsAttribute[]) assembly.GetCustomAttributes(typeof (CacheMappingsAttribute), false);
if (cacheMappings.Length == 0)
{
string message = string.Format(
"The given assembly '{0}' does not contain any cache information for generated types.",
assembly.FullName);
throw new ArgumentException(message, "assembly");
}
lock (typeCache.SyncRoot)
{
foreach (KeyValuePair mapping in cacheMappings[0].GetDeserializedMappings())
{
Type loadedType = assembly.GetType(mapping.Value);
if (loadedType != null)
RegisterInCache(mapping.Key, loadedType);
}
}
}
}
}