2007-04-27 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AppResourcesCompiler.cs
1 //
2 // System.Web.Compilation.AppResourceFilesCollection
3 //
4 // Authors:
5 //   Marek Habersack (grendello@gmail.com)
6 //
7 // (C) 2006 Marek Habersack
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30 #if NET_2_0
31 using System;
32 using System.CodeDom;
33 using System.CodeDom.Compiler;
34 using System.Collections;
35 using System.Collections.Generic;
36 using System.Globalization;
37 using System.IO;
38 using System.Reflection;
39 using System.Resources;
40 using System.Web;
41 using System.Web.Caching;
42 using System.Web.Configuration;
43 using System.Web.Util;
44
45 namespace System.Web.Compilation 
46 {
47         internal class AppResourcesCompiler
48         {
49                 const string cachePrefix = "@@LocalResourcesAssemblies";
50                 
51                 bool isGlobal;
52                 HttpContext context;
53                 AppResourceFilesCollection files;
54                 string tempDirectory;
55                 string virtualPath;
56                 
57                 string TempDirectory {
58                         get {
59                                 if (tempDirectory != null)
60                                         return tempDirectory;
61                                 return (tempDirectory = AppDomain.CurrentDomain.SetupInformation.DynamicBase);
62                         }
63                 }
64                 
65                 public AppResourcesCompiler (HttpContext context)
66                 {
67                         this.context = context;
68                         this.isGlobal = true;
69                         this.files = new AppResourceFilesCollection (context);
70                 }
71
72                 public AppResourcesCompiler (string virtualPath)
73                 {
74
75                         this.virtualPath = virtualPath;
76                         this.isGlobal = false;
77                         this.files = new AppResourceFilesCollection (HttpContext.Current.Request.MapPath (virtualPath));
78                 }
79                 
80                 public Assembly Compile ()
81                 {
82                         files.Collect ();
83                         if (!files.HasFiles)
84                                 return null;
85                         if (isGlobal)
86                                 return CompileGlobal ();
87                         else
88                                 return CompileLocal ();
89                 }
90
91                 Assembly CompileGlobal ()
92                 {
93                         string assemblyPath = FileUtils.CreateTemporaryFile (TempDirectory,
94                                                                              "App_GlobalResources",
95                                                                              "dll",
96                                                                              OnCreateRandomFile) as string;
97
98                         if (assemblyPath == null)
99                                 throw new ApplicationException ("Failed to create global resources assembly");
100                         
101                         CompilationSection config = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
102                         if (config == null || !CodeDomProvider.IsDefinedLanguage (config.DefaultLanguage))
103                                 throw new ApplicationException ("Could not get the default compiler.");
104                         CompilerInfo ci = CodeDomProvider.GetCompilerInfo (config.DefaultLanguage);
105                         if (ci == null || !ci.IsCodeDomProviderTypeValid)
106                                 throw new ApplicationException ("Failed to obtain the default compiler information.");
107
108                         CompilerParameters cp = ci.CreateDefaultCompilerParameters ();
109                         cp.OutputAssembly = assemblyPath;
110                         cp.GenerateExecutable = false;
111                         cp.TreatWarningsAsErrors = true;
112                         cp.IncludeDebugInformation = config.Debug;
113                         
114                         List <string>[] fileGroups = GroupGlobalFiles (cp);
115                         if (fileGroups == null || fileGroups.Length == 0)
116                                 return null;
117
118                         CodeCompileUnit unit = new CodeCompileUnit ();
119                         CodeNamespace ns = new CodeNamespace (null);
120                         ns.Imports.Add (new CodeNamespaceImport ("System"));
121                         ns.Imports.Add (new CodeNamespaceImport ("System.Globalization"));
122                         ns.Imports.Add (new CodeNamespaceImport ("System.Reflection"));
123                         ns.Imports.Add (new CodeNamespaceImport ("System.Resources"));
124                         unit.Namespaces.Add (ns);
125
126                         CodeDomProvider provider;
127                         provider = ci.CreateProvider ();
128                         if (provider == null)
129                                 throw new ApplicationException ("Failed to instantiate the default compiler.");
130                         
131                         Dictionary <string,bool> assemblies = new Dictionary<string,bool> ();
132                         foreach (List<string> ls in fileGroups)
133                                 DomFromResource (ls [0], unit, assemblies, provider);
134                         foreach (KeyValuePair<string,bool> de in assemblies)
135                                 unit.ReferencedAssemblies.Add (de.Key);
136                         
137                         AssemblyBuilder abuilder = new AssemblyBuilder (provider);
138                         abuilder.AddCodeCompileUnit (unit);
139
140                         CompilerResults results = abuilder.BuildAssembly (cp);
141                         Assembly ret = null;
142                         
143                         if (results.NativeCompilerReturnValue == 0) {
144                                 ret = results.CompiledAssembly;
145                                 BuildManager.TopLevelAssemblies.Add (ret);
146                                 HttpContext.AppGlobalResourcesAssembly = ret;
147                         } else {
148                                 if (context.IsCustomErrorEnabled)
149                                         throw new ApplicationException ("An error occurred while compiling global resources.");
150                                 throw new CompilationException (null, results.Errors, null);
151                         }
152                         HttpRuntime.WritePreservationFile (ret, "App_GlobalResources");
153                         HttpRuntime.EnableAssemblyMapping (true);
154
155                         return ret;
156                 }
157
158                 Assembly CompileLocal ()
159                 {
160                         if (String.IsNullOrEmpty (virtualPath))
161                                 return null;
162                         
163                         Assembly cached = GetCachedLocalResourcesAssembly (virtualPath);
164                         if (cached != null)
165                                 return cached;
166                         
167                         string prefix;
168                         if (virtualPath == "/")
169                                 prefix = "App_LocalResources.root";
170                         else
171                                 prefix = "App_LocalResources" + virtualPath.Replace ('/', '.');
172                         
173                         string assemblyPath = FileUtils.CreateTemporaryFile (TempDirectory,
174                                                                              prefix,
175                                                                              "dll",
176                                                                              OnCreateRandomFile) as string;
177                         if (assemblyPath == null)
178                                 throw new ApplicationException ("Failed to create global resources assembly");
179                         
180                         CompilationSection config = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
181                         if (config == null || !CodeDomProvider.IsDefinedLanguage (config.DefaultLanguage))
182                                 throw new ApplicationException ("Could not get the default compiler.");
183                         CompilerInfo ci = CodeDomProvider.GetCompilerInfo (config.DefaultLanguage);
184                         if (ci == null || !ci.IsCodeDomProviderTypeValid)
185                                 throw new ApplicationException ("Failed to obtain the default compiler information.");
186
187                         CompilerParameters cp = ci.CreateDefaultCompilerParameters ();
188                         cp.OutputAssembly = assemblyPath;
189                         cp.GenerateExecutable = false;
190                         cp.TreatWarningsAsErrors = true;
191                         cp.IncludeDebugInformation = config.Debug;
192
193                         List<AppResourceFileInfo> files = this.files.Files;
194                         foreach (AppResourceFileInfo arfi in files)
195                                 GetResourceFile (arfi, cp, true);
196
197                         CodeDomProvider provider;
198                         provider = ci.CreateProvider ();
199                         if (provider == null)
200                                 throw new ApplicationException ("Failed to instantiate the default compiler.");
201                         
202                         AssemblyBuilder abuilder = new AssemblyBuilder (provider);
203                         CompilerResults results = abuilder.BuildAssembly (cp);
204                         Assembly ret = null;
205                         
206                         if (results.NativeCompilerReturnValue == 0) {
207                                 ret = results.CompiledAssembly;
208                                 AddAssemblyToCache (virtualPath, ret);
209                         } else {
210                                 if (context.IsCustomErrorEnabled)
211                                         throw new ApplicationException ("An error occurred while compiling global resources.");
212                                 throw new CompilationException (null, results.Errors, null);
213                         }
214
215                         return ret;
216                 }
217                 
218                 internal static Assembly GetCachedLocalResourcesAssembly (string path)
219                 {
220                         Dictionary <string, Assembly> cache;
221
222                         cache = HttpRuntime.Cache[cachePrefix] as Dictionary <string, Assembly>;
223                         if (cache == null || !cache.ContainsKey (path))
224                                 return null;
225                         return cache [path];
226                 }
227                 
228                 void AddAssemblyToCache (string path, Assembly asm)
229                 {
230                         Cache runtimeCache = HttpRuntime.Cache;
231                         Dictionary <string, Assembly> cache;
232                         
233                         cache = runtimeCache[cachePrefix] as Dictionary <string, Assembly>;
234                         if (cache == null)
235                                 cache = new Dictionary <string, Assembly> ();
236                         cache [path] = asm;
237                         runtimeCache.Insert (cachePrefix, cache);
238                 }
239                 
240                 uint CountChars (char c, string s)
241                 {
242                         uint ret = 0;
243                         foreach (char ch in s) {
244                                 if (ch == c)
245                                         ret++;
246                         }
247                         return ret;
248                 }
249
250                 bool IsFileCultureValid (string fileName)
251                 {
252                     string tmp = Path.GetFileNameWithoutExtension (fileName);
253                     tmp = Path.GetExtension (tmp);
254                     if (tmp != null && tmp.Length > 0) {
255                               tmp = tmp.Substring (1);
256                             try {
257                                 CultureInfo.GetCultureInfo (tmp);
258                                 return true;
259                             } catch {
260                                 return false;
261                             }
262                     } 
263                     return false;
264                 }
265
266                 string GetResourceFile (AppResourceFileInfo arfi, CompilerParameters cp, bool local)
267                 {
268                         string resfile;
269                         if (arfi.Kind == AppResourceFileKind.ResX)
270                                 resfile = CompileResource (arfi, local);
271                         else
272                                 resfile = arfi.Info.FullName;
273                         if (!String.IsNullOrEmpty (resfile))
274                                 cp.EmbeddedResources.Add (resfile);
275                         return resfile;
276                 }
277                 
278                 List <string>[] GroupGlobalFiles (CompilerParameters cp)
279                 {
280                         List<AppResourceFileInfo> files = this.files.Files;
281                         List<List<string>> groups = new List<List<string>> ();
282                         AppResourcesLengthComparer<List<string>> lcList = new AppResourcesLengthComparer<List<string>> ();
283                         
284                         string tmp, s, basename;
285                         uint basedots, filedots;
286                         AppResourceFileInfo defaultFile;
287                         
288                         foreach (AppResourceFileInfo arfi in files) {
289                                 if (arfi.Kind != AppResourceFileKind.ResX && arfi.Kind != AppResourceFileKind.Resource)
290                                         continue;
291
292                                 s = arfi.Info.FullName;
293                                 basename = Path.GetFileNameWithoutExtension (s);
294                                 basedots = CountChars ('.', basename);
295                                 defaultFile = null;
296                                 
297                                 // If there are any files that start with this baseName, we have a default file
298                                 foreach (AppResourceFileInfo fi in files) {
299                                         if (fi.Seen)
300                                                 continue;
301                                         
302                                         string s2 = fi.Info.FullName;
303                                         if (s2 == null || s == s2)
304                                                 continue;
305                                         tmp = Path.GetFileNameWithoutExtension (s2);
306                                         filedots = CountChars ('.', tmp);
307
308                                         if (filedots == basedots + 1 && tmp.StartsWith (basename)) {
309                                                 if (IsFileCultureValid (s2)) {
310                                                         // A valid translated file for this name
311                                                         defaultFile = arfi;
312                                                         break;
313                                                 } else {
314                                                         // This file shares the base name, but the culture is invalid - we must
315                                                         // ignore it since the name of the generated strongly typed class for this
316                                                         // resource will clash with the one generated from the default file with
317                                                         // the given basename.
318                                                         fi.Seen = true;
319                                                 }
320                                         }
321                                 }
322                                 if (defaultFile != null) {
323                                         List<string> al = new List<string> ();
324                                         al.Add (GetResourceFile (arfi, cp, false));
325                                         arfi.Seen = true;
326                                         groups.Add (al);
327                                         
328                                 }
329                         }
330                         groups.Sort (lcList);
331
332                         string tmp2;
333                         // Now find their translated counterparts
334                         foreach (List<string> al in groups) {
335                                 s = al [0];
336                                 tmp = Path.GetFileNameWithoutExtension (s);
337                                 if (tmp.StartsWith ("Resources."))
338                                         tmp = tmp.Substring (10);
339                                 foreach (AppResourceFileInfo arfi in files) {
340                                         if (arfi.Seen)
341                                                 continue;
342                                         s = arfi.Info.FullName;
343                                         if (s == null)
344                                                 continue;
345                                         tmp2 = arfi.Info.Name;
346                                         if (tmp2.StartsWith (tmp)) {
347                                                 al.Add (GetResourceFile (arfi, cp, false));
348                                                 arfi.Seen = true;
349                                         }
350                                 }
351                         }
352
353                         // Anything that's left here might be orphans or lone default files.
354                         // For those files we check the part following the last dot
355                         // before the .resx/.resource extensions and test whether it's a registered
356                         // culture or not. If it is not a culture, then we have a
357                         // default file that doesn't have any translations. Otherwise,
358                         // the file is ignored (it's the same thing MS.NET does)
359                         foreach (AppResourceFileInfo arfi in files) {
360                                 if (arfi.Seen)
361                                         continue;
362
363                                 if (IsFileCultureValid (arfi.Info.FullName))
364                                         continue; // Culture found, we reject the file
365
366                                 // A single default file, create a group
367                                 List<string> al = new List<string> ();
368                                 al.Add (GetResourceFile (arfi, cp, false));
369                                 groups.Add (al);
370                         }
371                         groups.Sort (lcList);
372                         return groups.ToArray ();
373                 }
374
375                 // CodeDOM generation
376                 void DomFromResource (string resfile, CodeCompileUnit unit, Dictionary <string,bool> assemblies,
377                                       CodeDomProvider provider)
378                 {
379                         if (String.IsNullOrEmpty (resfile))
380                                 return;
381
382                         string fname, nsname, classname;
383
384                         fname = Path.GetFileNameWithoutExtension (resfile);
385                         nsname = Path.GetFileNameWithoutExtension (fname);
386                         classname = Path.GetExtension (fname);
387                         if (classname == null || classname.Length == 0) {
388                                 classname = nsname;
389                                 nsname = "Resources";
390                         } else {
391                                 if (!nsname.StartsWith ("Resources", StringComparison.InvariantCulture))
392                                         nsname = String.Format ("Resources.{0}", nsname);
393                                 classname = classname.Substring(1);
394                         }
395
396                         if (!String.IsNullOrEmpty (classname))
397                                 classname = classname.Replace ('.', '_');
398                         if (!String.IsNullOrEmpty (nsname))
399                                 nsname = nsname.Replace ('.', '_');
400                         
401                         if (!provider.IsValidIdentifier (nsname) || !provider.IsValidIdentifier (classname))
402                                 throw new ApplicationException ("Invalid resource file name.");
403
404                         ResourceReader res;
405                         try {
406                                 res = new ResourceReader (resfile);
407                         } catch (ArgumentException) {
408                                 // invalid stream, probably empty - ignore silently and abort
409                                 return;
410                         }
411                         
412                         CodeNamespace ns = new CodeNamespace (nsname);
413                         CodeTypeDeclaration cls = new CodeTypeDeclaration (classname);
414                         cls.IsClass = true;
415                         cls.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed;
416
417                         CodeMemberField cmf = new CodeMemberField (typeof(CultureInfo), "culture");
418                         cmf.InitExpression = new CodePrimitiveExpression (null);
419                         cmf.Attributes = MemberAttributes.Private | MemberAttributes.Final | MemberAttributes.Static;
420                         cls.Members.Add (cmf);
421
422                         cmf = new CodeMemberField (typeof(ResourceManager), "resourceManager");
423                         cmf.InitExpression = new CodePrimitiveExpression (null);
424                         cmf.Attributes = MemberAttributes.Private | MemberAttributes.Final | MemberAttributes.Static;
425                         cls.Members.Add (cmf);
426                         
427                         // Property: ResourceManager
428                         CodeMemberProperty cmp = new CodeMemberProperty ();
429                         cmp.Attributes = MemberAttributes.Public | MemberAttributes.Final | MemberAttributes.Static;
430                         cmp.Name = "ResourceManager";
431                         cmp.HasGet = true;
432                         cmp.Type = new CodeTypeReference (typeof(ResourceManager));
433                         CodePropertyResourceManagerGet (cmp.GetStatements, resfile, classname);
434                         cls.Members.Add (cmp);
435
436                         // Property: Culture
437                         cmp = new CodeMemberProperty ();
438                         cmp.Attributes = MemberAttributes.Public | MemberAttributes.Final;
439                         cmp.Attributes = MemberAttributes.Public | MemberAttributes.Final | MemberAttributes.Static;
440                         cmp.Name = "Culture";
441                         cmp.HasGet = true;
442                         cmp.HasSet = true;
443                         cmp.Type = new CodeTypeReference (typeof(CultureInfo));
444                         CodePropertyGenericGet (cmp.GetStatements, "culture", classname);
445                         CodePropertyGenericSet (cmp.SetStatements, "culture", classname);
446                         cls.Members.Add (cmp);
447
448                         // Add the resource properties
449                         Dictionary<string,bool> imports = new Dictionary<string,bool> ();
450                         try {
451                                 foreach (DictionaryEntry de in res) {
452                                         Type type = de.Value.GetType ();
453
454                                         if (!imports.ContainsKey (type.Namespace))
455                                                 imports [type.Namespace] = true;
456
457                                         string asname = new AssemblyName (type.Assembly.FullName).Name;
458                                         if (!assemblies.ContainsKey (asname))
459                                                 assemblies [asname] = true;
460                                         
461                                         cmp = new CodeMemberProperty ();
462                                         cmp.Attributes = MemberAttributes.Public | MemberAttributes.Final | MemberAttributes.Static;
463                                         cmp.Name = SanitizeResourceName ((string)de.Key);
464                                         cmp.HasGet = true;
465                                         CodePropertyResourceGet (cmp.GetStatements, (string)de.Key, type, classname);
466                                         cmp.Type = new CodeTypeReference (type);
467                                         cls.Members.Add (cmp);
468                                 }
469                         } catch (Exception ex) {
470                                 throw new ApplicationException ("Failed to compile global resources.", ex);
471                         }
472                         foreach (KeyValuePair<string,bool> de in imports)
473                                 ns.Imports.Add (new CodeNamespaceImport(de.Key));
474                         
475                         ns.Types.Add (cls);
476                         unit.Namespaces.Add (ns);
477                 }
478
479                 string SanitizeResourceName (string name)
480                 {
481                         return name.Replace (' ', '_').Replace ('-', '_').Replace ('.', '_');
482                 }
483                 
484                 CodeObjectCreateExpression NewResourceManager (string name, string typename)
485                 {
486                         CodeExpression resname = new CodePrimitiveExpression (name);
487                         CodePropertyReferenceExpression asm = new CodePropertyReferenceExpression (
488                                 new CodeTypeOfExpression (new CodeTypeReference (typename)),
489                                 "Assembly");
490                         
491                         return new CodeObjectCreateExpression ("System.Resources.ResourceManager",
492                                                                new CodeExpression [] {resname, asm});
493                 }
494                 
495                 void CodePropertyResourceManagerGet (CodeStatementCollection csc, string resfile, string typename)
496                 {
497                         string name = Path.GetFileNameWithoutExtension (resfile);
498                         CodeStatement st;
499                         CodeExpression exp;
500
501                         exp = new CodeFieldReferenceExpression (new CodeTypeReferenceExpression (typename), "resourceManager");
502                         st = new CodeConditionStatement (
503                                 new CodeBinaryOperatorExpression (
504                                         exp,
505                                         CodeBinaryOperatorType.IdentityInequality,
506                                         new CodePrimitiveExpression (null)),
507                                 new CodeStatement [] { new CodeMethodReturnStatement (exp) });
508                         csc.Add (st);
509
510                         st = new CodeAssignStatement (exp, NewResourceManager (name, typename));
511                         csc.Add (st);
512                         csc.Add (new CodeMethodReturnStatement (exp));
513                 }
514
515                 void CodePropertyResourceGet (CodeStatementCollection csc, string resname, Type restype, string typename)
516                 {
517                         CodeStatement st = new CodeVariableDeclarationStatement (
518                                 typeof (ResourceManager),
519                                 "rm",
520                                 new CodePropertyReferenceExpression (
521                                         new CodeTypeReferenceExpression (typename), "ResourceManager"));
522                         csc.Add (st);
523
524                         st = new CodeConditionStatement (
525                                 new CodeBinaryOperatorExpression (
526                                         new CodeVariableReferenceExpression ("rm"),
527                                         CodeBinaryOperatorType.IdentityEquality,
528                                         new CodePrimitiveExpression (null)),
529                                 new CodeStatement [] { new CodeMethodReturnStatement (new CodePrimitiveExpression (null)) });
530                         csc.Add (st);
531
532                         bool gotstr = (restype == typeof (string));
533                         CodeExpression exp = new CodeMethodInvokeExpression (
534                                 new CodeVariableReferenceExpression ("rm"),
535                                 gotstr ? "GetString" : "GetObject",
536                                 new CodeExpression [] { new CodePrimitiveExpression (resname),
537                                                         new CodeFieldReferenceExpression (
538                                                                 new CodeTypeReferenceExpression (typename), "culture") });
539                         st = new CodeVariableDeclarationStatement (
540                                 restype,
541                                 "obj",
542                                 gotstr ? exp : new CodeCastExpression (restype, exp));
543                         csc.Add (st);
544                         csc.Add (new CodeMethodReturnStatement (new CodeVariableReferenceExpression ("obj")));
545                 }
546                 
547                 void CodePropertyGenericGet (CodeStatementCollection csc, string field, string typename)
548                 {
549                         csc.Add(new CodeMethodReturnStatement (
550                                         new CodeFieldReferenceExpression (
551                                                 new CodeTypeReferenceExpression (typename), field)));
552                 }
553
554                 void CodePropertyGenericSet (CodeStatementCollection csc, string field, string typename)
555                 {
556                         csc.Add(new CodeAssignStatement (
557                                         new CodeFieldReferenceExpression (new CodeTypeReferenceExpression (typename), field),
558                                         new CodeVariableReferenceExpression ("value")));
559                 }
560                 
561                 string CompileResource (AppResourceFileInfo arfi, bool local)
562                 {
563                         string path = arfi.Info.FullName;
564                         string rname = Path.GetFileNameWithoutExtension (path) + ".resources";
565                         if (!local)
566                                 rname = "Resources." + rname;
567                         
568                         string resource = Path.Combine (TempDirectory, rname);
569                         FileStream source = null, destination = null;
570                         IResourceReader reader = null;
571                         ResourceWriter writer = null;
572
573                         try {
574                                 source = new FileStream (path, FileMode.Open, FileAccess.Read);
575                                 destination = new FileStream (resource, FileMode.Create, FileAccess.Write);
576                                 reader = GetReaderForKind (arfi.Kind, source);
577                                 writer = new ResourceWriter (destination);
578                                 foreach (DictionaryEntry de in reader) {
579                                         object val = de.Value;
580                                         if (val is string)
581                                                 writer.AddResource ((string)de.Key, (string)val);
582                                         else
583                                                 writer.AddResource ((string)de.Key, val);
584                                 }
585                         } catch (Exception ex) {
586                                 throw new HttpException ("Failed to compile resource file", ex);
587                         } finally {
588                                 if (reader != null)
589                                         reader.Close ();
590                                 else if (source != null)
591                                         source.Close ();
592                                 if (writer != null)
593                                         writer.Close ();
594                                 else if (destination != null)
595                                         destination.Close ();
596                         }
597                         
598                         return resource;
599                 }
600
601                 IResourceReader GetReaderForKind (AppResourceFileKind kind, Stream stream)
602                 {
603                         switch (kind) {
604                                 case AppResourceFileKind.ResX:
605                                         return new ResXResourceReader (stream);
606
607                                 case AppResourceFileKind.Resource:
608                                         return new ResourceReader (stream);
609
610                                 default:
611                                         return null;
612                         }
613                 }
614                 
615                                                                
616                 object OnCreateRandomFile (string path)
617                 {
618                         FileStream f = new FileStream (path, FileMode.CreateNew);
619                         f.Close ();
620                         return path;
621                 }
622         };
623 };
624 #endif