context: // NAnt - A .NET build tool
  // Copyright (C) 2002-2003 Gordon Weakliem
  //
  // This program is free software; you can redistribute it and/or modify
  // it under the terms of the GNU General Public License as published by
  // the Free Software Foundation; either version 2 of the License, or
  // (at your option) any later version.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License
  // along with this program; if not, write to the Free Software
  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  //
  // Gordon Weakliem (gweakliem@oddpost.com)
  // Gert Driesen (driesen@users.sourceforge.net)
  // Ian MacLean (ian_maclean@another.com)
  // Giuseppe Greco (giuseppe.greco@agamura.com)
  
  using System;
  using System.CodeDom;
  using System.CodeDom.Compiler;
  using System.Collections.Specialized;
  using System.Globalization;
  using System.IO;
  using System.Reflection;
  using System.Runtime.Remoting.Lifetime;
  using System.Security.Cryptography;
  using System.Text;
  
  using NAnt.Core;
  using NAnt.Core.Attributes;
  using NAnt.Core.Types;
  using NAnt.Core.Util;
  
  using NAnt.DotNet.Types;
  
  namespace NAnt.DotNet.Tasks {
      /// <summary>
      /// Generates an AssemblyInfo file using the attributes given.
      /// </summary>
      /// <example>
      ///   <para>
      ///   Create a C# AssemblyInfo file containing the specified assembly-level 
      ///   attributes.
      ///   </para>
      ///   <code>
      ///     <![CDATA[
      /// <asminfo output="AssemblyInfo.cs" language="CSharp">
      ///     <imports>
      ///         <import namespace="System" />
      ///         <import namespace="System.Reflection" />
      ///         <import namespace="System.EnterpriseServices" />
      ///         <import namespace="System.Runtime.InteropServices" />
      ///     </imports>
      ///     <attributes>
      ///         <attribute type="ComVisibleAttribute" value="false" />
      ///         <attribute type="CLSCompliantAttribute" value="true" />
      ///         <attribute type="AssemblyVersionAttribute" value="1.0.0.0" />
      ///         <attribute type="AssemblyTitleAttribute" value="My fun assembly" />
      ///         <attribute type="AssemblyDescriptionAttribute" value="More fun than a barrel of monkeys" />
      ///         <attribute type="AssemblyCopyrightAttribute" value="Copyright (c) 2002, Monkeyboy, Inc." />
      ///         <attribute type="ApplicationNameAttribute" value="FunAssembly" />
      ///     </attributes>
      ///     <references>
      ///         <include name="System.EnterpriseServices.dll" />
      ///     </references>
      /// </asminfo>
      ///     ]]>
      ///   </code>
      /// </example>
      /// <example>
      ///     <para>
      ///     Create a C# AssemblyInfo file containing an attribute with multiple
      ///     named properties by setting the <see cref="AssemblyAttribute.AsIs" /> 
      ///     attribute on the <see cref="AssemblyAttribute" /> element to 
      ///     <see langword="true" />.
      ///     </para>
      ///   <code>
      ///     <![CDATA[
      /// <asminfo output="AssemblyInfo.cs" language="CSharp">
      ///     <imports>
      ///         <import namespace="log4net.Config" />
      ///     </imports>
      ///     <attributes>
      ///         <attribute type="DOMConfiguratorAttribute" value="ConfigFile=&quot;config.log4net&quot;,Watch=true" asis="true" />
      ///     </attributes>
      ///     <references>
      ///         <include name="log4net.dll" />
      ///     </references>
      /// </asminfo>
      ///     ]]>
      ///   </code>
      /// </example>
      [TaskName("asminfo")]
      [Serializable()]
      public class AssemblyInfoTask : Task {
          #region Private Instance Fields
  
          private FileInfo _output;
          private CodeLanguage _language = CodeLanguage.CSharp;
          private AssemblyAttributeCollection _attributes = new AssemblyAttributeCollection();
          private NamespaceImportCollection _imports = new NamespaceImportCollection();
          private AssemblyFileSet _references = new AssemblyFileSet();
  
          #endregion Private Instance Fields
  
          #region Public Instance Properties
  
          /// <summary>
          /// Name of the AssemblyInfo file to generate.
          /// </summary>
          /// <value>
          /// The name of the AssemblyInfo file to generate.
          /// </value>
          [TaskAttribute("output", Required=true)]
          public FileInfo Output {
              get { return _output; }
              set { _output = value; }
          }
  
          /// <summary>
          /// The code language in which the AssemblyInfo file should be 
          /// generated.
          /// </summary>
          [TaskAttribute("language", Required=true)]
          [StringValidator(AllowEmpty=false)]
          public CodeLanguage Language {
              get { return _language; }
              set { 
                  if (!Enum.IsDefined(typeof(CodeLanguage), value)) {
                      throw new ArgumentException(string.Format(
                          CultureInfo.InvariantCulture,
                          ResourceUtils.GetString("NA2002"), value)); 
                  } else {
                      _language = value;
                  }
              }
          }
  
          /// <summary>
          /// The assembly-level attributes to generate.
          /// </summary>
          /// <value>
          /// The assembly-level attributes to generate.
          /// </value>
          [BuildElementCollection("attributes", "attribute")]
          public AssemblyAttributeCollection AssemblyAttributes {
              get { return _attributes; }
          }
  
          /// <summary>
          /// The namespaces to import.
          /// </summary>
          /// <value>
          /// The namespaces to import.
          /// </value>
          [BuildElement("imports")]
          public NamespaceImportCollection Imports {
              get { return _imports; }
              set { _imports = value; }
          }
  
          /// <summary>
          /// Assembly files used to locate the types of the specified attributes.
          /// </summary>
          [BuildElement("references")]
          public AssemblyFileSet References {
              get { return _references; }
              set { _references = value; }
          }
  
          #endregion Public Instance Properties
  
          #region Override implementation of Task
  
          /// <summary>
          /// Generates an AssemblyInfo file.
          /// </summary>
          protected override void ExecuteTask() {
              try {
                  StringCollection imports = new StringCollection();
  
                  foreach (NamespaceImport import in Imports) {
                      if (import.IfDefined && !import.UnlessDefined) {
                          imports.Add(import.Namespace);
                      }
                  }
  
                  // ensure base directory is set, even if fileset was not initialized
                  // from XML
                  if (References.BaseDirectory == null) {
                      References.BaseDirectory = new DirectoryInfo(Project.BaseDirectory);
                  }
  
                  // write out code to memory stream, so we can compare it later 
                  // to what is already present (if necessary)
                  MemoryStream generatedAsmInfoStream = new MemoryStream();
                  
                  using (StreamWriter writer = new StreamWriter(generatedAsmInfoStream, Encoding.Default)) {
                      // create new instance of CodeProviderInfo for specified CodeLanguage
                      CodeProvider codeProvider = new CodeProvider(this, Language);
  
                      // only generate imports here for C#, for VB we create the 
                      // imports as part of the assembly attributes compile unit
                      if (Language == CodeLanguage.CSharp) {
                          // generate imports code
                          codeProvider.GenerateImportCode(imports, writer);
                      }
  
                      // generate code for assembly attributes
                      codeProvider.GenerateAssemblyAttributesCode(AssemblyAttributes, 
                          imports, References.FileNames, writer);
  
                      // flush 
                      writer.Flush();
  
                      // check whether generated source should be persisted
                      if (NeedsPersisting(generatedAsmInfoStream)) {
                          using (FileStream fs = new FileStream(Output.FullName, FileMode.Create, FileAccess.Write)) {
                              byte[] buffer = generatedAsmInfoStream.ToArray();
                              fs.Write(buffer, 0, buffer.Length);
                              fs.Flush();
                              fs.Close();
                              generatedAsmInfoStream.Close();
                          }
  
                          Log(Level.Info, ResourceUtils.GetString("String_GeneratedFile"),
                              Output.FullName);
                      } else {
                          Log(Level.Verbose, ResourceUtils.GetString("String_FileUpToDate"),
                              Output.FullName);
                      }
                  }
              } catch (Exception ex) {
                  throw new BuildException(string.Format(
                      CultureInfo.InvariantCulture,
                      ResourceUtils.GetString("NA2004"), Output.FullName), Location, ex);
              }
          }
  
          #endregion Override implementation of Task
  
          #region Private Instance Methods
  
          /// <summary>
          /// Determines whether the specified AssemblyInfo file in the given
          /// <see cref="Stream" /> needs to be persisted.
          /// </summary>
          /// <param name="generatedAsmInfoStream"><see cref="Stream" /> holding the newly generated AssemblyInfo source.</param>
          /// <returns>
          /// <see langword="true" /> if the generated AssemblyInfo source needs
          /// to be persisted; otherwise, <see langword="false" />.
          /// </returns>
          private bool NeedsPersisting(Stream generatedAsmInfoStream) {
              // if output file doesn't exist, the stream will always need to be
              // persisted to the filesystem.
              if (!Output.Exists) {
                  Log(Level.Verbose, ResourceUtils.GetString("String_OutputFileDoesNotExist"),
                      Output.FullName);
                  return true;
              }
  
              byte[] existingAssemblyInfoHash = null;
              byte[] generatedAssemblyInfoHash = null;
  
              SHA1 hasher = new SHA1CryptoServiceProvider();
  
              // hash existing AssemblyInfo source
              using (FileStream fs = new FileStream(Output.FullName, FileMode.Open, FileAccess.Read)) {
                  existingAssemblyInfoHash = hasher.ComputeHash(fs);
              }
  
              // hash generated AssemblyInfo source
              generatedAsmInfoStream.Position = 0;
              generatedAssemblyInfoHash = hasher.ComputeHash(generatedAsmInfoStream);
  
              // release all resources
              hasher.Clear();
  
              //compare hash of generated source with of existing source
              if (Convert.ToBase64String(generatedAssemblyInfoHash) != Convert.ToBase64String(existingAssemblyInfoHash)) {
                  Log(Level.Verbose, ResourceUtils.GetString("String_OutputFileNotUpToDate"), 
                      Output.FullName);
                  return true;
              } else {
                  return false;
              }
          }
  
          #endregion Private Instance Methods
  
          /// <summary>
          /// Defines the supported code languages for generating an AssemblyInfo
          /// file.
          /// </summary>
          public enum CodeLanguage : int {
              /// <summary>
              /// A value for generating C# code.
              /// </summary>
              CSharp = 0,
  
              /// <summary>
              /// A value for generating JScript code.
              /// </summary>
              JScript = 1,
  
              /// <summary>
              /// A value for generating Visual Basic code.
              /// </summary>
              VB = 2,
          }
  
          /// <summary> 
          /// Encapsulates functionality to generate a code file with imports
          /// and assembly-level attributes.
          /// </summary>
          internal class CodeProvider {
              #region Private Instance Fields
  
              private readonly CodeLanguage _language;
              private readonly ICodeGenerator _generator;
  
              #endregion Private Instance Fields
  
              #region Public Instance Constructors
  
              /// <summary>
              /// Initializes a new instance of the <see cref="CodeProvider" />
              /// for the specified <see cref="CodeLanguage" />.
              /// </summary>
              /// <param name="assemblyInfoTask">The <see cref="AssemblyInfoTask" /> for which an instance of the <see cref="CodeProvider" /> class should be initialized.</param>
              /// <param name="codeLanguage">The <see cref="CodeLanguage" /> for which an instance of the <see cref="CodeProvider" /> class should be initialized.</param>
              public CodeProvider(AssemblyInfoTask assemblyInfoTask, CodeLanguage codeLanguage) {
                  CodeDomProvider provider = null;
  
                  switch (codeLanguage) {
                      case CodeLanguage.CSharp:
                          provider = new Microsoft.CSharp.CSharpCodeProvider();
                          break;
                      case CodeLanguage.JScript:
                          throw new NotSupportedException(ResourceUtils.GetString("NA2008"));
                      case CodeLanguage.VB:
                          provider = new Microsoft.VisualBasic.VBCodeProvider();
                          break;
                      default:
                          throw new NotSupportedException(ResourceUtils.GetString("NA2007"));
                  }
  
                  _generator = provider.CreateGenerator();
                  _language = codeLanguage;
              }
  
              #endregion Public Instance Constructors
      
              #region Private Instance Properties
  
              /// <summary>
              /// Gets the <see cref="CodeLanguage" /> in which the AssemblyInfo
              /// code will be generated.
              /// </summary>
              private CodeLanguage Language {
                  get { return _language; }
              }
  
              /// <summary>
              /// Gets the <see cref="ICodeGenerator" /> that will be used to 
              /// generate the AssemblyInfo code.
              /// </summary>
              private ICodeGenerator Generator {
                  get { return _generator; }
              }
  
              #endregion Private Instance Properties
  
              #region Public Instance Methods
  
              /// <summary>
              /// Generates code for the specified imports.
              /// </summary>
              /// <param name="imports">The imports for which code should be generated.</param>
              /// <param name="writer">The <see cref="TextWriter" /> to which the generated code will be written.</param>
              public void GenerateImportCode(StringCollection imports, TextWriter writer) {
                  CodeNamespace codeNamespace = new CodeNamespace();
  
                  foreach (string import in imports) {
                      codeNamespace.Imports.Add(new CodeNamespaceImport(import));
                  }
  
                  Generator.GenerateCodeFromNamespace(codeNamespace, writer, new CodeGeneratorOptions());
              }
  
              /// <summary>
              /// Generates code for the specified assembly attributes.
              /// </summary>
              /// <param name="assemblyAttributes">The assembly attributes for which code should be generated.</param>
              /// <param name="imports">Imports used to resolve the assembly attribute names to fully qualified type names.</param>
              /// <param name="assemblies">Assembly that will be used to resolve the attribute names to <see cref="Type" /> instances.</param>
              /// <param name="writer">The <see cref="TextWriter" /> to which the generated code will be written.</param>
              public void GenerateAssemblyAttributesCode(AssemblyAttributeCollection assemblyAttributes, StringCollection imports, StringCollection assemblies, TextWriter writer) {
  
gt:                 CodeCompileUnit codeCompileUnit = new CodeCompileUnit();