2008-04-19 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / BuildManager.cs
1 //
2 // System.Web.Compilation.BuildManager
3 //
4 // Authors:
5 //      Chris Toshok (toshok@ximian.com)
6 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
7 //      Marek Habersack (mhabersack@novell.com)
8 //
9 // (C) 2006-2008 Novell, Inc (http://www.novell.com)
10 //
11
12 //
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 // 
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 // 
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 //
32
33 #if NET_2_0
34
35 using System;
36 using System.CodeDom;
37 using System.CodeDom.Compiler;
38 using System.Collections;
39 using System.Collections.Generic;
40 using System.Collections.Specialized;
41 using System.IO;
42 using System.Reflection;
43 using System.Text;
44 using System.Threading;
45 using System.Web;
46 using System.Web.Caching;
47 using System.Web.Configuration;
48 using System.Web.Hosting;
49 using System.Web.Util;
50
51 namespace System.Web.Compilation {
52         public sealed class BuildManager {
53                 class BuildItem
54                 {
55                         public BuildProvider buildProvider;
56                         public AssemblyBuilder assemblyBuilder;
57                         public Type codeDomProviderType;
58                         public bool codeGenerated;
59                         public Assembly compiledAssembly;
60
61                         public ParseException ParserException {
62                                 get;
63                                 private set;
64                         }
65                         
66                         public bool ParsedFine {
67                                 get;
68                                 private set;
69                         }
70                         
71                         public CompilerParameters CompilerOptions {
72                                 get {
73                                         if (buildProvider == null)
74                                                 throw new HttpException ("No build provider.");
75                                         return buildProvider.CodeCompilerType.CompilerParameters;
76                                 }
77                         }
78
79                         public string VirtualPath {
80                                 get {
81                                         if (buildProvider == null)
82                                                 throw new HttpException ("No build provider.");
83                                         return buildProvider.VirtualPath;
84                                 }
85                         }
86
87                         public CodeCompileUnit CodeUnit {
88                                 get {
89                                         if (buildProvider == null)
90                                                 throw new HttpException ("No build provider.");
91                                         return buildProvider.CodeUnit;
92                                 }
93                         }
94                         
95                         public BuildItem (BuildProvider provider)
96                         {
97                                 this.buildProvider = provider;
98                                 try {
99                                         if (provider != null)
100                                                 codeDomProviderType = GetCodeDomProviderType (provider);
101                                         ParsedFine = true;
102                                         ParserException = null;
103                                 } catch (ParseException ex) {
104                                         ParsedFine = false;
105                                         ParserException = ex;
106                                 }
107                         }
108
109                         public void SetCompiledAssembly (AssemblyBuilder assemblyBuilder, Assembly compiledAssembly)
110                         {
111                                 if (this.compiledAssembly != null || this.assemblyBuilder == null || this.assemblyBuilder != assemblyBuilder)
112                                         return;
113
114                                 this.compiledAssembly = compiledAssembly;
115                         }
116                         
117                         public CodeDomProvider CreateCodeDomProvider ()
118                         {
119                                 if (codeDomProviderType == null)
120                                         throw new HttpException ("Unable to create compilation provider, no provider type given.");
121                                 
122                                 CodeDomProvider ret;
123
124                                 try {
125                                         ret = Activator.CreateInstance (codeDomProviderType) as CodeDomProvider;
126                                 } catch (Exception ex) {
127                                         throw new HttpException ("Failed to create compilation provider.", ex);
128                                 }
129
130                                 if (ret == null)
131                                         throw new HttpException ("Unable to instantiate code DOM provider '" + codeDomProviderType + "'.");
132
133                                 return ret;
134                         }
135
136                         public void GenerateCode ()
137                         {
138                                 if (buildProvider == null)
139                                         throw new HttpException ("Cannot generate code - missing build provider.");
140                                 
141                                 buildProvider.GenerateCode ();
142                                 codeGenerated = true;
143                         }
144
145                         public void StoreCodeUnit ()
146                         {
147                                 if (buildProvider == null)
148                                         throw new HttpException ("Cannot generate code - missing build provider.");
149                                 if (assemblyBuilder == null)
150                                         throw new HttpException ("Cannot generate code - missing assembly builder.");
151
152                                 buildProvider.GenerateCode (assemblyBuilder);
153                         }
154
155                         public override string ToString ()
156                         {
157                                 string ret = "BuildItem [";
158                                 string virtualPath = VirtualPath;
159                                 
160                                 if (!String.IsNullOrEmpty (virtualPath))
161                                         ret += virtualPath;
162
163                                 ret += "]";
164
165                                 return ret;
166                         }
167                 }
168
169                 class BuildCacheItem
170                 {
171                         public string compiledCustomString;
172                         public Assembly assembly;
173                         public Type type;
174                         public string virtualPath;
175
176                         public bool ValidBuild {
177                                 get;
178                                 private set;
179                         }
180
181                         public BuildCacheItem ()
182                         {
183                                 ValidBuild = false;
184                         }
185                         
186                         public BuildCacheItem (Assembly assembly, BuildProvider bp, CompilerResults results)
187                         {
188                                 this.assembly = assembly;
189                                 this.compiledCustomString = bp.GetCustomString (results);
190                                 this.type = bp.GetGeneratedType (results);
191                                 this.virtualPath = bp.VirtualPath;
192                                 ValidBuild = true;
193                         }
194                         
195                         public override string ToString ()
196                         {
197                                 StringBuilder sb = new StringBuilder ("BuildCacheItem [");
198                                 bool first = true;
199                                 
200                                 if (!String.IsNullOrEmpty (compiledCustomString)) {
201                                         sb.Append ("compiledCustomString: " + compiledCustomString);
202                                         first = false;
203                                 }
204                                 
205                                 if (assembly != null) {
206                                         sb.Append ((first ? "" : "; ") + "assembly: " + assembly.ToString ());
207                                         first = false;
208                                 }
209
210                                 if (type != null) {
211                                         sb.Append ((first ? "" : "; ") + "type: " + type.ToString ());
212                                         first = false;
213                                 }
214
215                                 if (!String.IsNullOrEmpty (virtualPath)) {
216                                         sb.Append ((first ? "" : "; ") + "virtualPath: " + virtualPath);
217                                         first = false;
218                                 }
219
220                                 sb.Append ("]");
221                                 
222                                 return sb.ToString ();
223                         }
224                 }
225                 
226                 enum BuildKind {
227                         Unknown,
228                         Pages,
229                         NonPages,
230                         Application,
231                         Theme,
232                         Fake
233                 };
234
235                 internal const string FAKE_VIRTUAL_PATH_PREFIX = "/@@MonoFakeVirtualPath@@";
236                 const string BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX = "Build_Manager";
237                 static int BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH = BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX.Length;
238                 static bool hosted;
239                 
240                 static object buildCacheLock = new object ();
241
242                 static Stack <BuildKind> recursiveBuilds = new Stack <BuildKind> ();
243                 
244                 //
245                 // Disabled - see comment at the end of BuildAssembly below
246                 //
247                 // static object buildCountLock = new object ();
248                 // static int buildCount = 0;
249                 
250                 static List<Assembly> AppCode_Assemblies = new List<Assembly>();
251                 static List<Assembly> TopLevel_Assemblies = new List<Assembly>();
252                 static bool haveResources;
253
254                 // The build cache which maps a virtual path to a build item with all the necessary
255                 // bits. 
256                 static Dictionary <string, BuildCacheItem> buildCache;
257
258                 // Maps the virtual path of a non-page build to the assembly that contains the
259                 // compiled type.
260                 static Dictionary <string, Assembly> nonPagesCache;
261                 
262                 static List <Assembly> referencedAssemblies = new List <Assembly> ();
263                 
264                 static Dictionary <string, object> compilationTickets;
265
266                 static Assembly globalAsaxAssembly;
267                 
268                 static Dictionary <string, BuildKind> knownFileTypes = new Dictionary <string, BuildKind> (StringComparer.OrdinalIgnoreCase) {
269                         {".aspx", BuildKind.Pages},
270                         {".asax", BuildKind.Application},
271                         {".ashx", BuildKind.NonPages},
272                         {".asmx", BuildKind.NonPages},
273                         {".ascx", BuildKind.NonPages},
274                         {".master", BuildKind.NonPages}
275                 };
276
277                 static Dictionary <string, bool> virtualPathsToIgnore;
278                 static bool haveVirtualPathsToIgnore;
279                 
280                 static BuildManager ()
281                 {
282                         IEqualityComparer <string> comparer;
283
284                         if (HttpRuntime.CaseInsensitive)
285                                 comparer = StringComparer.CurrentCultureIgnoreCase;
286                         else
287                                 comparer = StringComparer.CurrentCulture;
288
289                         buildCache = new Dictionary <string, BuildCacheItem> (comparer);
290                         nonPagesCache = new Dictionary <string, Assembly> (comparer);
291                         compilationTickets = new Dictionary <string, object> (comparer);
292
293                         AppDomain domain = AppDomain.CurrentDomain;
294                         hosted = (domain.GetData (ApplicationHost.MonoHostedDataKey) as string) == "yes";
295                 }
296                 
297                 internal static void ThrowNoProviderException (string extension)
298                 {
299                         string msg = "No registered provider for extension '{0}'.";
300                         throw new HttpException (String.Format (msg, extension));
301                 }
302                 
303                 public static object CreateInstanceFromVirtualPath (string virtualPath, Type requiredBaseType)
304                 {
305                         // virtualPath + Exists done in GetCompiledType()
306                         if (requiredBaseType == null)
307                                 throw new NullReferenceException (); // This is what MS does, but from somewhere else.
308
309                         // Get the Type.
310                         Type type = GetCompiledType (virtualPath);
311                         if (type == null)
312                                 //throw new HttpException ("Instance creation failed for virtual
313                                 //path '" + virtualPath + "'.");
314                                 return null;
315                         
316                         if (!requiredBaseType.IsAssignableFrom (type)) {
317                                 string msg = String.Format ("Type '{0}' does not inherit from '{1}'.",
318                                                                 type.FullName, requiredBaseType.FullName);
319                                 throw new HttpException (500, msg);
320                         }
321
322                         return Activator.CreateInstance (type, null);
323                 }
324
325                 public static ICollection GetReferencedAssemblies ()
326                 {
327                         List <Assembly> al = new List <Assembly> ();
328                         
329                         CompilationSection compConfig = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
330                         if (compConfig == null)
331                                 return al;
332                         
333                         bool addAssembliesInBin = false;
334                         foreach (AssemblyInfo info in compConfig.Assemblies) {
335                                 if (info.Assembly == "*")
336                                         addAssembliesInBin = true;
337                                 else
338                                         LoadAssembly (info, al);
339                         }
340
341                         foreach (Assembly topLevelAssembly in TopLevel_Assemblies)
342                                 al.Add (topLevelAssembly);
343
344                         foreach (string assLocation in WebConfigurationManager.ExtraAssemblies)
345                                 LoadAssembly (assLocation, al);
346                         
347                         if (addAssembliesInBin)
348                                 foreach (string s in HttpApplication.BinDirectoryAssemblies)
349                                         LoadAssembly (s, al);
350                         
351                         lock (buildCacheLock) {
352                                 foreach (Assembly asm in referencedAssemblies)
353                                         if (!al.Contains (asm))
354                                                 al.Add (asm);
355                                 
356                                 if (globalAsaxAssembly != null)
357                                         al.Add (globalAsaxAssembly);
358                         }
359                         
360                         return al;
361                 }
362
363                 static void LoadAssembly (string path, List <Assembly> al)
364                 {
365                         AddAssembly (Assembly.LoadFrom (path), al);
366                 }
367
368                 static void LoadAssembly (AssemblyInfo info, List <Assembly> al)
369                 {
370                         AddAssembly (Assembly.Load (info.Assembly), al);
371                 }
372
373                 static void AddAssembly (Assembly asm, List <Assembly> al)
374                 {
375                         if (al.Contains (asm))
376                                 return;
377
378                         al.Add (asm);
379                 }
380                 
381                 [MonoTODO ("Not implemented, always returns null")]
382                 public static BuildDependencySet GetCachedBuildDependencySet (HttpContext context, string virtualPath)
383                 {
384                         return null; // null is ok here until we store the dependency set in the Cache.
385                 }
386
387                 internal static BuildProvider GetBuildProviderForPath (VirtualPath virtualPath, bool throwOnMissing)
388                 {
389                         return GetBuildProviderForPath (virtualPath, null, throwOnMissing);
390                 }
391                 
392                 internal static BuildProvider GetBuildProviderForPath (VirtualPath virtualPath, CompilationSection section, bool throwOnMissing)
393                 {
394                         string extension = virtualPath.Extension;
395                         CompilationSection c = section;
396
397                         if (c == null)
398                                 c = WebConfigurationManager.GetSection ("system.web/compilation", virtualPath.Original) as CompilationSection;
399                         
400                         if (c == null)
401                                 if (throwOnMissing)
402                                         ThrowNoProviderException (extension);
403                                 else
404                                         return null;
405                         
406                         BuildProviderCollection coll = c.BuildProviders;
407                         if (coll == null || coll.Count == 0)
408                                 ThrowNoProviderException (extension);
409                         
410                         BuildProvider provider = coll.GetProviderForExtension (extension);
411                         if (provider == null)
412                                 if (throwOnMissing)
413                                         ThrowNoProviderException (extension);
414                                 else
415                                         return null;
416
417                         provider.SetVirtualPath (virtualPath);
418                         return provider;
419                 }
420
421                 static VirtualPath GetAbsoluteVirtualPath (string virtualPath)
422                 {
423                         string vp;
424
425                         if (!VirtualPathUtility.IsRooted (virtualPath)) {
426                                 HttpContext ctx = HttpContext.Current;
427                                 HttpRequest req = ctx != null ? ctx.Request : null;
428
429                                 if (req != null)
430                                         vp = VirtualPathUtility.GetDirectory (req.FilePath) + virtualPath;
431                                 else
432                                         throw new HttpException ("No context, cannot map paths.");
433                         } else
434                                 vp = virtualPath;
435
436                         return new VirtualPath (vp);
437                 }
438                 
439                 static BuildCacheItem GetCachedItem (VirtualPath virtualPath)
440                 {
441                         BuildCacheItem ret;
442                         
443                         lock (buildCacheLock) {
444                                 if (buildCache.TryGetValue (virtualPath.Absolute, out ret))
445                                         return ret;
446                         }
447
448                         return null;
449                 }
450                 
451                 public static Assembly GetCompiledAssembly (string virtualPath)
452                 {
453                         VirtualPath vp = GetAbsoluteVirtualPath (virtualPath);
454                         BuildCacheItem ret = GetCachedItem (vp);
455                         if (ret != null)
456                                 return ret.assembly;
457                         
458                         BuildAssembly (vp);
459                         ret = GetCachedItem (vp);
460                         if (ret != null)
461                                 return ret.assembly;
462
463                         return null;
464                 }
465
466                 public static Type GetCompiledType (string virtualPath)
467                 {
468                         VirtualPath vp = GetAbsoluteVirtualPath (virtualPath);
469                         BuildCacheItem ret = GetCachedItem (vp);
470
471                         if (ret != null)
472                                 return ret.type;
473                         
474                         BuildAssembly (vp);
475                         ret = GetCachedItem (vp);
476                         if (ret != null)
477                                 return ret.type;
478
479                         return null;
480                 }
481
482                 
483                 public static string GetCompiledCustomString (string virtualPath)
484                 {
485                         VirtualPath vp = GetAbsoluteVirtualPath (virtualPath);
486                         BuildCacheItem ret = GetCachedItem (vp);
487                         if (ret != null)
488                                 return ret.compiledCustomString;
489
490                         BuildAssembly (vp);
491                         ret = GetCachedItem (vp);
492                         if (ret != null)
493                                 return ret.compiledCustomString;
494
495                         return null;
496                 }
497
498                 static List <VirtualFile> GetFilesForBuild (VirtualPath virtualPath, out BuildKind kind)
499                 {
500                         string extension = virtualPath.Extension;
501                         var ret = new List <VirtualFile> ();
502                         
503                         if (virtualPath.StartsWith (FAKE_VIRTUAL_PATH_PREFIX)) {
504                                 kind = BuildKind.Fake;
505                                 return ret;
506                         }
507                         
508                         if (!knownFileTypes.TryGetValue (extension, out kind)) {
509                                 if (StrUtils.StartsWith (virtualPath.AppRelative, "~/App_Themes/"))
510                                         kind = BuildKind.Theme;
511                                 else
512                                         kind = BuildKind.Unknown;
513                         }
514
515                         if (kind == BuildKind.Theme || kind == BuildKind.Application)
516                                 return ret;
517                         
518                         bool doBatch = BatchMode;
519
520                         lock (buildCacheLock) {
521                                 if (recursiveBuilds.Count > 0 && recursiveBuilds.Peek () == kind)
522                                         doBatch = false;
523                                 recursiveBuilds.Push (kind);
524                         }
525
526                         string vpAbsolute = virtualPath.Absolute;
527                         VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
528                         VirtualDirectory dir = vpp.GetDirectory (vpAbsolute);
529                         
530                         if (doBatch && HostingEnvironment.HaveCustomVPP && dir != null && dir is DefaultVirtualDirectory)
531                                 doBatch = false;
532                         
533                         if (doBatch) {
534                                 if (dir == null)
535                                         throw new HttpException (404, "Virtual directory '" + virtualPath.Directory + "' does not exist.");
536                                 
537                                 BuildKind fileKind;
538                                 foreach (VirtualFile file in dir.Files) {
539                                         if (!knownFileTypes.TryGetValue (VirtualPathUtility.GetExtension (file.Name), out fileKind))
540                                                 continue;
541
542                                         if (kind == fileKind)
543                                                 ret.Add (file);
544                                 }
545                         } else {
546                                 VirtualFile vf = vpp.GetFile (vpAbsolute);
547                                 if (vf == null)
548                                         throw new HttpException (404, "Virtual file '" + virtualPath + "' does not exist.");
549                                 ret.Add (vf);
550                         }
551                         
552                         return ret;
553                 }
554
555                 static void SetCommonParameters (CompilationSection config, CompilerParameters p)
556                 {
557                         p.IncludeDebugInformation = config.Debug;
558                 }
559                 
560                 internal static CompilerType GetDefaultCompilerTypeForLanguage (string language, CompilationSection configSection)
561                 {
562                         // MS throws when accesing a Hashtable, we do here.
563                         if (language == null || language == "")
564                                 throw new ArgumentNullException ("language");
565                                 
566                         CompilationSection config;
567                         if (configSection == null)
568                                 config = WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection;
569                         else
570                                 config = configSection;
571                         
572                         Compiler compiler = config.Compilers.Get (language);
573                         CompilerParameters p;
574                         if (compiler != null) {
575                                 Type type = HttpApplication.LoadType (compiler.Type, true);
576                                 p = new CompilerParameters ();
577                                 p.CompilerOptions = compiler.CompilerOptions;
578                                 p.WarningLevel = compiler.WarningLevel;
579                                 SetCommonParameters (config, p);
580                                 return new CompilerType (type, p);
581                         }
582
583                         if (CodeDomProvider.IsDefinedLanguage (language)) {
584                                 CompilerInfo info = CodeDomProvider.GetCompilerInfo (language);
585                                 CompilerParameters par = info.CreateDefaultCompilerParameters ();
586                                 SetCommonParameters (config, par);
587                                 return new CompilerType (info.CodeDomProviderType, par);
588                         }
589                         
590                         throw new HttpException (String.Concat ("No compiler for language '", language, "'."));
591                 }
592                 
593                 internal static Type GetCodeDomProviderType (BuildProvider provider)
594                 {
595                         CompilerType codeCompilerType;
596                         Type codeDomProviderType = null;
597
598                         codeCompilerType = provider.CodeCompilerType;
599                         if (codeCompilerType != null)
600                                 codeDomProviderType = codeCompilerType.CodeDomProviderType;
601                                 
602                         if (codeDomProviderType == null)
603                                 throw new HttpException (String.Concat ("Provider '", provider, " 'fails to specify the compiler type."));
604
605                         return codeDomProviderType;
606                 }               
607
608                 static List <BuildItem> LoadBuildProviders (VirtualPath virtualPath, string virtualDir, Dictionary <string, bool> vpCache,
609                                                             out BuildKind kind, out string assemblyBaseName)
610                 {
611                         HttpContext ctx = HttpContext.Current;
612                         HttpRequest req = ctx != null ? ctx.Request : null;
613
614                         if (req == null)
615                                 throw new HttpException ("No context available, cannot build.");
616
617                         string vpAbsolute = virtualPath.Absolute;
618                         CompilationSection section = WebConfigurationManager.GetSection ("system.web/compilation", vpAbsolute) as CompilationSection;
619                         List <VirtualFile> files;
620                         
621                         try {
622                                 files = GetFilesForBuild (virtualPath, out kind);
623                         } catch (Exception ex) {
624                                 throw new HttpException ("Error loading build providers for path '" + virtualDir + "'.", ex);
625                         }
626
627                         List <BuildItem> ret = new List <BuildItem> ();
628                         BuildProvider provider = null;
629                         
630                         switch (kind) {
631                                 case BuildKind.Theme:
632                                         assemblyBaseName = "App_Theme_";
633                                         provider = new ThemeDirectoryBuildProvider ();
634                                         provider.SetVirtualPath (virtualPath);
635                                         break;
636
637                                 case BuildKind.Application:
638                                         assemblyBaseName = "App_global.asax.";
639                                         provider = new ApplicationFileBuildProvider ();
640                                         provider.SetVirtualPath (virtualPath);
641                                         break;
642
643                                 case BuildKind.Fake:
644                                         provider = GetBuildProviderForPath (virtualPath, section, false);
645                                         assemblyBaseName = null;
646                                         break;
647                                         
648                                 default:
649                                         assemblyBaseName = null;
650                                         break;
651                         }
652
653                         if (provider != null) {
654                                 ret.Add (new BuildItem (provider));
655                                 return ret;
656                         }
657                         
658                         string fileVirtualPath;
659                         string fileName;
660                         
661                         lock (buildCacheLock) {
662                                 foreach (VirtualFile f in files) {
663                                         fileVirtualPath = f.VirtualPath;
664                                         if (IgnoreVirtualPath (fileVirtualPath))
665                                                 continue;
666                                         
667                                         if (buildCache.ContainsKey (fileVirtualPath) || vpCache.ContainsKey (fileVirtualPath))
668                                                 continue;
669                                         
670                                         vpCache.Add (fileVirtualPath, true);
671                                         provider = GetBuildProviderForPath (new VirtualPath (fileVirtualPath), section, false);
672                                         if (provider == null)
673                                                 continue;
674
675                                         ret.Add (new BuildItem (provider));
676                                 }
677                         }
678
679                         return ret;
680                 }               
681
682                 static bool IgnoreVirtualPath (string virtualPath)
683                 {
684                         if (!haveVirtualPathsToIgnore)
685                                 return false;
686                         
687                         if (virtualPathsToIgnore.ContainsKey (virtualPath))
688                                 return true;
689                         
690                         return false;
691                 }
692
693                 static void AddPathToIgnore (string vp)
694                 {
695                         if (virtualPathsToIgnore == null)
696                                 virtualPathsToIgnore = new Dictionary <string, bool> ();
697                         
698                         VirtualPath path = GetAbsoluteVirtualPath (vp);
699                         string vpAbsolute = path.Absolute;
700                         if (virtualPathsToIgnore.ContainsKey (vpAbsolute))
701                                 return;
702
703                         virtualPathsToIgnore.Add (vpAbsolute, true);
704                         haveVirtualPathsToIgnore = true;
705                 }
706
707                 static char[] virtualPathsToIgnoreSplitChars = {','};
708                 static void LoadVirtualPathsToIgnore ()
709                 {
710                         if (virtualPathsToIgnore != null)
711                                 return;
712                         
713                         NameValueCollection appSettings = WebConfigurationManager.AppSettings;
714                         if (appSettings == null)
715                                 return;
716
717                         string pathsFromConfig = appSettings ["MonoAspnetBatchCompileIgnorePaths"];
718                         string pathsFromFile = appSettings ["MonoAspnetBatchCompileIgnoreFromFile"];
719
720                         if (!String.IsNullOrEmpty (pathsFromConfig)) {
721                                 string[] paths = pathsFromConfig.Split (virtualPathsToIgnoreSplitChars);
722                                 string path;
723                                 
724                                 foreach (string p in paths) {
725                                         path = p.Trim ();
726                                         if (path.Length == 0)
727                                                 continue;
728
729                                         AddPathToIgnore (path);
730                                 }
731                         }
732
733                         if (!String.IsNullOrEmpty (pathsFromFile)) {
734                                 string realpath;
735                                 HttpContext ctx = HttpContext.Current;
736                                 HttpRequest req = ctx != null ? ctx.Request : null;
737
738                                 if (req == null)
739                                         throw new HttpException ("Missing context, cannot continue.");
740
741                                 realpath = req.MapPath (pathsFromFile);
742                                 if (!File.Exists (realpath))
743                                         return;
744
745                                 string[] paths = File.ReadAllLines (realpath);
746                                 if (paths == null || paths.Length == 0)
747                                         return;
748
749                                 string path;
750                                 foreach (string p in paths) {
751                                         path = p.Trim ();
752                                         if (path.Length == 0)
753                                                 continue;
754
755                                         AddPathToIgnore (path);
756                                 }
757                         }
758                 }
759                 
760                 static AssemblyBuilder CreateAssemblyBuilder (string assemblyBaseName, VirtualPath virtualPath, BuildItem buildItem)
761                 {
762                         buildItem.assemblyBuilder = new AssemblyBuilder (virtualPath, buildItem.CreateCodeDomProvider (), assemblyBaseName);
763                         buildItem.assemblyBuilder.CompilerOptions = buildItem.CompilerOptions;
764                         
765                         return buildItem.assemblyBuilder;
766                 }
767
768                 static Dictionary <string, CompileUnitPartialType> GetUnitPartialTypes (CodeCompileUnit unit)
769                 {
770                         Dictionary <string, CompileUnitPartialType> ret = null;
771
772                         CompileUnitPartialType pt;
773                         foreach (CodeNamespace ns in unit.Namespaces) {
774                                 foreach (CodeTypeDeclaration type in ns.Types) {
775                                         if (type.IsPartial) {
776                                                 pt = new CompileUnitPartialType (unit, ns, type);
777
778                                                 if (ret == null)
779                                                         ret = new Dictionary <string, CompileUnitPartialType> ();
780                                                 
781                                                 ret.Add (pt.TypeName, pt);
782                                         }
783                                 }
784                         }
785
786                         return ret;
787                 }
788
789                 static bool TypeHasConflictingMember (CodeTypeDeclaration type, CodeMemberMethod member)
790                 {
791                         if (type == null || member == null)
792                                 return false;
793
794                         CodeMemberMethod method;
795                         string methodName = member.Name;
796                         int count;
797                         
798                         foreach (CodeTypeMember m in type.Members) {
799                                 if (m.Name != methodName)
800                                         continue;
801                                 
802                                 method = m as CodeMemberMethod;
803                                 if (method == null)
804                                         continue;
805                         
806                                 if ((count = method.Parameters.Count) != member.Parameters.Count)
807                                         continue;
808
809                                 CodeParameterDeclarationExpressionCollection methodA = method.Parameters;
810                                 CodeParameterDeclarationExpressionCollection methodB = member.Parameters;
811                         
812                                 for (int i = 0; i < count; i++)
813                                         if (methodA [i].Type != methodB [i].Type)
814                                                 continue;
815
816                                 return true;
817                         }
818                         
819                         return false;
820                 }
821
822                 static bool TypeHasConflictingMember (CodeTypeDeclaration type, CodeMemberField member)
823                 {
824                         if (type == null || member == null)
825                                 return false;
826
827                         CodeMemberField field = FindMemberByName (type, member.Name) as CodeMemberField;
828                         if (field == null)
829                                 return false;
830
831                         if (field.Type == member.Type)
832                                 return false; // This will get "flattened" by AssemblyBuilder
833                         
834                         return true;
835                 }
836                 
837                 static bool TypeHasConflictingMember (CodeTypeDeclaration type, CodeTypeMember member)
838                 {
839                         if (type == null || member == null)
840                                 return false;
841
842                         return (FindMemberByName (type, member.Name) != null);
843                 }
844
845                 static CodeTypeMember FindMemberByName (CodeTypeDeclaration type, string name)
846                 {
847                         foreach (CodeTypeMember m in type.Members) {
848                                 if (m == null || m.Name != name)
849                                         continue;
850                                 return m;
851                         }
852
853                         return null;
854                 }
855                 
856                 static bool PartialTypesConflict (CodeTypeDeclaration typeA, CodeTypeDeclaration typeB)
857                 {
858                         bool conflict;
859                         Type type;
860                         
861                         foreach (CodeTypeMember member in typeB.Members) {
862                                 conflict = false;
863                                 type = member.GetType ();
864                                 if (type == typeof (CodeMemberMethod))
865                                         conflict = TypeHasConflictingMember (typeA, (CodeMemberMethod) member);
866                                 else if (type == typeof (CodeMemberField))
867                                         conflict = TypeHasConflictingMember (typeA, (CodeMemberField) member);
868                                 else
869                                         conflict = TypeHasConflictingMember (typeA, member);
870                                 
871                                 if (conflict)
872                                         return true;
873                         }
874                         
875                         return false;
876                 }
877                 
878                 static bool CanAcceptCode (AssemblyBuilder assemblyBuilder, BuildItem buildItem)
879                 {
880                         CodeCompileUnit newUnit = buildItem.CodeUnit;
881                         if (newUnit == null)
882                                 return true;
883                         
884                         Dictionary <string, CompileUnitPartialType> unitPartialTypes = GetUnitPartialTypes (newUnit);
885
886                         if (unitPartialTypes == null)
887                                 return true;
888
889                         if (assemblyBuilder.Units.Count > CompilationConfig.MaxBatchSize)
890                                 return false;
891                         
892                         CompileUnitPartialType pt;                      
893                         foreach (List <CompileUnitPartialType> partialTypes in assemblyBuilder.PartialTypes.Values)
894                                 foreach (CompileUnitPartialType cupt in partialTypes)
895                                         if (unitPartialTypes.TryGetValue (cupt.TypeName, out pt) && PartialTypesConflict (cupt.PartialType, pt.PartialType))
896                                                 return false;
897                         
898                         return true;
899                 }
900                 
901                 static void AssignToAssemblyBuilder (string assemblyBaseName, VirtualPath virtualPath, BuildItem buildItem,
902                                                      Dictionary <Type, List <AssemblyBuilder>> assemblyBuilders)
903                 {
904                         if (!buildItem.codeGenerated)
905                                 buildItem.GenerateCode ();
906                         
907                         List <AssemblyBuilder> builders;
908
909                         if (!assemblyBuilders.TryGetValue (buildItem.codeDomProviderType, out builders)) {
910                                 builders = new List <AssemblyBuilder> ();
911                                 assemblyBuilders.Add (buildItem.codeDomProviderType, builders);
912                         }
913
914                         // Put it in the first assembly builder that doesn't have conflicting
915                         // partial types
916                         foreach (AssemblyBuilder assemblyBuilder in builders) {
917                                 if (CanAcceptCode (assemblyBuilder, buildItem)) {
918                                         buildItem.assemblyBuilder = assemblyBuilder;
919                                         buildItem.StoreCodeUnit ();
920                                         return;
921                                 }
922                         }
923
924                         // None of the existing builders can accept this unit, get it a new builder
925                         builders.Add (CreateAssemblyBuilder (assemblyBaseName, virtualPath, buildItem));
926                         buildItem.StoreCodeUnit ();
927                 }
928
929                 static void AssertVirtualPathExists (VirtualPath virtualPath)
930                 {
931                         string realpath;
932                         bool dothrow = false;
933                         
934                         if (virtualPath.StartsWith (FAKE_VIRTUAL_PATH_PREFIX)) {
935                                 realpath = virtualPath.Original.Substring (FAKE_VIRTUAL_PATH_PREFIX.Length);
936                                 if (!File.Exists (realpath) && !Directory.Exists (realpath))
937                                         dothrow = true;
938                         } else {
939                                 VirtualPathProvider vpp = HostingEnvironment.VirtualPathProvider;
940                                 string vpAbsolute = virtualPath.Absolute;
941                                 
942                                 if (!vpp.FileExists (vpAbsolute) && !vpp.DirectoryExists (vpAbsolute))
943                                         dothrow = true;
944                         }
945
946                         if (dothrow)
947                                 throw new HttpException (404, "The file '" + virtualPath + "' does not exist.");
948                 }
949                 
950                 static void BuildAssembly (VirtualPath virtualPath)
951                 {
952                         AssertVirtualPathExists (virtualPath);
953                         LoadVirtualPathsToIgnore ();
954                         
955                         object ticket;
956                         bool acquired;
957                         string virtualDir = virtualPath.Directory;
958                         BuildKind buildKind = BuildKind.Unknown;
959                         bool kindPushed = false;
960                         string vpAbsolute = virtualPath.Absolute;
961                         BuildItem vpBuildItem = null;
962                         
963                         acquired = AcquireCompilationTicket (virtualDir, out ticket);
964                         try {
965                                 Monitor.Enter (ticket);
966                                 lock (buildCacheLock) {
967                                         if (buildCache.ContainsKey (vpAbsolute))
968                                                 return;
969                                 }
970                                 
971                                 string assemblyBaseName;
972                                 Dictionary <string, bool> vpCache = new Dictionary <string, bool> ();
973                                 List <BuildItem> buildItems = LoadBuildProviders (virtualPath, virtualDir, vpCache, out buildKind, out assemblyBaseName);
974                                 kindPushed = true;
975
976                                 if (buildItems.Count == 0)
977                                         return;
978                                 
979                                 Dictionary <Type, List <AssemblyBuilder>> assemblyBuilders = new Dictionary <Type, List <AssemblyBuilder>> ();
980                                 bool checkForRecursion = buildKind == BuildKind.NonPages;
981                                 
982                                 foreach (BuildItem buildItem in buildItems) {
983                                         if (buildItem.VirtualPath == vpAbsolute) {
984                                                 if (!buildItem.ParsedFine)
985                                                         throw buildItem.ParserException;
986                                                 vpBuildItem = buildItem;
987                                         } else if (!buildItem.ParsedFine)
988                                                 continue;
989                                         
990                                         if (checkForRecursion) {
991                                                 // Expensive but, alas, necessary - the builder in
992                                                 // our list might've been put into a different
993                                                 // assembly in a recursive call.
994                                                 lock (buildCacheLock) {
995                                                         if (buildCache.ContainsKey (buildItem.VirtualPath))
996                                                                 continue;
997                                                 }
998                                         }
999                                         
1000                                         if (buildItem.assemblyBuilder == null)
1001                                                 AssignToAssemblyBuilder (assemblyBaseName, virtualPath, buildItem, assemblyBuilders);
1002                                 }
1003                                 CompilerResults results;
1004                                 Assembly compiledAssembly;
1005                                 string vp;
1006                                 BuildProvider bp;
1007                                 
1008                                 foreach (List <AssemblyBuilder> abuilders in assemblyBuilders.Values) {
1009                                         foreach (AssemblyBuilder abuilder in abuilders) {
1010                                                 abuilder.AddAssemblyReference (GetReferencedAssemblies () as List <Assembly>);
1011                                                 results = abuilder.BuildAssembly (virtualPath);
1012                                                 
1013                                                 // No results is not an error - it is possible that the assembly builder contained only .asmx and
1014                                                 // .ashx files which had no body, just the directive. In such case, no code unit or code file is added
1015                                                 // to the assembly builder and, in effect, no assembly is produced but there are STILL types that need
1016                                                 // to be added to the cache.
1017                                                 compiledAssembly = results != null ? results.CompiledAssembly : null;
1018                                                 
1019                                                 lock (buildCacheLock) {
1020                                                         switch (buildKind) {
1021                                                                 case BuildKind.NonPages:
1022                                                                         if (compiledAssembly != null)
1023                                                                                 AddToReferencedAssemblies (compiledAssembly);
1024                                                                         break;
1025
1026                                                                 case BuildKind.Application:
1027                                                                         globalAsaxAssembly = compiledAssembly;
1028                                                                         break;
1029                                                         }
1030                                                         
1031                                                         foreach (BuildItem buildItem in buildItems) {
1032                                                                 if (buildItem.assemblyBuilder != abuilder)
1033                                                                         continue;
1034                                                                 
1035                                                                 vp = buildItem.VirtualPath;
1036                                                                 bp = buildItem.buildProvider;
1037                                                                 buildItem.SetCompiledAssembly (abuilder, compiledAssembly);
1038                                                                 
1039                                                                 if (!buildCache.ContainsKey (vp)) {
1040                                                                         AddToCache (vp, bp);
1041                                                                         buildCache.Add (vp, new BuildCacheItem (compiledAssembly, bp, results));
1042                                                                 }
1043
1044                                                                 if (compiledAssembly != null && !nonPagesCache.ContainsKey (vp))
1045                                                                         nonPagesCache.Add (vp, compiledAssembly);
1046                                                         }
1047                                                 }
1048                                         }
1049                                 }
1050
1051                                 // WARNING: enabling this code breaks the test suite - it stays
1052                                 // disabled until I figure out what to do about it.
1053                                 // See http://support.microsoft.com/kb/319947
1054 //                              lock (buildCountLock) {
1055 //                                      buildCount++;
1056 //                                      if (buildCount > CompilationConfig.NumRecompilesBeforeAppRestart)
1057 //                                              HttpRuntime.UnloadAppDomain ();
1058 //                              }
1059                         } finally {
1060                                 if (kindPushed && buildKind == BuildKind.Pages || buildKind == BuildKind.NonPages) {
1061                                         lock (buildCacheLock) {
1062                                                 recursiveBuilds.Pop ();
1063                                         }
1064                                 }
1065                                 
1066                                 Monitor.Exit (ticket);
1067                                 if (acquired)
1068                                         ReleaseCompilationTicket (virtualDir);
1069                         }
1070                 }
1071
1072                 internal static void AddToReferencedAssemblies (Assembly asm)
1073                 {
1074                         lock (buildCacheLock) {
1075                                 if (referencedAssemblies.Contains (asm))
1076                                         return;
1077                                 
1078                                 referencedAssemblies.Add (asm);
1079                         }
1080                 }
1081                 
1082                 internal static void AddToCache (string virtualPath, BuildProvider bp)
1083                 {
1084                         HttpContext ctx = HttpContext.Current;
1085                         HttpRequest req = ctx != null ? ctx.Request : null;
1086
1087                         if (req == null)
1088                                 throw new HttpException ("No current context.");
1089                         
1090                         CacheItemRemovedCallback cb = new CacheItemRemovedCallback (OnVirtualPathChanged);
1091                         CacheDependency dep;
1092                         ICollection col = bp.VirtualPathDependencies;
1093                         int count;
1094                         
1095                         if (col != null && (count = col.Count) > 0) {
1096                                 string[] files = new string [count];
1097                                 int fileCount = 0;
1098                                 string file;
1099                                 
1100                                 foreach (object o in col) {
1101                                         file = o as string;
1102                                         if (String.IsNullOrEmpty (file))
1103                                                 continue;
1104                                         files [fileCount++] = req.MapPath (file);
1105                                 }
1106
1107                                 dep = new CacheDependency (files);
1108                         } else
1109                                 dep = null;
1110                         
1111                         HttpRuntime.InternalCache.Add (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX + virtualPath,
1112                                                        true,
1113                                                        dep,
1114                                                        Cache.NoAbsoluteExpiration,
1115                                                        Cache.NoSlidingExpiration,
1116                                                        CacheItemPriority.High,
1117                                                        cb);
1118                                                        
1119                 }
1120
1121                 static int RemoveVirtualPathFromCaches (VirtualPath virtualPath)
1122                 {
1123                         lock (buildCacheLock) {
1124                                 // This is expensive, but we must do it - we must not leave
1125                                 // the assembly in which the invalidated type lived. At the same
1126                                 // time, we must remove the virtual paths which were in that
1127                                 // assembly from the other caches, so that they get recompiled.
1128                                 BuildCacheItem item = GetCachedItem (virtualPath);
1129                                 if (item == null)
1130                                         return 0;
1131
1132                                 string vpAbsolute = virtualPath.Absolute;
1133                                 if (buildCache.ContainsKey (vpAbsolute))
1134                                         buildCache.Remove (vpAbsolute);
1135                                 
1136                                 Assembly asm;
1137                                 
1138                                 if (nonPagesCache.TryGetValue (vpAbsolute, out asm)) {
1139                                         nonPagesCache.Remove (vpAbsolute);
1140                                         if (referencedAssemblies.Contains (asm))
1141                                                 referencedAssemblies.Remove (asm);
1142
1143                                         ArrayList extraAssemblies = WebConfigurationManager.ExtraAssemblies;
1144                                         if (extraAssemblies != null && extraAssemblies.Contains (asm.Location))
1145                                                 extraAssemblies.Remove (asm.Location);
1146                                         
1147                                         List <string> keysToRemove = new List <string> ();
1148                                         foreach (KeyValuePair <string, Assembly> kvp in nonPagesCache)
1149                                                 if (kvp.Value == asm)
1150                                                         keysToRemove.Add (kvp.Key);
1151                                         
1152                                         foreach (string key in keysToRemove) {
1153                                                 nonPagesCache.Remove (key);
1154
1155                                                 if (buildCache.ContainsKey (key))
1156                                                         buildCache.Remove (key);
1157                                         }
1158                                 }
1159                                 
1160                                 return 1;
1161                         }
1162                 }
1163                 
1164                 static void OnVirtualPathChanged (string key, object value, CacheItemRemovedReason removedReason)
1165                 {
1166                         string virtualPath;
1167
1168                         if (StrUtils.StartsWith (key, BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX))
1169                                 virtualPath = key.Substring (BUILD_MANAGER_VIRTUAL_PATH_CACHE_PREFIX_LENGTH);
1170                         else
1171                                 return;
1172
1173                         RemoveVirtualPathFromCaches (new VirtualPath (virtualPath));
1174                 }
1175                 
1176                 static bool AcquireCompilationTicket (string key, out object ticket)
1177                 {
1178                         lock (((ICollection)compilationTickets).SyncRoot) {
1179                                 if (!compilationTickets.TryGetValue (key, out ticket)) {
1180                                         ticket = new Mutex ();
1181                                         compilationTickets.Add (key, ticket);
1182                                         return true;
1183                                 }
1184                         }
1185                         
1186                         return false;
1187                 }
1188
1189                 static void ReleaseCompilationTicket (string key)
1190                 {
1191                         lock (((ICollection)compilationTickets).SyncRoot) {
1192                                 if (compilationTickets.ContainsKey (key))
1193                                         compilationTickets.Remove (key);
1194                         }
1195                 }
1196                 
1197                 // The 2 GetType() overloads work on the global.asax, App_GlobalResources, App_WebReferences or App_Browsers
1198                 public static Type GetType (string typeName, bool throwOnError)
1199                 {
1200                         return GetType (typeName, throwOnError, false);
1201                 }
1202
1203                 public static Type GetType (string typeName, bool throwOnError, bool ignoreCase)
1204                 {
1205                         Type ret = null;
1206                         try {
1207                                 foreach (Assembly asm in TopLevel_Assemblies) {
1208                                         ret = asm.GetType (typeName, throwOnError, ignoreCase);
1209                                         if (ret != null)
1210                                                 break;
1211                                 }
1212                         } catch (Exception ex) {
1213                                 throw new HttpException ("Failed to find the specified type.", ex);
1214                         }
1215                         return ret;
1216                 }
1217
1218                 internal static ICollection GetVirtualPathDependencies (string virtualPath, BuildProvider bprovider)
1219                 {
1220                         BuildProvider provider = bprovider;
1221                         if (provider == null)
1222                                 provider = GetBuildProviderForPath (new VirtualPath (virtualPath), false);
1223                         if (provider == null)
1224                                 return null;
1225                         return provider.VirtualPathDependencies;
1226                 }
1227                 
1228                 public static ICollection GetVirtualPathDependencies (string virtualPath)
1229                 {
1230                         return GetVirtualPathDependencies (virtualPath, null);
1231                 }
1232                 
1233                 // Assemblies built from the App_Code directory
1234                 public static IList CodeAssemblies {
1235                         get { return AppCode_Assemblies; }
1236                 }
1237
1238                 internal static IList TopLevelAssemblies {
1239                         get { return TopLevel_Assemblies; }
1240                 }
1241
1242                 internal static bool HaveResources {
1243                         get { return haveResources; }
1244                         set { haveResources = value; }
1245                 }
1246                 
1247                 internal static bool BatchMode {
1248                         get {
1249                                 if (!hosted)
1250                                         return false; // Fix for bug #380985
1251                                 
1252                                 return CompilationConfig.Batch;
1253                         }
1254                 }
1255
1256                 internal static CompilationSection CompilationConfig {
1257                         get { return WebConfigurationManager.GetSection ("system.web/compilation") as CompilationSection; }
1258                 }
1259                         
1260         }
1261 }
1262
1263 #endif
1264