CustomGrammar.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ DotNET / DotNET / 8.0 / untmp / WIN_WINDOWS / lh_tools_devdiv_wpf / Windows / wcp / Speech / Src / Internal / SrgsCompiler / CustomGrammar.cs / 1 / CustomGrammar.cs

                            //  
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//
// 
// Description:
// 
// History: 
//      10/1/2004   [....]      Created
//---------------------------------------------------------------------------- 

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel; 
using System.CodeDom.Compiler;
using System.Globalization; 
using System.IO; 
using System.Reflection;
using System.Reflection.Emit; 
using System.Resources;
using System.Speech.Recognition;
using System.Speech.Internal.SrgsParser;
using System.Text; 
using Microsoft.CSharp;
using Microsoft.VisualBasic; 
//using Microsoft.VJSharp; 

#pragma warning disable 1634, 1691 // Allows suppression of certain PreSharp messages. 
#pragma warning disable 56507 // check for null or empty strings

#if !NO_STG
 
namespace System.Speech.Internal.SrgsCompiler
{ 
    ///  
    /// Summary description for CustomGrammar.
    ///  
    internal class CustomGrammar
    {
        //*******************************************************************
        // 
        // Constructors
        // 
        //******************************************************************* 

        #region Constructors 

        internal CustomGrammar ()
        {
        } 

        #endregion 
 
        //*******************************************************************
        // 
        // Internal methods
        //
        //********************************************************************
 
        #region internal methods
 
        ///  
        /// Creates the assembly and makes sure the it is valid.
        /// Creates the constructor for the grammar 
        /// 
        internal string CreateAssembly (int iCfg, string outputFile, CultureInfo culture)
        {
            // Temporary file for the IL 
            // Limitation to the ICompiler interfaces!
            string code = null; 
            File.Delete (outputFile); 

            try 
            {
                CreateAssembly (outputFile, false, null);

                // Check the validity of the code only on individual grammars 
                CheckValidAssembly (iCfg, ExtractCodeGenerated (outputFile));
 
                // Regenerate the code with the constructors added 
                code = GenerateCode (true, culture);
            } 
            finally
            {
                DeleteFile (outputFile);
            } 
            return code;
        } 
 
        /// 
        /// Creates the assembly and makes sure the it is valid. 
        /// Get the IL and PDB for the embeded code for this grammar
        /// 
        /// 
        ///  
        internal void CreateAssembly (out byte [] il, out byte [] pdb)
        { 
            // Temporary file for the IL 
            // Limitation to the ICompiler interfaces!
            string outputFile = Path.GetTempFileName () + ".dll"; 
            File.Delete (outputFile);

            try
            { 
                CreateAssembly (outputFile, _fDebugScript, null);
 
                il = ExtractCodeGenerated (outputFile); 
                pdb = null;
                if (_fDebugScript) 
                {
                    string pdbFile = outputFile.Substring (0, outputFile.LastIndexOf ('.')) + ".pdb";
                    pdb = ExtractCodeGenerated (pdbFile);
                    DeleteFile (pdbFile); 
                }
 
                // Check the validity of the code only on individual grammars 
                CheckValidAssembly (0, il);
 
            }
            finally
            {
                DeleteFile (outputFile); 
            }
        } 
 
        /// 
        /// Create a DLL with the CFG as a resource 
        /// 
        /// 
        /// 
        internal void CreateAssembly (string path, List cfgResources) 
        {
            CreateAssembly (path, _fDebugScript, cfgResources); 
        } 

        ///  
        /// Add the scripts defined in 'cg' to the set of scripts defined in 'cgCombined'.
        /// Build the union of teh codebehind files and assembly references
        /// 
        ///  
        /// 
        internal void Combine (CustomGrammar cg, string innerCode) 
        { 
            if (_rules.Count == 0)
            { 
                _language = cg._language;
            }
            else
            { 
                if (_language != cg._language)
                { 
                    XmlParser.ThrowSrgsException (SRID.IncompatibleLanguageProperties); 
                }
            } 

            if (_namespace == null)
            {
                _namespace = cg._namespace; 
            }
            else 
            { 
                if (_namespace != cg._namespace)
                { 
                    XmlParser.ThrowSrgsException (SRID.IncompatibleNamespaceProperties);
                }
            }
 
            _fDebugScript |= cg._fDebugScript;
 
            foreach (string codebehind in cg._codebehind) 
            {
                if (!_codebehind.Contains (codebehind)) 
                {
                    _codebehind.Add (codebehind);
                }
            } 

            foreach (string assemblyReferences in cg._assemblyReferences) 
            { 
                if (!_assemblyReferences.Contains (assemblyReferences))
                { 
                    _assemblyReferences.Add (assemblyReferences);
                }
            }
 
            foreach (string importNamespaces in cg._importNamespaces)
            { 
                if (!_importNamespaces.Contains (importNamespaces)) 
                {
                    _importNamespaces.Add (importNamespaces); 
                }
            }

            _keyFile = cg._keyFile; 

            _types.AddRange (cg._types); 
            foreach (Rule rule in cg._rules) 
            {
                if (_types.Contains (rule.Name)) 
                {
                    XmlParser.ThrowSrgsException (SRID.RuleDefinedMultipleTimes2, rule.Name);
                }
            } 

            // Combine all the scripts 
            _script.Append (innerCode); 
        }
 
        #endregion

        //*******************************************************************
        // 
        // Internal Properties
        // 
        //******************************************************************** 

        #region Internal Properties 

        internal bool HasScript
        {
            get 
            {
                bool has_script = _script.Length > 0 || _codebehind.Count > 0; 
                if (!has_script) 
                {
                    foreach (Rule rule in _rules) 
                    {
                        if (rule.Script.Length > 0)
                        {
                            has_script = true; 
                            break;
                        } 
                    } 
                }
                return has_script; 
            }
        }

        #endregion 

        //******************************************************************** 
        // 
        // Internal Types
        // 
        //*******************************************************************

        #region Internal Types
 
        internal class CfgResource
        { 
            internal string name; 
            internal byte [] data;
        } 

        #endregion

        //******************************************************************** 
        //
        // Internal Fields 
        // 
        //*******************************************************************
 
        #region Internal Fields

        // 'C#', 'VB' or 'JScript'
        internal string _language = "C#"; 

        // namespace for the class wrapping the inline code 
        internal string _namespace; 

        // namespace for the class wrapping the inline code 
        internal List _rules = new List ();

        // code behind dll
        internal Collection _codebehind = new Collection (); 

        // if set generates #line statements 
        internal bool _fDebugScript; 

        // List of assembly references to import 
        internal Collection _assemblyReferences = new Collection ();

        // List of namespaces to import
        internal Collection _importNamespaces = new Collection (); 

        // Key file for the strong name 
        internal string _keyFile; 

        // CFG scripts definition 
        internal Collection _scriptRefs = new Collection ();

        // inline script
        internal List _types = new List (); 

        // inline script 
        internal StringBuilder _script = new StringBuilder (); 

        #endregion 

        //*******************************************************************
        //
        // Private Methods 
        //
        //******************************************************************* 
 
        #region Private Methods
 
        private void CreateAssembly (string outputFile, bool debug, List cfgResources)
        {
            if (_language == null)
            { 
                XmlParser.ThrowSrgsException (SRID.NoLanguageSet);
            } 
 
            // Get the scrip to compile
            string sourceCode = GenerateCode (false, null); 

            // Script could end up in a file.
            string scriptFile = null;
 
            // List of files to compile; embedded script + code behind file
            string [] files = null; 
 
            try
            { 
                // Add an extra file for the embedded script if code behind files are available
                if (_codebehind.Count > 0)
                {
                    int cFiles = _codebehind.Count + (sourceCode != null ? 1 : 0); 
                    files = new string [cFiles];
 
                    for (int i = 0; i < _codebehind.Count; i++) 
                    {
                        files [i] = _codebehind [i]; 
                    }

                    if (sourceCode != null)
                    { 
                        scriptFile = files [files.Length - 1] = Path.GetTempFileName ();
                        // Write the script in a temporary filename 
                        using (StreamWriter sw = new StreamWriter (scriptFile)) 
                        {
                            sw.Write (sourceCode); 
                        }
                    }
                }
 
                // Compile the code files to [outputFile].dll and [outputFile.pdb
                CompileScript (outputFile, debug, sourceCode, files, cfgResources); 
            } 
            finally
            { 
                DeleteFile (scriptFile);
            }
        }
 
        private void CompileScript (string outputFile, bool debug, string code, string [] codeFiles, List cfgResouces)
        { 
            //string pdbFile = debug ? outputFile.Substring (0, outputFile.LastIndexOf ('.')) + ".pdb" : null; 

            using (CodeDomProvider codeDomProvider = CodeProvider ()) 
            {
                CompilerParameters parameters = GetCompilerParameters (outputFile, cfgResouces, debug, _assemblyReferences, _keyFile);

                CompilerResults results; 
                if (codeFiles != null)
                { 
                    // Compile the set of source files 
                    results = codeDomProvider.CompileAssemblyFromFile (parameters, codeFiles);
                } 
                else
                {
                    // Compile the set of source files
                    results = codeDomProvider.CompileAssemblyFromSource (parameters, code); 
                }
 
                if (results.Errors.Count > 0) 
                {
                    ThrowCompilationErrors (results); 
                }

                if (results.NativeCompilerReturnValue != 0)
                { 
                    XmlParser.ThrowSrgsException (SRID.UnexpectedError, results.NativeCompilerReturnValue);
                } 
            } 
        }
 
        private CodeDomProvider CodeProvider ()
        {
            CodeDomProvider codeDomProvider = null;
            switch (_language) 
            {
                case "C#": 
                    codeDomProvider = CreateCSharpCompiler (); 
                    break;
 
                case "VB.Net":
                    codeDomProvider = CreateVBCompiler ();
                    break;
 
                default:
                    XmlParser.ThrowSrgsException (SRID.UnsupportedLanguage, _language); 
                    break; 
            }
            return codeDomProvider; 
        }

        private string GenerateCode (bool classDefinitionOnly, CultureInfo culture)
        { 
            string script = string.Empty;
 
            // Select an instance of a compiler and wrap the script code with the 
            // class definition
            switch (_language) 
            {
                case "C#":
                    script = WrapScriptCSharp (classDefinitionOnly, culture);
                    break; 

                case "VB.Net": 
                    script = WrapScriptVB (classDefinitionOnly, culture); 
                    break;
 
                default:
                    XmlParser.ThrowSrgsException (SRID.UnsupportedLanguage, _language);
                    break;
            } 

            return script; 
        } 

        private string WrapScriptCSharp (bool classDefinitionOnly, CultureInfo culture) 
        {
            StringBuilder sbClasses = new StringBuilder ();

            // Combine all the classes into a single text 
            foreach (Rule rule in _rules)
            { 
                if (rule.Script != null) 
                {
                    WrapClassCSharp (sbClasses, rule.Name, rule.BaseClass, culture, rule.Script.ToString (), rule.Constructors.ToString ()); 
                }
            }

            // Add the global scripts 
            if (_script.Length > 0)
            { 
                sbClasses.Append (_script); 
            }
 
            // Add the using and name space definition
            return sbClasses.Length > 0 ? !classDefinitionOnly ? WrapScriptOuterCSharp (sbClasses.ToString ()) : sbClasses.ToString () : null;
        }
 
        private string WrapScriptVB (bool classDefinitionOnly, CultureInfo culture)
        { 
            StringBuilder sbClasses = new StringBuilder (); 

            // Combine all the classes into a single text 
            foreach (Rule rule in _rules)
            {
                if (rule.Script != null)
                { 
                    WrapClassVB (sbClasses, rule.Name, rule.BaseClass, culture, rule.Script.ToString (), rule.Constructors.ToString ());
                } 
            } 

            // Add the global scripts 
            if (_script.Length > 0)
            {
                sbClasses.Append (_script);
            } 

            // Add the using and name space definition 
            return sbClasses.Length > 0 ? !classDefinitionOnly ? WrapScriptOuterVB (sbClasses.ToString ()) : sbClasses.ToString () : null; 
        }
 
        /// 
        /// The CSharp assembly is loaded on the first call to the CSharpCodeProvider.
        /// Keeps this routine outside of CreateAssembly to avoid loading both the
        /// CSharp compiler, the VB compiler and JSccript compiler. 
        /// 
        ///  
        static private CodeDomProvider CreateCSharpCompiler () 
        {
            return new CSharpCodeProvider (); 
        }

        private string WrapScriptOuterCSharp (string innerCode)
        { 
            // Add the using and name space definition
            if (!string.IsNullOrEmpty (innerCode)) 
            { 
                // quick estimate for the string builder size
                int cNamespacesStrings = 0; 
                foreach (string importNamespace in _importNamespaces)
                {
                    cNamespacesStrings += importNamespace.Length;
                } 

                // Find the local namespace, System.Speech or Microsoft.SpeechServer 
                SRID srid = SRID.ArrayOfNullIllegal; 
                string speechNamespace = srid.GetType ().Namespace;
 
                // Add the using
                string usingStatements = string.Format (CultureInfo.InvariantCulture, "#line 1 \"{0}\"\nusing System;\nusing System.Collections.Generic;\nusing System.Diagnostics;\nusing {1};\nusing {1}.Recognition;\nusing {1}.Recognition.SrgsGrammar;\n", _preambuleMarker, speechNamespace);
                StringBuilder sbWhole = new StringBuilder (_script.Length + usingStatements.Length + 200);
 
                sbWhole.Append (usingStatements);
                foreach (string importNamespace in _importNamespaces) 
                { 
                    sbWhole.Append ("using ");
                    sbWhole.Append (importNamespace); 
                    sbWhole.Append (";\n");
                }

                // Add the namespace definition 
                if (_namespace != null)
                { 
                    sbWhole.Append ("namespace "); 
                    sbWhole.Append (_namespace);
                    sbWhole.Append ("\n{\n"); 
                }

                // Add all the classes
                sbWhole.Append (innerCode); 

                // close the namespace if any 
                if (_namespace != null) 
                {
                    sbWhole.Append ("}\n"); 
                }
                return sbWhole.ToString ();
            }
            else 
            {
                return null; 
            } 
        }
 
        private static void WrapClassCSharp (StringBuilder sb, string classname, string baseclass, CultureInfo culture, string script, string constructor)
        {
            // Add the class definition
            sb.Append ("public partial class "); 
            sb.Append (classname);
            sb.Append (" : "); 
            sb.Append (!string.IsNullOrEmpty (baseclass) ? baseclass : "Grammar"); 
            sb.Append (" \n {\n");
 
            // Only append the Association table for STG files.
            if (culture != null)
            {
                // Append the association class to CFGs 
                sb.Append ("[DebuggerBrowsable (DebuggerBrowsableState.Never)]public static string __cultureId = \"");
                sb.Append (culture.LCID.ToString (CultureInfo.InvariantCulture)); 
                sb.Append ("\";\n"); 
            }
 
            // The constructor if any
            sb.Append (constructor);

            // Add the user script 
            sb.Append (script);
 
            // override the propert IsStg to set it to tru; 
            sb.Append ("override protected bool IsStg { get { return true; }}\n\n");
 
            // close the class
            sb.Append ("\n}\n");
        }
 

        ///  
        /// The VB assembly is loaded on the first call to the CSharpCodeProvider. 
        /// Keeps this routine outside of CreateAssembly to avoid loading both the
        /// CSharp compiler, the VB compiler and JSccript compiler. 
        /// 
        /// 
        static private CodeDomProvider CreateVBCompiler ()
        { 
            return new VBCodeProvider ();
        } 
 
        private string WrapScriptOuterVB (string innerCode)
        { 
            // Add the using and name space definition
            if (!string.IsNullOrEmpty (innerCode))
            {
                // quick estimate for the string builder size 
                int cNamespacesStrings = 0;
                foreach (string importNamespace in _importNamespaces) 
                { 
                    cNamespacesStrings += importNamespace.Length;
                } 

                // Find the local namespace, System.Speech or Microsoft.SpeechServer
                SRID srid = SRID.ArrayOfNullIllegal;
                string speechNamespace = srid.GetType ().Namespace; 

                // Add the using 
                string usingStatements = string.Format (CultureInfo.InvariantCulture, "#ExternalSource (\"{0}\", 1)\nImports System\nImports System.Collections.Generic\nImports System.Diagnostics\nImports {1}\nImports {1}.Recognition\nImports {1}.Recognition.SrgsGrammar\n", _preambuleMarker, speechNamespace); 
                StringBuilder sbWhole = new StringBuilder (_script.Length + usingStatements.Length + 200);
 
                sbWhole.Append (usingStatements);
                foreach (string importNamespace in _importNamespaces)
                {
                    sbWhole.Append ("Imports "); 
                    sbWhole.Append (importNamespace);
                    sbWhole.Append ("\n"); 
                } 

                // Add the namespace definition 
                if (_namespace != null)
                {
                    sbWhole.Append ("Namespace ");
                    sbWhole.Append (_namespace); 
                    sbWhole.Append ("\n");
                } 
 
                sbWhole.Append ("#End ExternalSource\n");
 
                // Add all the classes
                sbWhole.Append (innerCode);

                // close the namespace if any 
                if (_namespace != null)
                { 
                    sbWhole.Append ("End Namespace\n"); 
                }
                return sbWhole.ToString (); 
            }
            else
            {
                return null; 
            }
        } 
 
        private static void WrapClassVB (StringBuilder sb, string classname, string baseclass, CultureInfo culture, string script, string constructor)
        { 
            // Add the class definition
            sb.Append ("Public Partial class ");
            sb.Append (classname);
            sb.Append ("\n Inherits "); 
            sb.Append (!string.IsNullOrEmpty (baseclass) ? baseclass : "Grammar");
            sb.Append (" \n"); 
 
            // Only append the Association table for STG files.
            if (culture != null) 
            {
                // Append the association class to CFGs
                sb.Append ("Public Shared __cultureId as String = \"");
                sb.Append (culture.LCID.ToString (CultureInfo.InvariantCulture)); 
                sb.Append ("\"\n");
            } 
 
            // The constructor if any
            sb.Append (constructor); 

            // Add the user script
            sb.Append (script);
 
            // override the propert IsStg to set it to tru;
            sb.Append ("Protected Overrides ReadOnly Property IsStg() As Boolean\nGet\nReturn True\nEnd Get\nEnd Property\n"); 
 
            // close the class
            sb.Append ("\nEnd Class\n"); 
        }

        private static void ThrowCompilationErrors (CompilerResults results)
        { 
            StringBuilder sbErrors = new StringBuilder ();
            foreach (CompilerError error in results.Errors) 
            { 
                if (sbErrors.Length > 0)
                { 
                    sbErrors.Append ("\n");
                }
                if (error.FileName.IndexOf (_preambuleMarker, StringComparison.Ordinal) == -1)
                { 
                    sbErrors.Append (error.FileName);
                    sbErrors.Append ("("); 
                    sbErrors.Append (error.Line); 
                    sbErrors.Append (",");
                    sbErrors.Append (error.Column); 
                    sbErrors.Append ("): ");
                }

                sbErrors.Append ("error "); 
                sbErrors.Append (error.ErrorNumber);
                sbErrors.Append (": "); 
                sbErrors.Append (error.ErrorText); 
            }
            XmlParser.ThrowSrgsException (SRID.GrammarCompilerError, sbErrors.ToString ()); 
        }

        private static CompilerParameters GetCompilerParameters (string outputFile, List cfgResources, bool debug, Collection assemblyReferences, string keyfile)
        { 
            CompilerParameters parameters = new CompilerParameters ();
            StringBuilder compilerOptions = new StringBuilder (); 
 
            // Get the compiler to use
            parameters.GenerateInMemory = false; 
            parameters.OutputAssembly = outputFile;

            // Set the debug flag
            if (parameters.IncludeDebugInformation = debug) 
            {
                // Add the debug FLAG 
                compilerOptions.Append ("/define:DEBUG "); 
            }
 
            // Set the key file if any
            if (keyfile != null)
            {
                // Add the keyfile flag 
                compilerOptions.Append ("/keyfile:");
                compilerOptions.Append (keyfile); 
            } 
            parameters.CompilerOptions = compilerOptions.ToString ();
 
            // add all the referenced dll
            parameters.ReferencedAssemblies.Add ("System.dll");

            // add the assembly for System.Speech 
            Assembly assembly = Assembly.GetExecutingAssembly ();
            parameters.ReferencedAssemblies.Add (assembly.Location); 
 
            foreach (string assemblyReference in assemblyReferences)
            { 
                parameters.ReferencedAssemblies.Add (assemblyReference);
            }

            // add the cfgs as resources if any 
            if (cfgResources != null)
            { 
                foreach (CfgResource cfgResource in cfgResources) 
                {
                    using (FileStream fs = new FileStream (cfgResource.name, FileMode.Create, FileAccess.Write)) 
                    {
                        using (BinaryWriter sw = new BinaryWriter (fs))
                        {
                            sw.Write (cfgResource.data, 0, cfgResource.data.Length); 
                            parameters.EmbeddedResources.Add (cfgResource.name);
                        } 
                    } 
                }
            } 
            return parameters;
        }

        private static void DeleteFile (string path) 
        {
            // Delete the temporary files if any 
            if (!string.IsNullOrEmpty (path)) 
            {
                try 
                {
                    File.Delete (path);
                }
#pragma warning disable 56502 // Remove the empty catch statements warnings 
                catch (IOException)
                { 
                } 
#pragma warning restore 56502
            } 
        }

        private void CheckValidAssembly (int iCfg, byte [] il)
        { 
#if true
            // Check all methods referenced in the rule; availability, public and arguments 
            Assembly executingAssembly = Assembly.GetExecutingAssembly (); 
            AppDomain appDomain = null;
            try 
            {
                appDomain = AppDomain.CreateDomain ("Loading Domain");
                AppDomainCompilerProxy proxy = (AppDomainCompilerProxy) appDomain.CreateInstanceFromAndUnwrap (executingAssembly.GetName ().CodeBase, "System.Speech.Internal.SrgsCompiler.AppDomainCompilerProxy");
#else 
                AppDomainCompilerProxy proxy = new AppDomainCompilerProxy ();
#endif 
 
                // Marshalling between App domains prevents to use complex types as they cannot
                // be marhalled accross app domain boundaries. Use 3 arrays instead 
                int count = _scriptRefs.Count;
                string [] rules = new string [count];
                string [] methods = new string [count];
                int [] methodScripts = new int [count]; 

                for (int i = 0; i < count; i++) 
                { 
                    ScriptRef scriptRef = _scriptRefs [i];
                    rules [i] = scriptRef._rule; 
                    methods [i] = scriptRef._sMethod;
                    methodScripts [i] = (int) scriptRef._method;
                }
 
                // Marshalling of all parameters must be achieved
                Exception e = proxy.CheckAssembly (il, iCfg, _language, _namespace, rules, methods, methodScripts); 
 
                // Throw the error if any
                if (e != null) 
                {
                    throw e;
                }
 
                // Get the constructors and the types
                AssociateConstructorsWithRules (proxy, rules, _rules, iCfg, _language); 
 
#if true
            } 
            finally
            {
                if (appDomain != null)
                { 
                    AppDomain.Unload (appDomain);
                    appDomain = null; 
                } 
            }
#endif 
        }

        static private void AssociateConstructorsWithRules (AppDomainCompilerProxy proxy, string [] names, List rules, int iCfg, string language)
        { 
            string [] constructors = proxy.Constructors ();
 
            // Build the constructors for the 
            foreach (Rule rule in rules)
            { 
                int i = 0;
                for (; i < names.Length && (i = Array.IndexOf (names, rule.Name, i)) >= 0; i++)
                {
                    if (constructors [i] != null) 
                    {
                        rule.Constructors.Append (constructors [i]); 
                    } 
                }
                if (rule.Constructors.Length == 0) 
                {
                    rule.Constructors.Append (proxy.GenerateConstructor (iCfg, new ParameterInfo [0], language, rule.Name));
                }
            } 
        }
 
        private static byte [] ExtractCodeGenerated (string path) 
        {
            byte [] data = null; 
            if (!string.IsNullOrEmpty (path))
            {
                // return the memory blob with the IL for .Net Semantics
                using (FileStream fs = new FileStream (path, FileMode.Open, FileAccess.Read, FileShare.Read)) 
                {
                    data = Helpers.ReadStreamToByteArray (fs, (int) fs.Length); 
                } 
            }
            return data; 
        }
        #endregion

        private const string _preambuleMarker = ""; 
    }
} 
#endif 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK