diff --git a/umbraco/presentation.ClientDependency/ClientDependencyHelper.cs b/umbraco/presentation.ClientDependency/ClientDependencyHelper.cs index 6da31df8ff..005749b8c7 100644 --- a/umbraco/presentation.ClientDependency/ClientDependencyHelper.cs +++ b/umbraco/presentation.ClientDependency/ClientDependencyHelper.cs @@ -6,98 +6,14 @@ using System.Web; using umbraco.presentation.ClientDependency.Providers; using System.Web.Configuration; using System.Configuration.Provider; +using umbraco.presentation.ClientDependency.Config; namespace umbraco.presentation.ClientDependency { public class ClientDependencyHelper { - - private static ClientDependencyProvider m_Provider = null; - private static ClientDependencyProviderCollection m_Providers = null; - private static List m_Extensions; - private static object m_Lock = new object(); - - public static ClientDependencyProvider DefaultProvider - { - get - { - LoadProviders(); - return m_Provider; - } - } - public static ClientDependencyProviderCollection ProviderCollection - { - get - { - LoadProviders(); - return m_Providers; - } - } - - /// - /// The file extensions of Client Dependencies that are file based as opposed to request based. - /// Any file that doesn't have the extensions listed here will be request based, request based is - /// more overhead for the server to process. - /// - /// - /// A request based JavaScript file may be a .ashx that dynamically creates JavaScript server side. - /// - /// - /// If this is not explicitly set, then the extensions 'js' and 'css' are the defaults. - /// - public static List FileBasedDependencyExtensionList - { - get - { - LoadProviders(); - return m_Extensions; - } - } - - private static void LoadProviders() - { - if (m_Provider == null) - { - lock (m_Lock) - { - // Do this again to make sure _provider is still null - if (m_Provider == null) - { - ClientDependencySection section = (ClientDependencySection)WebConfigurationManager.GetSection("system.web/clientDependency"); - - m_Providers = new ClientDependencyProviderCollection(); - - // if there is no section found, then add the standard providers to the collection with the standard - // default provider - if (section != null) - { - // Load registered providers and point _provider to the default provider - ProvidersHelper.InstantiateProviders(section.Providers, m_Providers, typeof(ClientDependencyProvider)); - m_Provider = m_Providers[section.DefaultProvider]; - if (m_Provider == null) - throw new ProviderException("Unable to load default ClientDependency provider"); - } - else - { - //get the default settings - section = new ClientDependencySection(); - m_Extensions = section.FileBasedDependencyExtensionList; - - PageHeaderProvider php = new PageHeaderProvider(); - php.Initialize(PageHeaderProvider.DefaultName, null); - m_Providers.Add(php); - ClientSideRegistrationProvider csrp = new ClientSideRegistrationProvider(); - csrp.Initialize(ClientSideRegistrationProvider.DefaultName, null); - m_Providers.Add(csrp); - - //set the default - m_Provider = m_Providers[section.DefaultProvider]; - } - } - } - } - } + /// /// Registers dependencies with the default provider @@ -106,8 +22,8 @@ namespace umbraco.presentation.ClientDependency /// public static void RegisterClientDependencies(Control control, ClientDependencyPathCollection paths) { - LoadProviders(); - ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths, m_Provider); + ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths, + ClientDependencySettings.Instance.DefaultProvider); service.ProcessDependencies(); } @@ -119,8 +35,8 @@ namespace umbraco.presentation.ClientDependency /// public static void RegisterClientDependencies(string providerName, Control control, ClientDependencyPathCollection paths) { - LoadProviders(); - ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths, m_Providers[providerName]); + ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths, + ClientDependencySettings.Instance.ProviderCollection[providerName]); service.ProcessDependencies(); } @@ -130,10 +46,9 @@ namespace umbraco.presentation.ClientDependency public static void RegisterClientDependencies(Control control, ClientDependencyPathCollection paths) where T: ClientDependencyProvider { - LoadProviders(); //need to find the provider with the type ClientDependencyProvider found = null; - foreach (ClientDependencyProvider p in m_Providers) + foreach (ClientDependencyProvider p in ClientDependencySettings.Instance.ProviderCollection) { if (p.GetType().Equals(typeof(T))) { @@ -149,7 +64,6 @@ namespace umbraco.presentation.ClientDependency public static void RegisterClientDependencies(ClientDependencyProvider provider, Control control, ClientDependencyPathCollection paths) { - LoadProviders(); ClientDependencyRegistrationService service = new ClientDependencyRegistrationService(control, paths, provider); service.ProcessDependencies(); } diff --git a/umbraco/presentation.ClientDependency/ClientDependencyLoader.cs b/umbraco/presentation.ClientDependency/ClientDependencyLoader.cs index 1f7f7bf2c5..306b1488ce 100644 --- a/umbraco/presentation.ClientDependency/ClientDependencyLoader.cs +++ b/umbraco/presentation.ClientDependency/ClientDependencyLoader.cs @@ -4,6 +4,7 @@ using System.Text; using System.Web.UI; using System.Web; using umbraco.presentation.ClientDependency.Providers; +using umbraco.presentation.ClientDependency.Config; namespace umbraco.presentation.ClientDependency { @@ -69,12 +70,12 @@ namespace umbraco.presentation.ClientDependency switch (EmbedType) { case ClientDependencyEmbedType.Header: - provider = ClientDependencyHelper.ProviderCollection[PageHeaderProvider.DefaultName]; + provider = ClientDependencySettings.Instance.ProviderCollection[PageHeaderProvider.DefaultName]; provider.IsDebugMode = IsDebugMode; ClientDependencyHelper.RegisterClientDependencies(provider, this.Page, Paths); break; case ClientDependencyEmbedType.ClientSideRegistration: - provider = ClientDependencyHelper.ProviderCollection[ClientSideRegistrationProvider.DefaultName]; + provider = ClientDependencySettings.Instance.ProviderCollection[ClientSideRegistrationProvider.DefaultName]; provider.IsDebugMode = IsDebugMode; ClientDependencyHelper.RegisterClientDependencies(provider, this.Page, Paths); break; diff --git a/umbraco/presentation.ClientDependency/CompositeDependencyHandler.cs b/umbraco/presentation.ClientDependency/CompositeDependencyHandler.cs index 96c791e0f2..f3539d653b 100644 --- a/umbraco/presentation.ClientDependency/CompositeDependencyHandler.cs +++ b/umbraco/presentation.ClientDependency/CompositeDependencyHandler.cs @@ -6,6 +6,8 @@ using System.Reflection; using System.IO; using System.IO.Compression; using System.Net; +using System.Linq; +using umbraco.presentation.ClientDependency.Config; namespace umbraco.presentation.ClientDependency { @@ -64,25 +66,80 @@ namespace umbraco.presentation.ClientDependency if (string.IsNullOrEmpty(fileset)) throw new ArgumentException("Must specify a fileset in the request"); - byte[] fileBytes = CombineFiles(fileset, context, type); - byte[] outputBytes = CompressBytes(context, fileBytes); - SetCaching(context); + string compositeFileName; + byte[] outputBytes; + CompositeFileMap map = CompositeFileXmlMapper.Instance.GetCompositeFile(fileset); + if (map == null || !map.HasFileBytes) + { + //get the file list + string[] strFiles = DecodeFrom64(fileset).Split(';'); + //combine files + byte[] fileBytes = CombineFiles(strFiles, context, type); + //compress data + CompressionType cType = CompressBytes(context, fileBytes, out outputBytes); + SetContentEncodingHeaders(context, cType); + //save combined file + compositeFileName = SaveCompositeFile(outputBytes, type); + + //Update the XML file map + CompositeFileXmlMapper.Instance.CreateMap(fileset, cType.ToString(), + strFiles.Select(x => new FileInfo(context.Server.MapPath(x))).ToList(), + compositeFileName); + + } + else + { + //the saved file's bytes are already compressed. + outputBytes = map.GetCompositeFileBytes(); + compositeFileName = map.CompositeFileName; + CompressionType cType = (CompressionType)Enum.Parse(typeof(CompressionType), map.CompressionType); + SetContentEncodingHeaders(context, cType); + } + + SetCaching(context, compositeFileName); context.Response.ContentType = type == ClientDependencyType.Javascript ? "text/javascript" : "text/css"; context.Response.OutputStream.Write(outputBytes, 0, outputBytes.Length); } + private enum CompressionType + { + deflate, gzip, none + } + + /// + /// Saves the file's bytes to disk with a hash of the byte array + /// + /// + /// + /// The new file path + /// + /// the extension will be: .cdj for JavaScript and .cdc for CSS + /// + private string SaveCompositeFile(byte[] fileContents, ClientDependencyType type) + { + if (!ClientDependencySettings.Instance.CompositeFilePath.Exists) + ClientDependencySettings.Instance.CompositeFilePath.Create(); + FileInfo fi = new FileInfo( + Path.Combine(ClientDependencySettings.Instance.CompositeFilePath.FullName, + fileContents.GetHashCode().ToString() + ".cd" + type.ToString().Substring(0, 1).ToLower())); + if (fi.Exists) + fi.Delete(); + FileStream fs = fi.Create(); + fs.Write(fileContents, 0, fileContents.Length); + fs.Close(); + return fi.FullName; + } + /// /// combines all files to a byte array /// /// /// /// - private byte[] CombineFiles(string fileList, HttpContext context, ClientDependencyType type) + private byte[] CombineFiles(string[] strFiles, HttpContext context, ClientDependencyType type) { - MemoryStream ms = new MemoryStream(5000); - //get the file list, and write the contents of each file to the output stream. - string[] strFiles = DecodeFrom64(fileList).Split(';'); + MemoryStream ms = new MemoryStream(5000); StreamWriter sw = new StreamWriter(ms); foreach (string s in strFiles) { @@ -91,7 +148,7 @@ namespace umbraco.presentation.ClientDependency try { FileInfo fi = new FileInfo(context.Server.MapPath(s)); - if (ClientDependencyHelper.FileBasedDependencyExtensionList.Contains(fi.Extension.ToLower().Replace(".", ""))) + if (ClientDependencySettings.Instance.FileBasedDependencyExtensionList.Contains(fi.Extension.ToLower().Replace(".", ""))) { //if the file doesn't exist, then we'll assume it is a URI external request if (!fi.Exists) @@ -213,7 +270,7 @@ namespace umbraco.presentation.ClientDependency /// Sets the output cache parameters and also the client side caching parameters /// /// - private void SetCaching(HttpContext context) + private void SetCaching(HttpContext context, string fileName) { //This ensures OutputCaching is set for this handler and also controls //client side caching on the browser side. Default is 10 days. @@ -235,47 +292,68 @@ namespace umbraco.presentation.ClientDependency //This is the only way to set the max-age cachability header in ASP.Net! FieldInfo maxAgeField = cache.GetType().GetField("_maxAge", BindingFlags.Instance | BindingFlags.NonPublic); maxAgeField.SetValue(cache, duration); + + //make this output cache dependent on the file. + context.Response.AddFileDependency(fileName); + } + + /// + /// Sets the content encoding headers based on compressions + /// + /// + /// + private void SetContentEncodingHeaders(HttpContext context, CompressionType type) + { + if (type == CompressionType.deflate) + { + context.Response.AddHeader("Content-encoding", "deflate"); + } + else if (type == CompressionType.gzip) + { + context.Response.AddHeader("Content-encoding", "gzip"); + } } /// /// Compresses the bytes if the browser supports it /// - private byte[] CompressBytes(HttpContext context, byte[] fileBytes) + private CompressionType CompressBytes(HttpContext context, byte[] fileBytes, out byte[] outputBytes) { + CompressionType type = CompressionType.none; string acceptEncoding = context.Request.Headers["Accept-Encoding"]; + //not compressed initially + outputBytes = fileBytes; + if (!string.IsNullOrEmpty(acceptEncoding)) { MemoryStream ms = new MemoryStream(); Stream compressedStream = null; acceptEncoding = acceptEncoding.ToLowerInvariant(); - bool isCompressed = false; - + //deflate is faster in .Net according to Mads Kristensen (blogengine.net) if (acceptEncoding.Contains("deflate")) { - context.Response.AddHeader("Content-encoding", "deflate"); compressedStream = new DeflateStream(ms, CompressionMode.Compress, true); - isCompressed = true; + type = CompressionType.deflate; } else if (acceptEncoding.Contains("gzip")) { - context.Response.AddHeader("Content-encoding", "gzip"); compressedStream = new GZipStream(ms, CompressionMode.Compress, true); - isCompressed = true; + type = CompressionType.gzip; } - - if (isCompressed) + + if (type != CompressionType.none) { //write the bytes to the compressed stream compressedStream.Write(fileBytes, 0, fileBytes.Length); compressedStream.Close(); - byte[] outputBytes = ms.ToArray(); + byte[] output = ms.ToArray(); ms.Close(); - return outputBytes; + outputBytes = output; } } - //not compressed - return fileBytes; + + return type; } private string DecodeFrom64(string toDecode) diff --git a/umbraco/presentation.ClientDependency/CompositeFileMap.cs b/umbraco/presentation.ClientDependency/CompositeFileMap.cs new file mode 100644 index 0000000000..89cdebd5dc --- /dev/null +++ b/umbraco/presentation.ClientDependency/CompositeFileMap.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using System.IO; + +namespace umbraco.presentation.ClientDependency +{ + /// + /// Deserialized structure of the XML stored in the map file + /// + public class CompositeFileMap + { + + internal CompositeFileMap(string key, string compressionType, string file, List files) + { + DependentFiles = files; + Base64Key = key; + CompositeFileName = file; + CompressionType = compressionType; + } + + public string Base64Key { get; private set; } + public string CompositeFileName { get; private set; } + public string CompressionType { get; private set; } + + private byte[] m_FileBytes; + + /// + /// If for some reason the file doesn't exist any more or we cannot read the file, this will return false. + /// + public bool HasFileBytes + { + get + { + GetCompositeFileBytes(); + return m_FileBytes != null; + } + } + + /// + /// Returns the file's bytes + /// + public byte[] GetCompositeFileBytes() + { + if (m_FileBytes == null) + { + try + { + FileInfo fi = new FileInfo(CompositeFileName); + FileStream fs = fi.OpenRead(); + byte[] fileBytes = new byte[fs.Length]; + fs.Read(fileBytes, 0, fileBytes.Length); + fs.Close(); + m_FileBytes = fileBytes; + } + catch + { + m_FileBytes = null; + } + } + return m_FileBytes; + } + + public List DependentFiles { get; private set; } + + } +} diff --git a/umbraco/presentation.ClientDependency/CompositeFileXmlMapper.cs b/umbraco/presentation.ClientDependency/CompositeFileXmlMapper.cs new file mode 100644 index 0000000000..ca643a7ce1 --- /dev/null +++ b/umbraco/presentation.ClientDependency/CompositeFileXmlMapper.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Xml.Linq; +using System.Xml; +using System.IO; +using umbraco.presentation.ClientDependency.Config; + +namespace umbraco.presentation.ClientDependency +{ + + /// + /// Creates an XML file to map a saved composite file to the URL requested for the + /// dependency handler. + /// This is used in order to determine which individual files are dependant on what composite file so + /// a user can remove it to clear the cache, and also if the cache expires but the file still exists + /// this allows the system to simply read the one file again instead of compiling all of the other files + /// into one again. + /// + public class CompositeFileXmlMapper + { + + /// + /// Singleton + /// + public static CompositeFileXmlMapper Instance + { + get + { + return m_Mapper; + } + } + + private CompositeFileXmlMapper() + { + Initialize(); + } + + private static readonly CompositeFileXmlMapper m_Mapper = new CompositeFileXmlMapper(); + + private const string MapFileName = "map.xml"; + + private XDocument m_Doc; + private FileInfo m_XmlFile; + private object m_Lock = new object(); + + /// + /// Loads in the existing file contents. If the file doesn't exist, it creates one. + /// + private void Initialize() + { + + m_XmlFile = new FileInfo( + Path.Combine(ClientDependencySettings.Instance.CompositeFilePath.FullName, MapFileName)); + + EnsureXmlFile(); + + lock (m_Lock) + { + try + { + m_Doc = XDocument.Load(m_XmlFile.FullName); + } + catch (XmlException ex) + { + //if it's an xml exception, create a new one and try one more time... should always work. + CreateNewXmlFile(); + m_Doc = XDocument.Load(m_XmlFile.FullName); + } + } + } + + private void CreateNewXmlFile() + { + if (m_XmlFile.Exists) + m_XmlFile.Delete(); + + m_Doc = new XDocument(new XDeclaration("1.0", "UTF-8", "yes"), + new XElement("map")); + m_Doc.Save(m_XmlFile.FullName); + } + + private void EnsureXmlFile() + { + if (!m_XmlFile.Exists) + { + lock (m_Lock) + { + //double check + if (!m_XmlFile.Exists) + { + CreateNewXmlFile(); + } + } + } + } + + /// + /// Returns the composite file map associated with the base 64 key of the URL + /// for the handler. + /// + /// + /// + public CompositeFileMap GetCompositeFile(string base64Key) + { + XElement x = FindItem(base64Key); + //try + //{ + return (x == null ? null : new CompositeFileMap(base64Key, + x.Attribute("compression").Value, + x.Attribute("file").Value, + x.Descendants("file") + .Select(f => new FileInfo(f.Attribute("name").Value)) + .ToList())); + //} + //catch + //{ + // return null; + //} + } + + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// ]]> + /// + public void CreateMap(string base64Key, string compressionType, List dependentFiles, string compositeFile) + { + lock (m_Lock) + { + //see if we can find an item with the key already + XElement x = FindItem(base64Key); + + if (x != null) + { + x.Attribute("file").Value = compositeFile; + //remove all of the files so we can re-add them. + x.Element("files").Remove(); + + x.Add(CreateFileNode(dependentFiles)); + } + else + { + //if it doesn't exist, create it + m_Doc.Root.Add(new XElement("item", + new XAttribute("key", base64Key), + new XAttribute("file", compositeFile), + new XAttribute("compression", compressionType), + CreateFileNode(dependentFiles))); + } + + m_Doc.Save(m_XmlFile.FullName); + } + } + + private XElement FindItem(string key) + { + return m_Doc.Root.Elements("item") + .Where(e => (string)e.Attribute("key") == key) + .SingleOrDefault(); + } + + private XElement CreateFileNode(List files) + { + XElement x = new XElement("files"); + + //add all of the files + files.ForEach(d => + { + x.Add(new XElement("file", + new XAttribute("name", d.FullName))); + }); + + return x; + } + } +} diff --git a/umbraco/presentation.ClientDependency/Providers/ClientDependencySection.cs b/umbraco/presentation.ClientDependency/Config/ClientDependencySection.cs similarity index 77% rename from umbraco/presentation.ClientDependency/Providers/ClientDependencySection.cs rename to umbraco/presentation.ClientDependency/Config/ClientDependencySection.cs index 5996814de3..14af87f2c9 100644 --- a/umbraco/presentation.ClientDependency/Providers/ClientDependencySection.cs +++ b/umbraco/presentation.ClientDependency/Config/ClientDependencySection.cs @@ -4,7 +4,7 @@ using System.Text; using System.Configuration; using System.Linq; -namespace umbraco.presentation.ClientDependency.Providers +namespace umbraco.presentation.ClientDependency.Config { public class ClientDependencySection : ConfigurationSection { @@ -22,6 +22,16 @@ namespace umbraco.presentation.ClientDependency.Providers set { base["defaultProvider"] = value; } } + /// + /// This is the folder that composite script/css files will be stored once they are combined. + /// + [StringValidator(MinLength = 1)] + [ConfigurationProperty("compositeFilePath", DefaultValue = "~/App_Data/ClientDependency")] + public string CompositeFilePath + { + get { return (string)base["compositeFilePath"]; } + set { base["compositeFilePath"] = value; } + } /// /// The configuration section to set the FileBasedDependencyExtensionList. This is a comma separated list. diff --git a/umbraco/presentation.ClientDependency/Config/ClientDependencySettings.cs b/umbraco/presentation.ClientDependency/Config/ClientDependencySettings.cs new file mode 100644 index 0000000000..91729b158d --- /dev/null +++ b/umbraco/presentation.ClientDependency/Config/ClientDependencySettings.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web.Configuration; +using umbraco.presentation.ClientDependency.Providers; +using System.Configuration.Provider; +using System.IO; +using System.Web; + +namespace umbraco.presentation.ClientDependency.Config +{ + public class ClientDependencySettings + { + + private ClientDependencySettings() + { + LoadProviders(); + } + + /// + /// Singleton + /// + public static ClientDependencySettings Instance + { + get + { + return m_Settings; + } + } + + private static readonly ClientDependencySettings m_Settings = new ClientDependencySettings(); + + private object m_Lock = new object(); + private ClientDependencyProvider m_Provider = null; + private ClientDependencyProviderCollection m_Providers = null; + private List m_Extensions; + private DirectoryInfo m_CompositeFilePath; + + /// + /// The file extensions of Client Dependencies that are file based as opposed to request based. + /// Any file that doesn't have the extensions listed here will be request based, request based is + /// more overhead for the server to process. + /// + /// + /// A request based JavaScript file may be a .ashx that dynamically creates JavaScript server side. + /// + /// + /// If this is not explicitly set, then the extensions 'js' and 'css' are the defaults. + /// + public List FileBasedDependencyExtensionList + { + get + { + return m_Extensions; + } + } + + public ClientDependencyProvider DefaultProvider + { + get + { + return m_Provider; + } + } + public ClientDependencyProviderCollection ProviderCollection + { + get + { + return m_Providers; + } + } + public DirectoryInfo CompositeFilePath + { + get + { + return m_CompositeFilePath; + } + } + + private void LoadProviders() + { + if (m_Provider == null) + { + lock (m_Lock) + { + // Do this again to make sure _provider is still null + if (m_Provider == null) + { + ClientDependencySection section = (ClientDependencySection)WebConfigurationManager.GetSection("system.web/clientDependency"); + + m_Providers = new ClientDependencyProviderCollection(); + + // if there is no section found, then add the standard providers to the collection with the standard + // default provider + if (section != null) + { + // Load registered providers and point _provider to the default provider + ProvidersHelper.InstantiateProviders(section.Providers, m_Providers, typeof(ClientDependencyProvider)); + } + else + { + //create a new section with the default settings + section = new ClientDependencySection(); + PageHeaderProvider php = new PageHeaderProvider(); + php.Initialize(PageHeaderProvider.DefaultName, null); + m_Providers.Add(php); + ClientSideRegistrationProvider csrp = new ClientSideRegistrationProvider(); + csrp.Initialize(ClientSideRegistrationProvider.DefaultName, null); + m_Providers.Add(csrp); + } + + //set the default + m_Provider = m_Providers[section.DefaultProvider]; + if (m_Provider == null) + throw new ProviderException("Unable to load default ClientDependency provider"); + + m_Extensions = section.FileBasedDependencyExtensionList; + m_CompositeFilePath = new DirectoryInfo(HttpContext.Current.Server.MapPath(section.CompositeFilePath)); + } + } + } + } + } +} diff --git a/umbraco/presentation.ClientDependency/umbraco.presentation.ClientDependency.csproj b/umbraco/presentation.ClientDependency/umbraco.presentation.ClientDependency.csproj index e819cc132f..ba66187cca 100644 --- a/umbraco/presentation.ClientDependency/umbraco.presentation.ClientDependency.csproj +++ b/umbraco/presentation.ClientDependency/umbraco.presentation.ClientDependency.csproj @@ -46,11 +46,17 @@ + + 3.5 + + + + @@ -61,7 +67,7 @@ - + diff --git a/umbraco/presentation/umbraco/umbraco.aspx.designer.cs b/umbraco/presentation/umbraco/umbraco.aspx.designer.cs index 9825c87fef..10cb94c019 100644 --- a/umbraco/presentation/umbraco/umbraco.aspx.designer.cs +++ b/umbraco/presentation/umbraco/umbraco.aspx.designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:2.0.50727.3053 +// Runtime Version:2.0.50727.3082 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated.