/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collection.Generic; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Xml.Serialization; using System35; using JetBrains.Annotations; using JetBrains.Build.InstallationData; using JetBrains.Omea.Containers; namespace JetBrains.Omea.Base.Install { /// /// Invokes installation/uninstallation related services. /// This class should not be made static (and, conscequently, public interface methods on it), so that it were created and disposed accordingly. /// public class Installer : IDisposable { #region Data /// /// Maps the attributes storing the installation data to the objects that process their registration and unregistration. /// Don't use, see . /// [NotNull] protected OneToSetMap myMapAttributeToInstallers; /// /// Maps the attribute-installer types to the created instances of their objects. /// Don't use, see . /// [NotNull] protected Dictionary myMapInstallerTypeToInstance; [NotNull] private readonly ICollection myAssemblies; private Func myResolveSourceDirRoot; #endregion #region Init /// /// A constructor for spawning the installer on an application descriptor that is defined in an attribute. /// public Installer([NotNull] ICollection assemblies) { if(assemblies == null) throw new ArgumentNullException("assemblies"); // Validate var hashAssemblies = new HashSet(assemblies.Count); foreach(Assembly assembly in assemblies) { if(hashAssemblies.Contains(assembly)) throw new InvalidOperationException(string.Format("Duplicate assembly “{0}”.", assembly.FullName)); hashAssemblies.Add(assembly); } myAssemblies = assemblies; } #endregion #region Attributes /// /// Gets the list of all the product assemblies that are searched for attributes. /// [NotNull] public ICollection Assemblies { get { return myAssemblies; } } /// /// Maps the attribute-installer types to the created instances of their objects. /// public Dictionary MapInstallerTypeToInstance { get { if(myMapInstallerTypeToInstance == null) CollectAttributeInstallers(); if(myMapInstallerTypeToInstance == null) throw new InvalidOperationException(string.Format("Failed to collect the attribute installers.")); return myMapInstallerTypeToInstance; } } #endregion #region Operations /// /// Dupms the given Registry data into a file. /// public static void DumpInstallationData(InstallationDataXml data, string sRegistryRegistrationDataFile) { var serializer = new XmlSerializer(typeof(RegistryXml)); using(var stream = new FileStream(sRegistryRegistrationDataFile, FileMode.Create, FileAccess.Write, FileShare.Read)) serializer.Serialize(stream, data); } /// /// Gets all the Registry data that should be written to or erased from the Registry upon installation or uninstallation. /// This includes the static app-global Registry data and dynamic registration info collected from the Assembly attributes. /// [NotNull] public InstallationDataXml HarvestInstallationData() { var data = new InstallationDataXml(); // Run the attribute installers statically (each instance once) InvokeAttributeInstallersStatic(data); // Run the attribute installers per each attribute instance InvokeAttributeInstallersInstance(data); data.EnsureNotNull(); data.RemoveDuplicates(); data.AssertValid(); return data; } #endregion #region Implementation /// /// Collects the handlers that implement installation against the assembly attributes, see . /// Do not call, use . /// protected void CollectAttributeInstallers() { // Init only once if(myMapAttributeToInstallers != null) return; myMapAttributeToInstallers = new OneToSetMap(); // Cache the installer objects to singleton them in case they handle multiple attrs myMapInstallerTypeToInstance = new Dictionary(); foreach(Assembly assembly in Assemblies) // All the assemblies { try { foreach(Type typeInstaller in assembly.GetTypes()) // All the types { // Does the current type declare it installs any attrs? object[] attributes = typeInstaller.GetCustomAttributes(typeof(InstallAttributesAttribute), false); if(attributes.Length == 0) continue; // Create the installer instance, if not created yet IInstallAttributes installer; if(!myMapInstallerTypeToInstance.TryGetValue(typeInstaller, out installer)) { object objectInstaller = Activator.CreateInstance(typeInstaller); installer = objectInstaller as IInstallAttributes; if(installer == null) throw new InvalidOperationException(string.Format("The attribute-installer object of type “{0}” does not implement the required “{1}” interface.", typeInstaller.FullName, typeof(IInstallAttributes).FullName)); myMapInstallerTypeToInstance.Add(typeInstaller, installer); } // Add attributes foreach(InstallAttributesAttribute attribute in attributes) { if(attribute.AttributeToInstall != null) // A single-time installer { // Duplicate? if((myMapAttributeToInstallers.ContainsKey(attribute.AttributeToInstall)) && (myMapAttributeToInstallers[attribute.AttributeToInstall].Contains(installer))) throw new InvalidOperationException(string.Format("The installer class “{0}” registers for the “{1}” attribute twice.", typeInstaller.FullName, attribute.AttributeToInstall.FullName)); myMapAttributeToInstallers.Add(attribute.AttributeToInstall, installer); } } } } catch(Exception ex) { throw new InvalidOperationException(string.Format("Failed to collect attribute-installers from the “{0}” assembly. {1}", assembly.FullName, ex.Message), ex); } } } /// /// Invokes the registration handlers for assembly attributes, see , from the assemblies listed in the AllAssemblies.xml. /// [NotNull] protected InstallationDataXml InvokeAttributeInstallersInstance(InstallationDataXml retval) { // Process each known assembly foreach(Assembly assembly in Assemblies) { // Invoke registration try { foreach(var pair in MapAttributeToInstallers) { foreach(object attribute in assembly.GetCustomAttributes(pair.Key, false)) { foreach(IInstallAttributes installer in pair.Value) { try { // Collect installation data! InstallationDataXml data = installer.InstallInstance(this, attribute); if(data != null) retval.MergeWith(data); } catch(Exception ex) { throw new InvalidOperationException(string.Format("Failed to collect the installation data for the attribute of type “{0}” from the assembly “{1}” using the “{2}” installer. {3}", pair.Key.AssemblyQualifiedName, assembly.FullName, installer.GetType().AssemblyQualifiedName, ex.Message), ex); } } } } } catch(Exception ex) { throw new InvalidOperationException(string.Format("Failed to process the “{0}” assembly. {1}", assembly.FullName, ex.Message), ex); } } return retval; } /// /// Collects the one-time global registration data from the attribute installers, one that is not per-attribute or per-assembly. /// Invoked from , don't call manually. /// protected void InvokeAttributeInstallersStatic(InstallationDataXml total) { foreach(IInstallAttributes installer in MapInstallerTypeToInstance.Values) { InstallationDataXml data = installer.InstallStatic(this); if(data != null) total.MergeWith(data); } } /// /// Maps the attributes storing the installation data to the objects that process their registration and unregistration. /// protected OneToSetMap MapAttributeToInstallers { get { if(myMapAttributeToInstallers == null) CollectAttributeInstallers(); if(myMapAttributeToInstallers == null) throw new InvalidOperationException(string.Format("Failed to collect the attribute installers.")); return myMapAttributeToInstallers; } } /// /// Gets or sets the resolver that allows to get a physical file system path for the given source file system directory root. /// TODO: internal because of the temporary Func`2 problems, should be made public when solved. /// internal Func ResolveSourceDirRoot { get { if(myResolveSourceDirRoot == null) throw new InvalidOperationException(string.Format("The installer has not been provided with this information.")); return myResolveSourceDirRoot; } set { if(myResolveSourceDirRoot != null) throw new InvalidOperationException(string.Format("This value may be set only once.")); myResolveSourceDirRoot = value; } } #endregion #region Overrides ~Installer() { Dispose(); } #endregion #region IDisposable Members /// /// Clean up any resources being used. /// public void Dispose() { GC.SuppressFinalize(this); } #endregion } }