2007-04-06 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / BaseCompiler.cs
1 //
2 // System.Web.Compilation.BaseCompiler
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (c) Copyright 2002,2003 Ximian, Inc (http://www.ximian.com)
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.CodeDom;
33 using System.CodeDom.Compiler;
34 using System.Collections;
35 using System.Collections.Specialized;
36 using System.Reflection;
37 using System.Text;
38 using System.Web.UI;
39 using System.Web.Configuration;
40 using System.IO;
41
42 namespace System.Web.Compilation
43 {
44         abstract class BaseCompiler
45         {
46 #if NET_2_0
47                 static BindingFlags replaceableFlags = BindingFlags.Public | BindingFlags.NonPublic |
48                                                   BindingFlags.Instance;
49 #endif
50
51                 TemplateParser parser;
52                 CodeDomProvider provider;
53                 ICodeCompiler compiler;
54                 CodeCompileUnit unit;
55                 CodeNamespace mainNS;
56                 CompilerParameters compilerParameters;
57 #if NET_2_0
58                 bool isRebuilding = false;
59                 protected Hashtable partialNameOverride = new Hashtable();
60                 protected CodeTypeDeclaration partialClass;
61                 protected CodeTypeReferenceExpression partialClassExpr;
62 #endif
63                 protected CodeTypeDeclaration mainClass;
64                 protected CodeTypeReferenceExpression mainClassExpr;
65                 protected static CodeThisReferenceExpression thisRef = new CodeThisReferenceExpression ();
66
67                 protected BaseCompiler (TemplateParser parser)
68                 {
69                         compilerParameters = new CompilerParameters ();
70                         this.parser = parser;
71                 }
72
73                 void Init ()
74                 {
75                         unit = new CodeCompileUnit ();
76 #if NET_2_0
77                         if (parser.IsPartial) {
78                                 string ns = null;
79                                 string classtype = parser.PartialClassName;
80
81                                 if (classtype.Contains (".")) {
82                                         int dot = classtype.LastIndexOf (".");
83                                         ns = classtype.Substring (0, dot);
84                                         classtype = classtype.Substring (dot + 1);
85                                 }
86                                 
87                                 CodeNamespace partialNS = new CodeNamespace (ns);
88                                 partialClass = new CodeTypeDeclaration (classtype);
89                                 partialClass.IsPartial = true;  
90                                 partialClassExpr = new CodeTypeReferenceExpression (parser.PartialClassName);
91                                 
92                                 unit.Namespaces.Add (partialNS);
93                                 partialClass.TypeAttributes = TypeAttributes.Public;
94                                 partialNS.Types.Add (partialClass);
95                         }
96 #endif
97                         mainNS = new CodeNamespace ("ASP");
98                         mainClass = new CodeTypeDeclaration (parser.ClassName);
99                         CodeTypeReference baseTypeRef;
100 #if NET_2_0
101                         if (partialClass != null) {
102                                 baseTypeRef = new CodeTypeReference (parser.PartialClassName);
103                                 baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
104                         } else {
105                                 baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
106                                 if (parser.BaseTypeIsGlobal)
107                                         baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
108                         }
109 #else
110                         baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
111 #endif
112                         mainClass.BaseTypes.Add (baseTypeRef);
113
114                         mainClassExpr = new CodeTypeReferenceExpression ("ASP." + parser.ClassName);
115
116                         unit.Namespaces.Add (mainNS);
117                         mainClass.TypeAttributes = TypeAttributes.Public;
118                         mainNS.Types.Add (mainClass);
119
120                         foreach (object o in parser.Imports) {
121                                 if (o is string)
122                                         mainNS.Imports.Add (new CodeNamespaceImport ((string) o));
123                         }
124
125                         // StringCollection.Contains has O(n) complexity, but
126                         // considering the number of comparisons we make on
127                         // average and the fact that using an intermediate array
128                         // would be even more costly, this is fine here.
129                         StringCollection refAsm = unit.ReferencedAssemblies;
130                         string asmName;
131                         if (parser.Assemblies != null) {
132                                 foreach (object o in parser.Assemblies) {
133                                         asmName = o as string;
134                                         if (asmName != null && !refAsm.Contains (asmName))
135                                                 refAsm.Add (asmName);
136                                 }
137                         }
138
139 #if NET_2_0
140                         ArrayList al = WebConfigurationManager.ExtraAssemblies;
141                         if (al != null && al.Count > 0) {
142                                 foreach (object o in al) {
143                                         asmName = o as string;
144                                         if (asmName != null && !refAsm.Contains (asmName))
145                                                 refAsm.Add (asmName);
146                                 }
147                         }
148
149                         IList list = BuildManager.CodeAssemblies;
150                         if (list != null && list.Count > 0) {
151                                 Assembly asm;
152                                 foreach (object o in list) {
153                                         asm = o as Assembly;
154                                         if (o == null)
155                                                 continue;
156                                         asmName = asm.Location;
157                                         if (asmName != null && !refAsm.Contains (asmName))
158                                                 refAsm.Add (asmName);
159                                 }
160                         }
161 #endif
162                         // Late-bound generators specifics (as for MonoBASIC/VB.NET)
163                         unit.UserData["RequireVariableDeclaration"] = parser.ExplicitOn;
164                         unit.UserData["AllowLateBound"] = !parser.StrictOn;
165                         
166                         AddInterfaces ();
167                         AddClassAttributes ();
168                         CreateStaticFields ();
169                         AddApplicationAndSessionObjects ();
170                         AddScripts ();
171                         CreateMethods ();
172                         CreateConstructor (null, null);
173                 }
174
175 #if NET_2_0
176                 internal CodeDomProvider Provider {
177                         get { return provider; }
178                 }
179
180                 internal CodeCompileUnit CompileUnit {
181                         get { return unit; }
182                 }
183 #endif
184                 protected virtual void CreateStaticFields ()
185                 {
186                         CodeMemberField fld = new CodeMemberField (typeof (bool), "__initialized");
187                         fld.Attributes = MemberAttributes.Private | MemberAttributes.Static;
188                         fld.InitExpression = new CodePrimitiveExpression (false);
189                         mainClass.Members.Add (fld);
190                 }
191
192 #if NET_2_0
193                 void AssignAppRelativeVirtualPath (CodeConstructor ctor)
194                 {
195                         Type baseType = parser.CodeFileBaseClassType;
196
197                         if (baseType == null)
198                                 baseType = parser.BaseType;
199                         if (baseType == null)
200                                 return;
201                         if (!baseType.IsSubclassOf (typeof (System.Web.UI.TemplateControl)))
202                                 return;
203                         
204                         string arvp = Path.Combine (parser.BaseVirtualDir, Path.GetFileName (parser.InputFile));
205                         if (VirtualPathUtility.IsAbsolute (arvp))
206                                 arvp = "~" + arvp;
207                         
208                         CodeExpression cast = new CodeCastExpression (baseType, new CodeThisReferenceExpression ());
209                         CodePropertyReferenceExpression arvpProp = new CodePropertyReferenceExpression (cast, "AppRelativeVirtualPath");
210                         CodeAssignStatement arvpAssign = new CodeAssignStatement ();
211                         arvpAssign.Left = arvpProp;
212                         arvpAssign.Right = new CodePrimitiveExpression (arvp);
213                         ctor.Statements.Add (arvpAssign);
214                 }
215 #endif
216                 
217                 protected virtual void CreateConstructor (CodeStatementCollection localVars,
218                                                           CodeStatementCollection trueStmt)
219                 {
220                         CodeConstructor ctor = new CodeConstructor ();
221                         ctor.Attributes = MemberAttributes.Public;
222                         mainClass.Members.Add (ctor);
223
224 #if NET_2_0
225                         AssignAppRelativeVirtualPath (ctor);
226 #endif
227                         if (localVars != null)
228                                 ctor.Statements.AddRange (localVars);
229
230                         CodeTypeReferenceExpression r;
231 #if NET_2_0
232                         if (parser.IsPartial)
233                                 r = new CodeTypeReferenceExpression (mainClass.Name);
234                         else
235 #endif
236                         r = new CodeTypeReferenceExpression (mainNS.Name + "." + mainClass.Name);
237                         CodeFieldReferenceExpression initialized;
238                         initialized = new CodeFieldReferenceExpression (r, "__initialized");
239                         
240                         CodeBinaryOperatorExpression bin;
241                         bin = new CodeBinaryOperatorExpression (initialized,
242                                                                 CodeBinaryOperatorType.ValueEquality,
243                                                                 new CodePrimitiveExpression (false));
244
245                         CodeAssignStatement assign = new CodeAssignStatement (initialized,
246                                                                               new CodePrimitiveExpression (true));
247
248                         CodeConditionStatement cond = new CodeConditionStatement (bin, assign);
249                         if (trueStmt != null)
250                                 cond.TrueStatements.AddRange (trueStmt);
251                         
252                         ctor.Statements.Add (cond);
253                 }
254                 
255                 void AddScripts ()
256                 {
257                         if (parser.Scripts == null || parser.Scripts.Count == 0)
258                                 return;
259
260                         foreach (object o in parser.Scripts) {
261                                 if (o is string)
262                                         mainClass.Members.Add (new CodeSnippetTypeMember ((string) o));
263                         }
264                 }
265                 
266                 protected internal virtual void CreateMethods ()
267                 {
268                 }
269
270 #if NET_2_0
271                 void InternalCreatePageProperty (string retType, string name, string contextProperty)
272                 {
273                         CodeMemberProperty property = new CodeMemberProperty ();
274                         property.Name = name;
275                         property.Type = new CodeTypeReference (retType);
276                         property.Attributes = MemberAttributes.Family | MemberAttributes.Final;
277
278                         CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
279                         CodeCastExpression cast = new CodeCastExpression ();
280                         ret.Expression = cast;
281                         
282                         CodePropertyReferenceExpression refexp = new CodePropertyReferenceExpression ();
283                         refexp.TargetObject = new CodePropertyReferenceExpression (new CodeThisReferenceExpression (), "Context");
284                         refexp.PropertyName = contextProperty;
285                         
286                         cast.TargetType = new CodeTypeReference (retType);
287                         cast.Expression = refexp;
288                         
289                         property.GetStatements.Add (ret);
290                         if (partialClass == null)
291                                 mainClass.Members.Add (property);
292                         else
293                                 partialClass.Members.Add (property);
294                 }
295                 
296                 protected void CreateProfileProperty ()
297                 {
298                         string retType;
299                         ProfileSection ps = WebConfigurationManager.GetSection ("system.web/profile") as ProfileSection;
300                         if (ps != null && ps.PropertySettings.Count > 0)
301                                 retType = "ProfileCommon";
302                         else
303                                 retType = "System.Web.Profile.DefaultProfile";
304                         InternalCreatePageProperty (retType, "Profile", "Profile");
305                 }
306 #endif
307                 
308                 protected virtual void AddInterfaces ()
309                 {
310                         if (parser.Interfaces == null)
311                                 return;
312
313                         foreach (object o in parser.Interfaces) {
314                                 if (o is string)
315                                         mainClass.BaseTypes.Add (new CodeTypeReference ((string) o));
316                         }
317                 }
318
319                 protected virtual void AddClassAttributes ()
320                 {
321                 }
322                 
323                 protected virtual void AddApplicationAndSessionObjects ()
324                 {
325                 }
326
327                 /* Utility methods for <object> stuff */
328                 protected void CreateApplicationOrSessionPropertyForObject (Type type,
329                                                                             string propName,
330                                                                             bool isApplication,
331                                                                             bool isPublic)
332                 {
333                         /* if isApplication this generates (the 'cachedapp' field is created earlier):
334                         private MyNS.MyClass app {
335                                 get {
336                                         if ((this.cachedapp == null)) {
337                                                 this.cachedapp = ((MyNS.MyClass)
338                                                         (this.Application.StaticObjects.GetObject("app")));
339                                         }
340                                         return this.cachedapp;
341                                 }
342                         }
343
344                         else, this is for Session:
345                         private MyNS.MyClass ses {
346                                 get {
347                                         return ((MyNS.MyClass) (this.Session.StaticObjects.GetObject("ses")));
348                                 }
349                         }
350
351                         */
352
353                         CodeExpression result = null;
354
355                         CodeMemberProperty prop = new CodeMemberProperty ();
356                         prop.Type = new CodeTypeReference (type);
357                         prop.Name = propName;
358                         if (isPublic)
359                                 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
360                         else
361                                 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
362
363                         CodePropertyReferenceExpression p1;
364                         if (isApplication)
365                                 p1 = new CodePropertyReferenceExpression (thisRef, "Application");
366                         else
367                                 p1 = new CodePropertyReferenceExpression (thisRef, "Session");
368
369                         CodePropertyReferenceExpression p2;
370                         p2 = new CodePropertyReferenceExpression (p1, "StaticObjects");
371
372                         CodeMethodReferenceExpression getobject;
373                         getobject = new CodeMethodReferenceExpression (p2, "GetObject");
374
375                         CodeMethodInvokeExpression invoker;
376                         invoker = new CodeMethodInvokeExpression (getobject,
377                                                 new CodePrimitiveExpression (propName));
378
379                         CodeCastExpression cast = new CodeCastExpression (prop.Type, invoker);
380
381                         if (isApplication) {
382                                 CodeFieldReferenceExpression field;
383                                 field = new CodeFieldReferenceExpression (thisRef, "cached" + propName);
384
385                                 CodeConditionStatement stmt = new CodeConditionStatement();
386                                 stmt.Condition = new CodeBinaryOperatorExpression (field,
387                                                         CodeBinaryOperatorType.IdentityEquality,
388                                                         new CodePrimitiveExpression (null));
389
390                                 CodeAssignStatement assign = new CodeAssignStatement ();
391                                 assign.Left = field;
392                                 assign.Right = cast;
393                                 stmt.TrueStatements.Add (assign);
394                                 prop.GetStatements.Add (stmt);
395                                 result = field;
396                         } else {
397                                 result = cast;
398                         }
399                                                 
400                         prop.GetStatements.Add (new CodeMethodReturnStatement (result));
401                         mainClass.Members.Add (prop);
402                 }
403
404                 protected string CreateFieldForObject (Type type, string name)
405                 {
406                         string fieldName = "cached" + name;
407                         CodeMemberField f = new CodeMemberField (type, fieldName);
408                         f.Attributes = MemberAttributes.Private;
409                         mainClass.Members.Add (f);
410                         return fieldName;
411                 }
412
413                 protected void CreatePropertyForObject (Type type, string propName, string fieldName, bool isPublic)
414                 {
415                         CodeFieldReferenceExpression field = new CodeFieldReferenceExpression (thisRef, fieldName);
416                         CodeMemberProperty prop = new CodeMemberProperty ();
417                         prop.Type = new CodeTypeReference (type);
418                         prop.Name = propName;
419                         if (isPublic)
420                                 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
421                         else
422                                 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
423
424                         CodeConditionStatement stmt = new CodeConditionStatement();
425                         stmt.Condition = new CodeBinaryOperatorExpression (field,
426                                                 CodeBinaryOperatorType.IdentityEquality,
427                                                 new CodePrimitiveExpression (null));
428
429                         CodeObjectCreateExpression create = new CodeObjectCreateExpression (prop.Type); 
430                         stmt.TrueStatements.Add (new CodeAssignStatement (field, create));
431                         prop.GetStatements.Add (stmt);
432                         prop.GetStatements.Add (new CodeMethodReturnStatement (field));
433
434                         mainClass.Members.Add (prop);
435                 }
436                 /******/
437
438                 void CheckCompilerErrors (CompilerResults results)
439                 {
440                         if (results.NativeCompilerReturnValue == 0)
441                                 return;
442
443                         string fileText = null;
444                         CompilerErrorCollection errors = results.Errors;
445                         CompilerError ce = (errors != null && errors.Count > 0) ? errors [0] : null;
446                         string inFile = (ce != null) ? ce.FileName : null;
447                         
448                         if (inFile != null && File.Exists (inFile))
449                                 fileText = File.ReadAllText (inFile);
450                         else {
451                                 StringWriter writer = new StringWriter();
452                                 provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
453                                 fileText = writer.ToString ();
454                         }
455                         throw new CompilationException (parser.InputFile, errors, fileText);
456                 }
457
458                 protected string DynamicDir ()
459                 {
460                         return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
461                 }
462
463                 [MonoTODO ("find out how to extract the warningLevel and compilerOptions in the <system.codedom> case")]
464                 public virtual Type GetCompiledType () 
465                 {
466                         Type type = CachingCompiler.GetTypeFromCache (parser.InputFile);
467                         if (type != null)
468                                 return type;
469
470                         Init ();
471                         string lang = parser.Language;
472 #if NET_2_0
473                         CompilationSection config = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
474                         Compiler comp = config.Compilers[lang];
475
476                         string compilerOptions = "";
477                         int warningLevel = 0;
478
479                         if (comp == null) {
480                                 CompilerInfo info = CodeDomProvider.GetCompilerInfo (lang);
481                                 if (info != null && info.IsCodeDomProviderTypeValid)
482                                         provider = info.CreateProvider ();
483
484                                 // XXX there's no way to get
485                                 // warningLevel or compilerOptions out
486                                 // of the provider.. they're in the
487                                 // configuration section, though.
488                         }
489                         else {
490                                 Type t = Type.GetType (comp.Type, true);
491                                 provider = Activator.CreateInstance (t) as CodeDomProvider;
492
493                                 compilerOptions = comp.CompilerOptions;
494                                 warningLevel = comp.WarningLevel;
495                         }
496
497 #else
498                         CompilationConfiguration config;
499
500                         config = CompilationConfiguration.GetInstance (parser.Context);
501                         provider = config.GetProvider (lang);
502
503                         string compilerOptions = config.GetCompilerOptions (lang);
504                         int warningLevel = config.GetWarningLevel (lang);
505 #endif
506                         if (provider == null)
507                                 throw new HttpException ("Configuration error. Language not supported: " +
508                                                           lang, 500);
509
510                         compiler = provider.CreateCompiler ();
511
512                         compilerParameters.IncludeDebugInformation = parser.Debug;
513                         compilerParameters.CompilerOptions = compilerOptions + " " + parser.CompilerOptions;
514
515                         compilerParameters.WarningLevel = warningLevel;
516                         bool keepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
517
518                         string tempdir = config.TempDirectory;
519                         if (tempdir == null || tempdir == "")
520                                 tempdir = DynamicDir ();
521                                 
522                         TempFileCollection tempcoll = new TempFileCollection (tempdir, keepFiles);
523                         compilerParameters.TempFiles = tempcoll;
524                         string dllfilename = Path.GetFileName (tempcoll.AddExtension ("dll", true));
525                         compilerParameters.OutputAssembly = Path.Combine (DynamicDir (), dllfilename);
526
527                         CompilerResults results = CachingCompiler.Compile (this);
528                         CheckCompilerErrors (results);
529                         Assembly assembly = results.CompiledAssembly;
530                         if (assembly == null) {
531                                 if (!File.Exists (compilerParameters.OutputAssembly)) {
532                                         results.TempFiles.Delete ();
533                                         throw new CompilationException (parser.InputFile, results.Errors,
534                                                 "No assembly returned after compilation!?");
535                                 }
536
537                                 assembly = Assembly.LoadFrom (compilerParameters.OutputAssembly);
538                         }
539
540                         results.TempFiles.Delete ();
541                         Type mainClassType = assembly.GetType (mainClassExpr.Type.BaseType, true);
542
543 #if NET_2_0
544                         if (parser.IsPartial) {
545                                 // With the partial classes, we need to make sure we
546                                 // don't have any methods that should have not been
547                                 // created (because they are accessible from the base
548                                 // types). We cannot do this normally because the
549                                 // codebehind file is actually a partial class and we
550                                 // have no way of identifying the partial class' base
551                                 // type until now.
552                                 if (!isRebuilding && CheckPartialBaseType (mainClassType)) {
553                                         isRebuilding = true;
554                                         parser.RootBuilder.ResetState ();
555                                         return GetCompiledType ();
556                                 }
557                         }
558 #endif
559
560                         return mainClassType;
561                 }
562
563 #if NET_2_0
564                 internal bool IsRebuildingPartial
565                 {
566                         get { return isRebuilding; }
567                 }
568
569                 internal bool CheckPartialBaseType (Type type)
570                 {
571                         // Get the base type. If we don't have any (bad thing), we
572                         // don't need to replace ourselves. Also check for the
573                         // core file, since that won't have any either.
574                         Type baseType = type.BaseType;
575                         if (baseType == null || baseType == typeof(System.Web.UI.Page))
576                                 return false;
577
578                         bool rebuild = false;
579
580                         if (CheckPartialBaseFields (type, baseType))
581                                 rebuild = true;
582
583                         if (CheckPartialBaseProperties (type, baseType))
584                                 rebuild = true;
585
586                         return rebuild;
587                 }
588
589                 internal bool CheckPartialBaseFields (Type type, Type baseType)
590                 {
591                         bool rebuild = false;
592
593                         foreach (FieldInfo baseInfo in baseType.GetFields (replaceableFlags)) {
594                                 if (baseInfo.IsPrivate)
595                                         continue;
596
597                                 FieldInfo typeInfo = type.GetField (baseInfo.Name, replaceableFlags);
598
599                                 if (typeInfo != null && typeInfo.DeclaringType == type) {
600                                         partialNameOverride [typeInfo.Name] = true;
601                                         rebuild = true;
602                                 }
603                         }
604
605                         return rebuild;
606                 }
607
608                 internal bool CheckPartialBaseProperties (Type type, Type baseType)
609                 {
610                         bool rebuild = false;
611
612                         foreach (PropertyInfo baseInfo in baseType.GetProperties ()) {
613                                 PropertyInfo typeInfo = type.GetProperty (baseInfo.Name);
614
615                                 if (typeInfo != null && typeInfo.DeclaringType == type) {
616                                         partialNameOverride [typeInfo.Name] = true;
617                                         rebuild = true;
618                                 }
619                         }
620
621                         return rebuild;
622                 }
623 #endif
624
625                 internal CompilerParameters CompilerParameters {
626                         get { return compilerParameters; }
627                 }
628
629                 internal CodeCompileUnit Unit {
630                         get { return unit; }
631                 }
632
633                 internal virtual ICodeCompiler Compiler {
634                         get { return compiler; }
635                 }
636
637                 internal TemplateParser Parser {
638                         get { return parser; }
639                 }
640         }
641 }
642