2007-03-12 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 #endif
61                 protected CodeTypeDeclaration mainClass;
62                 protected CodeTypeReferenceExpression mainClassExpr;
63                 protected static CodeThisReferenceExpression thisRef = new CodeThisReferenceExpression ();
64
65                 protected BaseCompiler (TemplateParser parser)
66                 {
67                         compilerParameters = new CompilerParameters ();
68                         this.parser = parser;
69                 }
70
71                 void Init ()
72                 {
73                         unit = new CodeCompileUnit ();
74 #if NET_2_0
75                         if (parser.IsPartial) {
76                                 string ns = null;
77                                 string classtype = parser.PartialClassName;
78
79                                 if (classtype.Contains (".")) {
80                                         int dot = classtype.LastIndexOf (".");
81                                         ns = classtype.Substring (0, dot);
82                                         classtype = classtype.Substring (dot + 1);
83                                 }
84                                 
85                                 mainNS = new CodeNamespace (ns);
86                                 mainClass = new CodeTypeDeclaration (classtype);
87                                 mainClass.IsPartial = true;     
88                                 mainClassExpr = new CodeTypeReferenceExpression (parser.PartialClassName);
89                         } else {
90 #endif
91                         mainNS = new CodeNamespace ("ASP");
92                         mainClass = new CodeTypeDeclaration (parser.ClassName);
93                         CodeTypeReference baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
94 #if NET_2_0
95                         if (parser.BaseTypeIsGlobal)
96                                 baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
97 #endif
98                         mainClass.BaseTypes.Add (baseTypeRef);
99                         mainClassExpr = new CodeTypeReferenceExpression ("ASP." + parser.ClassName);
100 #if NET_2_0
101                         }
102 #endif
103                         unit.Namespaces.Add (mainNS);
104                         mainClass.TypeAttributes = TypeAttributes.Public;
105                         mainNS.Types.Add (mainClass);
106
107                         foreach (object o in parser.Imports) {
108                                 if (o is string)
109                                         mainNS.Imports.Add (new CodeNamespaceImport ((string) o));
110                         }
111
112                         // StringCollection.Contains has O(n) complexity, but
113                         // considering the number of comparisons we make on
114                         // average and the fact that using an intermediate array
115                         // would be even more costly, this is fine here.
116                         StringCollection refAsm = unit.ReferencedAssemblies;
117                         string asmName;
118                         if (parser.Assemblies != null) {
119                                 foreach (object o in parser.Assemblies) {
120                                         asmName = o as string;
121                                         if (asmName != null && !refAsm.Contains (asmName))
122                                                 refAsm.Add (asmName);
123                                 }
124                         }
125
126 #if NET_2_0
127                         ArrayList al = WebConfigurationManager.ExtraAssemblies;
128                         if (al != null && al.Count > 0) {
129                                 foreach (object o in al) {
130                                         asmName = o as string;
131                                         if (asmName != null && !refAsm.Contains (asmName))
132                                                 refAsm.Add (asmName);
133                                 }
134                         }
135
136                         IList list = BuildManager.CodeAssemblies;
137                         if (list != null && list.Count > 0) {
138                                 Assembly asm;
139                                 foreach (object o in list) {
140                                         asm = o as Assembly;
141                                         if (o == null)
142                                                 continue;
143                                         asmName = asm.Location;
144                                         if (asmName != null && !refAsm.Contains (asmName))
145                                                 refAsm.Add (asmName);
146                                 }
147                         }
148 #endif
149                         // Late-bound generators specifics (as for MonoBASIC/VB.NET)
150                         unit.UserData["RequireVariableDeclaration"] = parser.ExplicitOn;
151                         unit.UserData["AllowLateBound"] = !parser.StrictOn;
152                         
153                         AddInterfaces ();
154                         AddClassAttributes ();
155                         CreateStaticFields ();
156                         AddApplicationAndSessionObjects ();
157                         AddScripts ();
158                         CreateMethods ();
159                         CreateConstructor (null, null);
160                 }
161
162 #if NET_2_0
163                 internal CodeDomProvider Provider {
164                         get { return provider; }
165                 }
166
167                 internal CodeCompileUnit CompileUnit {
168                         get { return unit; }
169                 }
170 #endif
171                 protected virtual void CreateStaticFields ()
172                 {
173                         CodeMemberField fld = new CodeMemberField (typeof (bool), "__initialized");
174                         fld.Attributes = MemberAttributes.Private | MemberAttributes.Static;
175                         fld.InitExpression = new CodePrimitiveExpression (false);
176                         mainClass.Members.Add (fld);
177                 }
178
179                 protected virtual void CreateConstructor (CodeStatementCollection localVars,
180                                                           CodeStatementCollection trueStmt)
181                 {
182                         CodeConstructor ctor = new CodeConstructor ();
183                         ctor.Attributes = MemberAttributes.Public;
184                         mainClass.Members.Add (ctor);
185
186                         if (localVars != null)
187                                 ctor.Statements.AddRange (localVars);
188
189                         CodeTypeReferenceExpression r;
190 #if NET_2_0
191                         if (parser.IsPartial)
192                                 r = new CodeTypeReferenceExpression (mainClass.Name);
193                         else
194 #endif
195                         r = new CodeTypeReferenceExpression (mainNS.Name + "." + mainClass.Name);
196                         CodeFieldReferenceExpression initialized;
197                         initialized = new CodeFieldReferenceExpression (r, "__initialized");
198                         
199                         CodeBinaryOperatorExpression bin;
200                         bin = new CodeBinaryOperatorExpression (initialized,
201                                                                 CodeBinaryOperatorType.ValueEquality,
202                                                                 new CodePrimitiveExpression (false));
203
204                         CodeAssignStatement assign = new CodeAssignStatement (initialized,
205                                                                               new CodePrimitiveExpression (true));
206
207                         CodeConditionStatement cond = new CodeConditionStatement (bin, assign);
208                         if (trueStmt != null)
209                                 cond.TrueStatements.AddRange (trueStmt);
210                         
211                         ctor.Statements.Add (cond);
212                 }
213                 
214                 void AddScripts ()
215                 {
216                         if (parser.Scripts == null || parser.Scripts.Count == 0)
217                                 return;
218
219                         foreach (object o in parser.Scripts) {
220                                 if (o is string)
221                                         mainClass.Members.Add (new CodeSnippetTypeMember ((string) o));
222                         }
223                 }
224                 
225                 protected internal virtual void CreateMethods ()
226                 {
227                 }
228
229 #if NET_2_0
230                 void InternalCreatePageProperty (string retType, string name, string contextProperty)
231                 {
232                         CodeMemberProperty property = new CodeMemberProperty ();
233                         property.Name = name;
234                         property.Type = new CodeTypeReference (retType);
235                         property.Attributes = MemberAttributes.Family | MemberAttributes.Final;
236
237                         CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
238                         CodeCastExpression cast = new CodeCastExpression ();
239                         ret.Expression = cast;
240                         
241                         CodePropertyReferenceExpression refexp = new CodePropertyReferenceExpression ();
242                         refexp.TargetObject = new CodePropertyReferenceExpression (new CodeThisReferenceExpression (), "Context");
243                         refexp.PropertyName = contextProperty;
244                         
245                         cast.TargetType = new CodeTypeReference (retType);
246                         cast.Expression = refexp;
247                         
248                         property.GetStatements.Add (ret);
249                         mainClass.Members.Add (property);
250                 }
251                 
252                 protected void CreateProfileProperty ()
253                 {
254                         string retType;
255                         ProfileSection ps = WebConfigurationManager.GetSection ("system.web/profile") as ProfileSection;
256                         if (ps != null && ps.PropertySettings.Count > 0)
257                                 retType = "ProfileCommon";
258                         else
259                                 retType = "System.Web.Profile.DefaultProfile";
260                         InternalCreatePageProperty (retType, "Profile", "Profile");
261                 }
262 #endif
263                 
264                 protected virtual void AddInterfaces ()
265                 {
266                         if (parser.Interfaces == null)
267                                 return;
268
269                         foreach (object o in parser.Interfaces) {
270                                 if (o is string)
271                                         mainClass.BaseTypes.Add (new CodeTypeReference ((string) o));
272                         }
273                 }
274
275                 protected virtual void AddClassAttributes ()
276                 {
277                 }
278                 
279                 protected virtual void AddApplicationAndSessionObjects ()
280                 {
281                 }
282
283                 /* Utility methods for <object> stuff */
284                 protected void CreateApplicationOrSessionPropertyForObject (Type type,
285                                                                             string propName,
286                                                                             bool isApplication,
287                                                                             bool isPublic)
288                 {
289                         /* if isApplication this generates (the 'cachedapp' field is created earlier):
290                         private MyNS.MyClass app {
291                                 get {
292                                         if ((this.cachedapp == null)) {
293                                                 this.cachedapp = ((MyNS.MyClass)
294                                                         (this.Application.StaticObjects.GetObject("app")));
295                                         }
296                                         return this.cachedapp;
297                                 }
298                         }
299
300                         else, this is for Session:
301                         private MyNS.MyClass ses {
302                                 get {
303                                         return ((MyNS.MyClass) (this.Session.StaticObjects.GetObject("ses")));
304                                 }
305                         }
306
307                         */
308
309                         CodeExpression result = null;
310
311                         CodeMemberProperty prop = new CodeMemberProperty ();
312                         prop.Type = new CodeTypeReference (type);
313                         prop.Name = propName;
314                         if (isPublic)
315                                 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
316                         else
317                                 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
318
319                         CodePropertyReferenceExpression p1;
320                         if (isApplication)
321                                 p1 = new CodePropertyReferenceExpression (thisRef, "Application");
322                         else
323                                 p1 = new CodePropertyReferenceExpression (thisRef, "Session");
324
325                         CodePropertyReferenceExpression p2;
326                         p2 = new CodePropertyReferenceExpression (p1, "StaticObjects");
327
328                         CodeMethodReferenceExpression getobject;
329                         getobject = new CodeMethodReferenceExpression (p2, "GetObject");
330
331                         CodeMethodInvokeExpression invoker;
332                         invoker = new CodeMethodInvokeExpression (getobject,
333                                                 new CodePrimitiveExpression (propName));
334
335                         CodeCastExpression cast = new CodeCastExpression (prop.Type, invoker);
336
337                         if (isApplication) {
338                                 CodeFieldReferenceExpression field;
339                                 field = new CodeFieldReferenceExpression (thisRef, "cached" + propName);
340
341                                 CodeConditionStatement stmt = new CodeConditionStatement();
342                                 stmt.Condition = new CodeBinaryOperatorExpression (field,
343                                                         CodeBinaryOperatorType.IdentityEquality,
344                                                         new CodePrimitiveExpression (null));
345
346                                 CodeAssignStatement assign = new CodeAssignStatement ();
347                                 assign.Left = field;
348                                 assign.Right = cast;
349                                 stmt.TrueStatements.Add (assign);
350                                 prop.GetStatements.Add (stmt);
351                                 result = field;
352                         } else {
353                                 result = cast;
354                         }
355                                                 
356                         prop.GetStatements.Add (new CodeMethodReturnStatement (result));
357                         mainClass.Members.Add (prop);
358                 }
359
360                 protected string CreateFieldForObject (Type type, string name)
361                 {
362                         string fieldName = "cached" + name;
363                         CodeMemberField f = new CodeMemberField (type, fieldName);
364                         f.Attributes = MemberAttributes.Private;
365                         mainClass.Members.Add (f);
366                         return fieldName;
367                 }
368
369                 protected void CreatePropertyForObject (Type type, string propName, string fieldName, bool isPublic)
370                 {
371                         CodeFieldReferenceExpression field = new CodeFieldReferenceExpression (thisRef, fieldName);
372                         CodeMemberProperty prop = new CodeMemberProperty ();
373                         prop.Type = new CodeTypeReference (type);
374                         prop.Name = propName;
375                         if (isPublic)
376                                 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
377                         else
378                                 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
379
380                         CodeConditionStatement stmt = new CodeConditionStatement();
381                         stmt.Condition = new CodeBinaryOperatorExpression (field,
382                                                 CodeBinaryOperatorType.IdentityEquality,
383                                                 new CodePrimitiveExpression (null));
384
385                         CodeObjectCreateExpression create = new CodeObjectCreateExpression (prop.Type); 
386                         stmt.TrueStatements.Add (new CodeAssignStatement (field, create));
387                         prop.GetStatements.Add (stmt);
388                         prop.GetStatements.Add (new CodeMethodReturnStatement (field));
389
390                         mainClass.Members.Add (prop);
391                 }
392                 /******/
393
394                 void CheckCompilerErrors (CompilerResults results)
395                 {
396                         if (results.NativeCompilerReturnValue == 0)
397                                 return;
398
399                         StringWriter writer = new StringWriter();
400                         provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
401                         throw new CompilationException (parser.InputFile, results.Errors, writer.ToString ());
402                 }
403
404                 protected string DynamicDir ()
405                 {
406                         return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
407                 }
408
409                 [MonoTODO ("find out how to extract the warningLevel and compilerOptions in the <system.codedom> case")]
410                 public virtual Type GetCompiledType () 
411                 {
412                         Type type = CachingCompiler.GetTypeFromCache (parser.InputFile);
413                         if (type != null)
414                                 return type;
415
416                         Init ();
417                         string lang = parser.Language;
418 #if NET_2_0
419                         CompilationSection config = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
420                         Compiler comp = config.Compilers[lang];
421
422                         string compilerOptions = "";
423                         int warningLevel = 0;
424
425                         if (comp == null) {
426                                 CompilerInfo info = CodeDomProvider.GetCompilerInfo (lang);
427                                 if (info != null && info.IsCodeDomProviderTypeValid)
428                                         provider = info.CreateProvider ();
429
430                                 // XXX there's no way to get
431                                 // warningLevel or compilerOptions out
432                                 // of the provider.. they're in the
433                                 // configuration section, though.
434                         }
435                         else {
436                                 Type t = Type.GetType (comp.Type, true);
437                                 provider = Activator.CreateInstance (t) as CodeDomProvider;
438
439                                 compilerOptions = comp.CompilerOptions;
440                                 warningLevel = comp.WarningLevel;
441                         }
442
443 #else
444                         CompilationConfiguration config;
445
446                         config = CompilationConfiguration.GetInstance (parser.Context);
447                         provider = config.GetProvider (lang);
448
449                         string compilerOptions = config.GetCompilerOptions (lang);
450                         int warningLevel = config.GetWarningLevel (lang);
451 #endif
452                         if (provider == null)
453                                 throw new HttpException ("Configuration error. Language not supported: " +
454                                                           lang, 500);
455
456                         compiler = provider.CreateCompiler ();
457
458                         compilerParameters.IncludeDebugInformation = parser.Debug;
459                         compilerParameters.CompilerOptions = compilerOptions + " " + parser.CompilerOptions;
460
461                         compilerParameters.WarningLevel = warningLevel;
462                         bool keepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
463
464                         string tempdir = config.TempDirectory;
465                         if (tempdir == null || tempdir == "")
466                                 tempdir = DynamicDir ();
467                                 
468                         TempFileCollection tempcoll = new TempFileCollection (tempdir, keepFiles);
469                         compilerParameters.TempFiles = tempcoll;
470                         string dllfilename = Path.GetFileName (tempcoll.AddExtension ("dll", true));
471                         compilerParameters.OutputAssembly = Path.Combine (DynamicDir (), dllfilename);
472
473                         CompilerResults results = CachingCompiler.Compile (this);
474                         CheckCompilerErrors (results);
475                         Assembly assembly = results.CompiledAssembly;
476                         if (assembly == null) {
477                                 if (!File.Exists (compilerParameters.OutputAssembly)) {
478                                         results.TempFiles.Delete ();
479                                         throw new CompilationException (parser.InputFile, results.Errors,
480                                                 "No assembly returned after compilation!?");
481                                 }
482
483                                 assembly = Assembly.LoadFrom (compilerParameters.OutputAssembly);
484                         }
485
486                         results.TempFiles.Delete ();
487                         Type mainClassType = assembly.GetType (mainClassExpr.Type.BaseType, true);
488
489 #if NET_2_0
490                         if (parser.IsPartial) {
491                                 // With the partial classes, we need to make sure we
492                                 // don't have any methods that should have not been
493                                 // created (because they are accessible from the base
494                                 // types). We cannot do this normally because the
495                                 // codebehind file is actually a partial class and we
496                                 // have no way of identifying the partial class' base
497                                 // type until now.
498                                 if (!isRebuilding && CheckPartialBaseType (mainClassType)) {
499                                         isRebuilding = true;
500                                         parser.RootBuilder.ResetState ();
501                                         return GetCompiledType ();
502                                 }
503                         }
504 #endif
505
506                         return mainClassType;
507                 }
508
509 #if NET_2_0
510                 internal bool IsRebuildingPartial
511                 {
512                         get { return isRebuilding; }
513                 }
514
515                 internal bool CheckPartialBaseType (Type type)
516                 {
517                         // Get the base type. If we don't have any (bad thing), we
518                         // don't need to replace ourselves. Also check for the
519                         // core file, since that won't have any either.
520                         Type baseType = type.BaseType;
521                         if (baseType == null || baseType == typeof(System.Web.UI.Page))
522                                 return false;
523
524                         bool rebuild = false;
525
526                         if (CheckPartialBaseFields (type, baseType))
527                                 rebuild = true;
528
529                         if (CheckPartialBaseProperties (type, baseType))
530                                 rebuild = true;
531
532                         return rebuild;
533                 }
534
535                 internal bool CheckPartialBaseFields (Type type, Type baseType)
536                 {
537                         bool rebuild = false;
538
539                         foreach (FieldInfo baseInfo in baseType.GetFields (replaceableFlags)) {
540                                 if (baseInfo.IsPrivate)
541                                         continue;
542
543                                 FieldInfo typeInfo = type.GetField (baseInfo.Name, replaceableFlags);
544
545                                 if (typeInfo != null && typeInfo.DeclaringType == type) {
546                                         partialNameOverride [typeInfo.Name] = true;
547                                         rebuild = true;
548                                 }
549                         }
550
551                         return rebuild;
552                 }
553
554                 internal bool CheckPartialBaseProperties (Type type, Type baseType)
555                 {
556                         bool rebuild = false;
557
558                         foreach (PropertyInfo baseInfo in baseType.GetProperties ()) {
559                                 PropertyInfo typeInfo = type.GetProperty (baseInfo.Name);
560
561                                 if (typeInfo != null && typeInfo.DeclaringType == type) {
562                                         partialNameOverride [typeInfo.Name] = true;
563                                         rebuild = true;
564                                 }
565                         }
566
567                         return rebuild;
568                 }
569 #endif
570
571                 internal CompilerParameters CompilerParameters {
572                         get { return compilerParameters; }
573                 }
574
575                 internal CodeCompileUnit Unit {
576                         get { return unit; }
577                 }
578
579                 internal virtual ICodeCompiler Compiler {
580                         get { return compiler; }
581                 }
582
583                 internal TemplateParser Parser {
584                         get { return parser; }
585                 }
586         }
587 }
588