Merge pull request #5714 from alexischr/update_bockbuild
[mono.git] / mcs / class / System.Web / System.Web.Compilation / AppResourcesCompiler.cs
1 //
2 // System.Web.Compilation.AppResourceFilesCollection
3 //
4 // Authors:
5 //   Marek Habersack (mhabersack@novell.com)
6 //
7 // (C) 2006 Marek Habersack
8 // (C) 2007-2009 Novell, Inc http://novell.com/
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.CodeDom;
34 using System.CodeDom.Compiler;
35 using System.Collections;
36 using System.Collections.Generic;
37 using System.ComponentModel.Design;
38 using System.Globalization;
39 using System.IO;
40 using System.Reflection;
41 using System.Resources;
42 using System.Text;
43 using System.Web;
44 using System.Web.Caching;
45 using System.Web.Configuration;
46 using System.Web.Util;
47
48 namespace System.Web.Compilation 
49 {
50         class AppResourcesCompiler
51         {
52                 // This class fixes bug #466059
53                 class TypeResolutionService : ITypeResolutionService
54                 {
55                         List <Assembly> referencedAssemblies;
56                         Dictionary <string, Type> mappedTypes;
57
58                         public Assembly GetAssembly (AssemblyName name)
59                         {
60                                 return GetAssembly (name, false);
61                         }
62                         
63                         public Assembly GetAssembly (AssemblyName name, bool throwOnError)
64                         {
65                                 try {
66                                         return Assembly.Load (name);
67                                 } catch {
68                                         if (throwOnError)
69                                                 throw;
70                                 }
71
72                                 return null;
73                         }
74
75                         public void ReferenceAssembly (AssemblyName name)
76                         {
77                                 if (referencedAssemblies == null)
78                                         referencedAssemblies = new List <Assembly> ();
79
80                                 Assembly asm = GetAssembly (name, false);
81                                 if (asm == null)
82                                         return;
83                                 
84                                 if (referencedAssemblies.Contains (asm))
85                                         return;
86                                 
87                                 referencedAssemblies.Add (asm);
88                         }
89
90                         public string GetPathOfAssembly (AssemblyName name)
91                         {
92                                 if (name == null)
93                                         return null;
94
95                                 Assembly asm = GetAssembly (name, false);
96                                 if (asm == null)
97                                         return null;
98                                 
99                                 return asm.Location;
100                         }
101
102                         public Type GetType (string name)
103                         {
104                                 return GetType (name, false, false);
105                         }
106
107                         public Type GetType (string name, bool throwOnError)
108                         {
109                                 return GetType (name, throwOnError, false);
110                         }
111
112                         public Type GetType (string name, bool throwOnError, bool ignoreCase)
113                         {
114                                 if (String.IsNullOrEmpty (name)) {
115                                         if (throwOnError)
116                                                 throw new ArgumentNullException ("name");
117                                         else
118                                                 return null;
119                                 }
120
121                                 int idx = name.IndexOf (',');
122                                 Type type = null;
123                                 if (idx == -1) {
124                                         type = MapType (name, false);
125                                         if (type != null)
126                                                 return type;
127                                         
128                                         type = FindInAssemblies (name, ignoreCase);
129                                         if (type == null) {
130                                                 if (throwOnError)
131                                                         throw new InvalidOperationException ("Type '" + name + "' is not fully qualified and there are no referenced assemblies.");
132                                                 else
133                                                         return null;
134                                         }
135
136                                         return type;
137                                 }
138
139                                 type = MapType (name, true);
140                                 if (type != null)
141                                         return type;
142                                 
143                                 return Type.GetType (name, throwOnError, ignoreCase);
144                         }
145
146                         Type MapType (string name, bool full)
147                         {
148                                 if (mappedTypes == null)
149                                         mappedTypes = new Dictionary <string, Type> (StringComparer.Ordinal);
150
151                                 Type ret;
152                                 if (mappedTypes.TryGetValue (name, out ret))
153                                         return ret;
154
155                                 if (!full) {
156                                         if (String.Compare (name, "ResXDataNode", StringComparison.Ordinal) == 0)
157                                                 return AddMappedType (name, typeof (ResXDataNode));
158                                         if (String.Compare (name, "ResXFileRef", StringComparison.Ordinal) == 0)
159                                                 return AddMappedType (name, typeof (ResXFileRef));
160                                         if (String.Compare (name, "ResXNullRef", StringComparison.Ordinal) == 0)
161                                                 return AddMappedType (name, typeof (ResXNullRef));
162                                         if (String.Compare (name, "ResXResourceReader", StringComparison.Ordinal) == 0)
163                                                 return AddMappedType (name, typeof (ResXResourceReader));
164                                         if (String.Compare (name, "ResXResourceWriter", StringComparison.Ordinal) == 0)
165                                                 return AddMappedType (name, typeof (ResXResourceWriter));
166
167                                         return null;
168                                 }
169
170                                 if (name.IndexOf ("System.Windows.Forms") == -1)
171                                         return null;
172
173                                 if (name.IndexOf ("ResXDataNode", StringComparison.Ordinal) != -1)
174                                         return AddMappedType (name, typeof (ResXDataNode));
175                                 if (name.IndexOf ("ResXFileRef", StringComparison.Ordinal) != -1)
176                                         return AddMappedType (name, typeof (ResXFileRef));
177                                 if (name.IndexOf ("ResXNullRef", StringComparison.Ordinal) != -1)
178                                         return AddMappedType (name, typeof (ResXNullRef));
179                                 if (name.IndexOf ("ResXResourceReader", StringComparison.Ordinal) != -1)
180                                         return AddMappedType (name, typeof (ResXResourceReader));
181                                 if (name.IndexOf ("ResXResourceWriter", StringComparison.Ordinal) != -1)
182                                         return AddMappedType (name, typeof (ResXResourceWriter));
183
184                                 return null;
185                         }
186
187                         Type AddMappedType (string name, Type type)
188                         {
189                                 mappedTypes.Add (name, type);
190                                 return type;
191                         }
192                         
193                         Type FindInAssemblies (string name, bool ignoreCase)
194                         {
195                                 Type ret = Type.GetType (name, false);
196                                 if (ret != null)
197                                         return ret;
198
199                                 if (referencedAssemblies == null || referencedAssemblies.Count == 0)
200                                         return null;
201
202                                 foreach (Assembly asm in referencedAssemblies) {
203                                         ret = asm.GetType (name, false, ignoreCase);
204                                         if (ret != null)
205                                                 return ret;
206                                 }
207
208                                 return null;
209                         }
210                 }
211                 
212                 const string cachePrefix = "@@LocalResourcesAssemblies";
213                 
214                 bool isGlobal;
215                 AppResourceFilesCollection files;
216                 string tempDirectory;
217                 string virtualPath;
218                 Dictionary <string, List <string>> cultureFiles;
219                 List <string> defaultCultureFiles;
220                 
221                 string TempDirectory {
222                         get {
223                                 if (tempDirectory != null)
224                                         return tempDirectory;
225                                 return (tempDirectory = AppDomain.CurrentDomain.SetupInformation.DynamicBase);
226                         }
227                 }
228
229                 public Dictionary <string, List <string>> CultureFiles {
230                         get { return cultureFiles; }
231                 }
232
233                 public List <string> DefaultCultureFiles {
234                         get { return defaultCultureFiles; }
235                 }
236                 
237                 static AppResourcesCompiler ()
238                 {
239                         if (!BuildManager.IsPrecompiled)
240                                 return;
241
242                         string[] binDirAssemblies = HttpApplication.BinDirectoryAssemblies;
243                         if (binDirAssemblies == null || binDirAssemblies.Length == 0)
244                                 return;
245
246                         string name;
247                         Assembly asm;
248                         foreach (string asmPath in binDirAssemblies) {
249                                 if (String.IsNullOrEmpty (asmPath))
250                                         continue;
251                                 
252                                 name = Path.GetFileName (asmPath);
253                                 if (name.StartsWith ("App_LocalResources.", StringComparison.OrdinalIgnoreCase)) {
254                                         string virtualPath = GetPrecompiledVirtualPath (asmPath);
255                                         if (String.IsNullOrEmpty (virtualPath))
256                                                 continue;
257
258                                         asm = LoadAssembly (asmPath);
259                                         if (asm == null)
260                                                 continue;
261                                         
262                                         AddAssemblyToCache (virtualPath, asm);
263                                         continue;
264                                 }
265
266                                 if (String.Compare (name, "App_GlobalResources.dll", StringComparison.OrdinalIgnoreCase) != 0)
267                                         continue;
268
269                                 asm = LoadAssembly (asmPath);
270                                 if (asm == null)
271                                         continue;
272
273                                 HttpContext.AppGlobalResourcesAssembly = asm;
274                         }
275                 }
276                 
277                 public AppResourcesCompiler (HttpContext context)
278                 {
279                         this.isGlobal = true;
280                         this.files = new AppResourceFilesCollection (context);
281                         this.cultureFiles = new Dictionary <string, List <string>> (StringComparer.OrdinalIgnoreCase);
282                 }
283
284                 public AppResourcesCompiler (string virtualPath)
285                 {
286
287                         this.virtualPath = virtualPath;
288                         this.isGlobal = false;
289                         this.files = new AppResourceFilesCollection (HttpContext.Current.Request.MapPath (virtualPath));
290                         this.cultureFiles = new Dictionary <string, List <string>> (StringComparer.OrdinalIgnoreCase);
291                 }
292
293                 static Assembly LoadAssembly (string asmPath)
294                 {
295                         try {
296                                 return Assembly.LoadFrom (asmPath);
297                         } catch (BadImageFormatException) {
298                                 // ignore
299                                 return null;
300                         }
301                 }
302                 
303                 static string GetPrecompiledVirtualPath (string asmPath)
304                 {
305                         string compiledFile = Path.ChangeExtension (asmPath, ".compiled");
306                         
307                         if (!File.Exists (compiledFile))
308                                 return null;
309
310                         var pfile = new PreservationFile (compiledFile);
311                         string virtualPath = pfile.VirtualPath;
312                         if (String.IsNullOrEmpty (virtualPath))
313                                 return "/";
314
315                         if (virtualPath.EndsWith ("/App_LocalResources/", StringComparison.OrdinalIgnoreCase))
316                                 virtualPath = virtualPath.Substring (0, virtualPath.Length - 19);
317                         
318                         return virtualPath;
319                 }
320                 
321                 public Assembly Compile ()
322                 {
323                         files.Collect ();
324                         if (!files.HasFiles)
325                                 return null;
326                         if (isGlobal)
327                                 return CompileGlobal ();
328                         else
329                                 return CompileLocal ();
330                 }
331
332                 Assembly CompileGlobal ()
333                 {
334                         string assemblyPath = FileUtils.CreateTemporaryFile (TempDirectory,
335                                                                              "App_GlobalResources",
336                                                                              "dll",
337                                                                              OnCreateRandomFile) as string;
338
339                         if (assemblyPath == null)
340                                 throw new ApplicationException ("Failed to create global resources assembly");
341                         
342                         List <string>[] fileGroups = GroupGlobalFiles ();
343                         if (fileGroups == null || fileGroups.Length == 0)
344                                 return null;
345                         
346                         CodeCompileUnit unit = new CodeCompileUnit ();
347                         CodeNamespace ns = new CodeNamespace (null);
348                         ns.Imports.Add (new CodeNamespaceImport ("System"));
349                         ns.Imports.Add (new CodeNamespaceImport ("System.Globalization"));
350                         ns.Imports.Add (new CodeNamespaceImport ("System.Reflection"));
351                         ns.Imports.Add (new CodeNamespaceImport ("System.Resources"));
352                         unit.Namespaces.Add (ns);
353
354                         AppResourcesAssemblyBuilder builder = new AppResourcesAssemblyBuilder ("App_GlobalResources", assemblyPath,
355                                                                                                this);
356                         CodeDomProvider provider = builder.Provider;
357                         
358                         Dictionary <string,bool> assemblies = new Dictionary<string,bool> ();
359                         foreach (List<string> ls in fileGroups)
360                                 DomFromResource (ls [0], unit, assemblies, provider);
361                         
362                         foreach (KeyValuePair<string,bool> de in assemblies)
363                                 unit.ReferencedAssemblies.Add (de.Key);
364                         
365                         builder.Build (unit);
366                         HttpContext.AppGlobalResourcesAssembly = builder.MainAssembly;
367                         
368                         return builder.MainAssembly;
369                 }
370
371                 Assembly CompileLocal ()
372                 {
373                         if (String.IsNullOrEmpty (virtualPath))
374                                 return null;
375                         
376                         Assembly cached = GetCachedLocalResourcesAssembly (virtualPath);
377                         if (cached != null)
378                                 return cached;
379                         
380                         string prefix;
381                         if (virtualPath == "/")
382                                 prefix = "App_LocalResources.root";
383                         else
384                                 prefix = "App_LocalResources" + virtualPath.Replace ('/', '.');
385                         
386                         string assemblyPath = FileUtils.CreateTemporaryFile (TempDirectory,
387                                                                              prefix,
388                                                                              "dll",
389                                                                              OnCreateRandomFile) as string;
390                         if (assemblyPath == null)
391                                 throw new ApplicationException ("Failed to create local resources assembly");
392
393                         List<AppResourceFileInfo> files = this.files.Files;
394                         foreach (AppResourceFileInfo arfi in files)
395                                 GetResourceFile (arfi, true);
396
397                         AppResourcesAssemblyBuilder builder = new AppResourcesAssemblyBuilder ("App_LocalResources", assemblyPath,
398                                                                                                this);
399                         builder.Build ();
400                         Assembly ret = builder.MainAssembly;
401                         
402                         if (ret != null)
403                                 AddAssemblyToCache (virtualPath, ret);
404
405                         return ret;
406                 }
407                 
408                 internal static Assembly GetCachedLocalResourcesAssembly (string path)
409                 {
410                         Dictionary <string, Assembly> cache;
411
412                         cache = HttpRuntime.InternalCache[cachePrefix] as Dictionary <string, Assembly>;
413                         if (cache == null || !cache.ContainsKey (path))
414                                 return null;
415                         return cache [path];
416                 }
417                 
418                 static void AddAssemblyToCache (string path, Assembly asm)
419                 {
420                         Cache runtimeCache = HttpRuntime.InternalCache;
421                         Dictionary <string, Assembly> cache;
422                         
423                         cache = runtimeCache[cachePrefix] as Dictionary <string, Assembly>;
424                         if (cache == null)
425                                 cache = new Dictionary <string, Assembly> ();
426                         cache [path] = asm;
427                         runtimeCache.Insert (cachePrefix, cache);
428                 }
429                 
430                 uint CountChars (char c, string s)
431                 {
432                         uint ret = 0;
433                         foreach (char ch in s) {
434                                 if (ch == c)
435                                         ret++;
436                         }
437                         return ret;
438                 }
439
440                 string IsFileCultureValid (string fileName)
441                 {
442                     string tmp = Path.GetFileNameWithoutExtension (fileName);
443                     tmp = Path.GetExtension (tmp);
444                     if (tmp != null && tmp.Length > 0) {
445                               tmp = tmp.Substring (1);
446                             try {
447                                 CultureInfo.GetCultureInfo (tmp);
448                                 return tmp;
449                             } catch {
450                                 return null;
451                             }
452                     } 
453                     return null;
454                 }
455                 
456                 string GetResourceFile (AppResourceFileInfo arfi, bool local)
457                 {
458                         string resfile;
459                         if (arfi.Kind == AppResourceFileKind.ResX)
460                                 resfile = CompileResource (arfi, local);
461                         else
462                                 resfile = arfi.Info.FullName;
463                         if (!String.IsNullOrEmpty (resfile)) {
464                                 string culture = IsFileCultureValid (resfile);
465                                 List <string> cfiles;
466                                 if (culture != null) {
467                                         if (cultureFiles.ContainsKey (culture))
468                                                 cfiles = cultureFiles [culture];
469                                         else {
470                                                 cfiles = new List <string> (1);
471                                                 cultureFiles [culture] = cfiles;
472                                         }
473                                 } else {
474                                         if (defaultCultureFiles == null)
475                                                 defaultCultureFiles = new List <string> ();
476                                         cfiles = defaultCultureFiles;
477                                 }
478                                 
479                                 cfiles.Add (resfile);
480                         }
481                                 
482                         return resfile;
483                 }
484                 
485                 List <string>[] GroupGlobalFiles ()
486                 {
487                         List<AppResourceFileInfo> files = this.files.Files;
488                         List<List<string>> groups = new List<List<string>> ();
489                         AppResourcesLengthComparer<List<string>> lcList = new AppResourcesLengthComparer<List<string>> ();
490                         
491                         string tmp, s, basename;
492                         uint basedots, filedots;
493                         AppResourceFileInfo defaultFile;
494                         
495                         foreach (AppResourceFileInfo arfi in files) {
496                                 if (arfi.Kind != AppResourceFileKind.ResX && arfi.Kind != AppResourceFileKind.Resource)
497                                         continue;
498
499                                 s = arfi.Info.FullName;
500                                 basename = Path.GetFileNameWithoutExtension (s);
501                                 basedots = CountChars ('.', basename);
502                                 defaultFile = null;
503                                 
504                                 // If there are any files that start with this baseName, we have a default file
505                                 foreach (AppResourceFileInfo fi in files) {
506                                         if (fi.Seen)
507                                                 continue;
508                                         
509                                         string s2 = fi.Info.FullName;
510                                         if (s2 == null || s == s2)
511                                                 continue;
512                                         tmp = Path.GetFileNameWithoutExtension (s2);
513                                         filedots = CountChars ('.', tmp);
514
515                                         if (filedots == basedots + 1 && tmp.StartsWith (basename)) {
516                                                 if (IsFileCultureValid (s2) != null) {
517                                                         // A valid translated file for this name
518                                                         defaultFile = arfi;
519                                                         break;
520                                                 } else {
521                                                         // This file shares the base name, but the culture is invalid - we must
522                                                         // ignore it since the name of the generated strongly typed class for this
523                                                         // resource will clash with the one generated from the default file with
524                                                         // the given basename.
525                                                         fi.Seen = true;
526                                                 }
527                                         }
528                                 }
529                                 if (defaultFile != null) {
530                                         List<string> al = new List<string> ();
531                                         al.Add (GetResourceFile (arfi, false));
532                                         arfi.Seen = true;
533                                         groups.Add (al);
534                                         
535                                 }
536                         }
537                         groups.Sort (lcList);
538
539                         string tmp2;
540                         // Now find their translated counterparts
541                         foreach (List<string> al in groups) {
542                                 s = al [0];
543                                 tmp = Path.GetFileNameWithoutExtension (s);
544                                 if (tmp.StartsWith ("Resources."))
545                                         tmp = tmp.Substring (10);
546                                 foreach (AppResourceFileInfo arfi in files) {
547                                         if (arfi.Seen)
548                                                 continue;
549                                         s = arfi.Info.FullName;
550                                         if (s == null)
551                                                 continue;
552                                         tmp2 = arfi.Info.Name;
553                                         if (tmp2.StartsWith (tmp)) {
554                                                 al.Add (GetResourceFile (arfi, false));
555                                                 arfi.Seen = true;
556                                         }
557                                 }
558                         }
559
560                         // Anything that's left here might be orphans or lone default files.
561                         // For those files we check the part following the last dot
562                         // before the .resx/.resource extensions and test whether it's a registered
563                         // culture or not. If it is not a culture, then we have a
564                         // default file that doesn't have any translations. Otherwise,
565                         // the file is ignored (it's the same thing MS.NET does)
566                         foreach (AppResourceFileInfo arfi in files) {
567                                 if (arfi.Seen)
568                                         continue;
569
570                                 if (IsFileCultureValid (arfi.Info.FullName) != null)
571                                         continue; // Culture found, we reject the file
572
573                                 // A single default file, create a group
574                                 List<string> al = new List<string> ();
575                                 al.Add (GetResourceFile (arfi, false));
576                                 groups.Add (al);
577                         }
578                         groups.Sort (lcList);
579                         return groups.ToArray ();
580                 }
581
582                 // CodeDOM generation
583                 void DomFromResource (string resfile, CodeCompileUnit unit, Dictionary <string,bool> assemblies,
584                                       CodeDomProvider provider)
585                 {
586                         if (String.IsNullOrEmpty (resfile))
587                                 return;
588
589                         string fname, nsname, classname;
590
591                         fname = Path.GetFileNameWithoutExtension (resfile);
592                         nsname = Path.GetFileNameWithoutExtension (fname);
593                         classname = Path.GetExtension (fname);
594                         if (classname == null || classname.Length == 0) {
595                                 classname = nsname;
596                                 nsname = "Resources";
597                         } else {
598                                 if (!nsname.StartsWith ("Resources", StringComparison.InvariantCulture))
599                                         nsname = String.Concat ("Resources.", nsname);
600                                 classname = classname.Substring(1);
601                         }
602
603                         if (!String.IsNullOrEmpty (classname))
604                                 classname = classname.Replace ('.', '_');
605                         if (!String.IsNullOrEmpty (nsname))
606                                 nsname = nsname.Replace ('.', '_');
607                         
608                         if (!provider.IsValidIdentifier (nsname) || !provider.IsValidIdentifier (classname))
609                                 throw new ApplicationException ("Invalid resource file name.");
610
611                         ResourceReader res;
612                         try {
613                                 res = new ResourceReader (resfile);
614                         } catch (ArgumentException) {
615                                 // invalid stream, probably empty - ignore silently and abort
616                                 return;
617                         }
618                         
619                         CodeNamespace ns = new CodeNamespace (nsname);
620                         CodeTypeDeclaration cls = new CodeTypeDeclaration (classname);
621                         cls.IsClass = true;
622                         cls.TypeAttributes = TypeAttributes.Public | TypeAttributes.Sealed;
623
624                         CodeMemberField cmf = new CodeMemberField (typeof(CultureInfo), "_culture");
625                         cmf.InitExpression = new CodePrimitiveExpression (null);
626                         cmf.Attributes = MemberAttributes.Private | MemberAttributes.Final | MemberAttributes.Static;
627                         cls.Members.Add (cmf);
628
629                         cmf = new CodeMemberField (typeof(ResourceManager), "_resourceManager");
630                         cmf.InitExpression = new CodePrimitiveExpression (null);
631                         cmf.Attributes = MemberAttributes.Private | MemberAttributes.Final | MemberAttributes.Static;
632                         cls.Members.Add (cmf);
633                         
634                         // Property: ResourceManager
635                         CodeMemberProperty cmp = new CodeMemberProperty ();
636                         cmp.Attributes = MemberAttributes.Public | MemberAttributes.Final | MemberAttributes.Static;
637                         cmp.Name = "ResourceManager";
638                         cmp.HasGet = true;
639                         cmp.Type = new CodeTypeReference (typeof(ResourceManager));
640                         CodePropertyResourceManagerGet (cmp.GetStatements, resfile, classname);
641                         cls.Members.Add (cmp);
642
643                         // Property: Culture
644                         cmp = new CodeMemberProperty ();
645                         cmp.Attributes = MemberAttributes.Public | MemberAttributes.Final;
646                         cmp.Attributes = MemberAttributes.Public | MemberAttributes.Final | MemberAttributes.Static;
647                         cmp.Name = "Culture";
648                         cmp.HasGet = true;
649                         cmp.HasSet = true;
650                         cmp.Type = new CodeTypeReference (typeof(CultureInfo));
651                         CodePropertyGenericGet (cmp.GetStatements, "_culture", classname);
652                         CodePropertyGenericSet (cmp.SetStatements, "_culture", classname);
653                         cls.Members.Add (cmp);
654
655                         // Add the resource properties
656                         Dictionary<string,bool> imports = new Dictionary<string,bool> ();
657                         try {
658                                 foreach (DictionaryEntry de in res) {
659                                         Type type = de.Value.GetType ();
660
661                                         if (!imports.ContainsKey (type.Namespace))
662                                                 imports [type.Namespace] = true;
663
664                                         string asname = new AssemblyName (type.Assembly.FullName).Name;
665                                         if (!assemblies.ContainsKey (asname))
666                                                 assemblies [asname] = true;
667                                         
668                                         cmp = new CodeMemberProperty ();
669                                         cmp.Attributes = MemberAttributes.Public | MemberAttributes.Final | MemberAttributes.Static;
670                                         cmp.Name = SanitizeResourceName (provider, (string)de.Key);
671                                         cmp.HasGet = true;
672                                         CodePropertyResourceGet (cmp.GetStatements, (string)de.Key, type, classname);
673                                         cmp.Type = new CodeTypeReference (type);
674                                         cls.Members.Add (cmp);
675                                 }
676                         } catch (Exception ex) {
677                                 throw new ApplicationException ("Failed to compile global resources.", ex);
678                         }
679                         foreach (KeyValuePair<string,bool> de in imports)
680                                 ns.Imports.Add (new CodeNamespaceImport(de.Key));
681                         
682                         ns.Types.Add (cls);
683                         unit.Namespaces.Add (ns);
684                 }
685
686                 static bool is_identifier_start_character (int c)
687                 {
688                         return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || Char.IsLetter ((char)c);
689                 }
690
691                 static bool is_identifier_part_character (char c)
692                 {
693                         if (c >= 'a' && c <= 'z')
694                                 return true;
695
696                         if (c >= 'A' && c <= 'Z')
697                                 return true;
698
699                         if (c == '_' || (c >= '0' && c <= '9'))
700                                 return true;
701
702                         if (c < 0x80)
703                                 return false;
704
705                         return Char.IsLetter (c) || Char.GetUnicodeCategory (c) == UnicodeCategory.ConnectorPunctuation;
706                 }
707                 
708                 string SanitizeResourceName (CodeDomProvider provider, string name)
709                 {
710                         if (provider.IsValidIdentifier (name))
711                                 return provider.CreateEscapedIdentifier (name);
712
713                         var sb = new StringBuilder ();
714                         char ch = name [0];
715                         if (is_identifier_start_character (ch))
716                                 sb.Append (ch);
717                         else {
718                                 sb.Append ('_');
719                                 if (ch >= '0' && ch <= '9')
720                                         sb.Append (ch);
721                         }
722                         
723                         for (int i = 1; i < name.Length; i++) {
724                                 ch = name [i];
725                                 if (is_identifier_part_character (ch))
726                                         sb.Append (ch);
727                                 else
728                                         sb.Append ('_');
729                         }
730                         
731                         return provider.CreateEscapedIdentifier (sb.ToString ());
732                 }
733                 
734                 CodeObjectCreateExpression NewResourceManager (string name, string typename)
735                 {
736                         CodeExpression resname = new CodePrimitiveExpression (name);
737                         CodePropertyReferenceExpression asm = new CodePropertyReferenceExpression (
738                                 new CodeTypeOfExpression (new CodeTypeReference (typename)),
739                                 "Assembly");
740                         
741                         return new CodeObjectCreateExpression ("System.Resources.ResourceManager",
742                                                                new CodeExpression [] {resname, asm});
743                 }
744                 
745                 void CodePropertyResourceManagerGet (CodeStatementCollection csc, string resfile, string typename)
746                 {
747                         string name = Path.GetFileNameWithoutExtension (resfile);
748                         CodeStatement st;
749                         CodeExpression exp;
750
751                         exp = new CodeFieldReferenceExpression (new CodeTypeReferenceExpression (typename), "_resourceManager");
752                         st = new CodeConditionStatement (
753                                 new CodeBinaryOperatorExpression (
754                                         exp,
755                                         CodeBinaryOperatorType.IdentityInequality,
756                                         new CodePrimitiveExpression (null)),
757                                 new CodeStatement [] { new CodeMethodReturnStatement (exp) });
758                         csc.Add (st);
759
760                         st = new CodeAssignStatement (exp, NewResourceManager (name, typename));
761                         csc.Add (st);
762                         csc.Add (new CodeMethodReturnStatement (exp));
763                 }
764
765                 void CodePropertyResourceGet (CodeStatementCollection csc, string resname, Type restype, string typename)
766                 {
767                         CodeStatement st = new CodeVariableDeclarationStatement (
768                                 typeof (ResourceManager),
769                                 "rm",
770                                 new CodePropertyReferenceExpression (
771                                         new CodeTypeReferenceExpression (typename), "ResourceManager"));
772                         csc.Add (st);
773
774                         st = new CodeConditionStatement (
775                                 new CodeBinaryOperatorExpression (
776                                         new CodeVariableReferenceExpression ("rm"),
777                                         CodeBinaryOperatorType.IdentityEquality,
778                                         new CodePrimitiveExpression (null)),
779                                 new CodeStatement [] { new CodeMethodReturnStatement (new CodePrimitiveExpression (null)) });
780                         csc.Add (st);
781
782                         bool gotstr = (restype == typeof (string));
783                         CodeExpression exp = new CodeMethodInvokeExpression (
784                                 new CodeVariableReferenceExpression ("rm"),
785                                 gotstr ? "GetString" : "GetObject",
786                                 new CodeExpression [] { new CodePrimitiveExpression (resname),
787                                                         new CodeFieldReferenceExpression (
788                                                                 new CodeTypeReferenceExpression (typename), "_culture") });
789                         st = new CodeVariableDeclarationStatement (
790                                 restype,
791                                 "obj",
792                                 gotstr ? exp : new CodeCastExpression (restype, exp));
793                         csc.Add (st);
794                         csc.Add (new CodeMethodReturnStatement (new CodeVariableReferenceExpression ("obj")));
795                 }
796                 
797                 void CodePropertyGenericGet (CodeStatementCollection csc, string field, string typename)
798                 {
799                         csc.Add(new CodeMethodReturnStatement (
800                                         new CodeFieldReferenceExpression (
801                                                 new CodeTypeReferenceExpression (typename), field)));
802                 }
803
804                 void CodePropertyGenericSet (CodeStatementCollection csc, string field, string typename)
805                 {
806                         csc.Add(new CodeAssignStatement (
807                                         new CodeFieldReferenceExpression (new CodeTypeReferenceExpression (typename), field),
808                                         new CodeVariableReferenceExpression ("value")));
809                 }
810                 
811                 string CompileResource (AppResourceFileInfo arfi, bool local)
812                 {
813                         string path = arfi.Info.FullName;
814                         string rname = Path.GetFileNameWithoutExtension (path) + ".resources";
815                         if (!local)
816                                 rname = "Resources." + rname;
817                         
818                         string resource = Path.Combine (TempDirectory, rname);
819                         FileStream source = null, destination = null;
820                         IResourceReader reader = null;
821                         ResourceWriter writer = null;
822
823                         try {
824                                 source = new FileStream (path, FileMode.Open, FileAccess.Read);
825                                 destination = new FileStream (resource, FileMode.Create, FileAccess.Write);
826                                 reader = GetReaderForKind (arfi.Kind, source, path);
827                                 writer = new ResourceWriter (destination);
828                                 foreach (DictionaryEntry de in reader) {
829                                         object val = de.Value;
830                                         if (val is string)
831                                                 writer.AddResource ((string)de.Key, (string)val);
832                                         else
833                                                 writer.AddResource ((string)de.Key, val);
834                                 }
835                         } catch (Exception ex) {
836                                 throw new HttpException ("Failed to compile resource file", ex);
837                         } finally {
838                                 if (reader != null)
839                                         reader.Dispose ();
840                                 if (source != null)
841                                         source.Dispose ();
842                                 if (writer != null)
843                                         writer.Dispose ();
844                                 if (destination != null)
845                                         destination.Dispose ();
846                         }
847                         
848                         return resource;
849                 }
850
851                 IResourceReader GetReaderForKind (AppResourceFileKind kind, Stream stream, string path)
852                 {
853                         switch (kind) {
854                                 case AppResourceFileKind.ResX:
855                                         var ret = new ResXResourceReader (stream, new TypeResolutionService ());
856                                         if (!String.IsNullOrEmpty (path))
857                                                 ret.BasePath = Path.GetDirectoryName (path);
858                                         return ret;
859
860                                 case AppResourceFileKind.Resource:
861                                         return new ResourceReader (stream);
862
863                                 default:
864                                         return null;
865                         }
866                 }
867                 
868                                                                
869                 object OnCreateRandomFile (string path)
870                 {
871                         FileStream f = new FileStream (path, FileMode.CreateNew);
872                         f.Close ();
873                         return path;
874                 }
875         };
876 };
877