2006-03-24 Chris Toshok <toshok@ximian.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.Reflection;
36 using System.Text;
37 using System.Web.UI;
38 using System.Web.Configuration;
39 using System.IO;
40
41 namespace System.Web.Compilation
42 {
43         abstract class BaseCompiler
44         {
45 #if NET_2_0
46                 static BindingFlags replaceableFlags = BindingFlags.Public | BindingFlags.NonPublic |
47                                                   BindingFlags.Instance;
48 #endif
49
50                 TemplateParser parser;
51                 CodeDomProvider provider;
52                 ICodeCompiler compiler;
53                 CodeCompileUnit unit;
54                 CodeNamespace mainNS;
55                 CompilerParameters compilerParameters;
56 #if NET_2_0
57                 bool isRebuilding = false;
58                 protected Hashtable partialNameOverride = new Hashtable();
59 #endif
60                 protected CodeTypeDeclaration mainClass;
61                 protected CodeTypeReferenceExpression mainClassExpr;
62                 protected static CodeThisReferenceExpression thisRef = new CodeThisReferenceExpression ();
63
64                 protected BaseCompiler (TemplateParser parser)
65                 {
66                         compilerParameters = new CompilerParameters ();
67                         this.parser = parser;
68                 }
69
70                 void Init ()
71                 {
72                         unit = new CodeCompileUnit ();
73 #if NET_2_0
74                         if (parser.IsPartial) {
75                                 mainNS = new CodeNamespace ();
76                                 mainClass = new CodeTypeDeclaration (parser.PartialClassName);
77                                 mainClass.IsPartial = true;     
78                                 mainClassExpr = new CodeTypeReferenceExpression (parser.PartialClassName);
79                         } else {
80 #endif
81                         mainNS = new CodeNamespace ("ASP");
82                         mainClass = new CodeTypeDeclaration (parser.ClassName);
83                         mainClass.BaseTypes.Add (new CodeTypeReference (parser.BaseType.FullName));
84                         mainClassExpr = new CodeTypeReferenceExpression ("ASP." + parser.ClassName);
85 #if NET_2_0
86                         }
87 #endif
88                         unit.Namespaces.Add (mainNS);
89                         mainClass.TypeAttributes = TypeAttributes.Public;
90                         mainNS.Types.Add (mainClass);
91
92                         foreach (object o in parser.Imports) {
93                                 if (o is string)
94                                         mainNS.Imports.Add (new CodeNamespaceImport ((string) o));
95                         }
96
97                         if (parser.Assemblies != null) {
98                                 foreach (object o in parser.Assemblies) {
99                                         if (o is string)
100                                                 unit.ReferencedAssemblies.Add ((string) o);
101                                 }
102                         }
103
104                         // Late-bound generators specifics (as for MonoBASIC/VB.NET)
105                         unit.UserData["RequireVariableDeclaration"] = parser.ExplicitOn;
106                         unit.UserData["AllowLateBound"] = !parser.StrictOn;
107                         
108                         AddInterfaces ();
109                         AddClassAttributes ();
110                         CreateStaticFields ();
111                         AddApplicationAndSessionObjects ();
112                         AddScripts ();
113                         CreateMethods ();
114                         CreateConstructor (null, null);
115                 }
116
117 #if NET_2_0
118                 internal CodeDomProvider Provider {
119                         get { return provider; }
120                 }
121
122                 internal CodeCompileUnit CompileUnit {
123                         get { return unit; }
124                 }
125 #endif
126                 protected virtual void CreateStaticFields ()
127                 {
128                         CodeMemberField fld = new CodeMemberField (typeof (bool), "__initialized");
129                         fld.Attributes = MemberAttributes.Private | MemberAttributes.Static;
130                         fld.InitExpression = new CodePrimitiveExpression (false);
131                         mainClass.Members.Add (fld);
132                 }
133
134                 protected virtual void CreateConstructor (CodeStatementCollection localVars,
135                                                           CodeStatementCollection trueStmt)
136                 {
137                         CodeConstructor ctor = new CodeConstructor ();
138                         ctor.Attributes = MemberAttributes.Public;
139                         mainClass.Members.Add (ctor);
140
141                         if (localVars != null)
142                                 ctor.Statements.AddRange (localVars);
143
144                         CodeTypeReferenceExpression r;
145 #if NET_2_0
146                         if (parser.IsPartial)
147                                 r = new CodeTypeReferenceExpression (mainClass.Name);
148                         else
149 #endif
150                         r = new CodeTypeReferenceExpression (mainNS.Name + "." + mainClass.Name);
151                         CodeFieldReferenceExpression initialized;
152                         initialized = new CodeFieldReferenceExpression (r, "__initialized");
153                         
154                         CodeBinaryOperatorExpression bin;
155                         bin = new CodeBinaryOperatorExpression (initialized,
156                                                                 CodeBinaryOperatorType.ValueEquality,
157                                                                 new CodePrimitiveExpression (false));
158
159                         CodeAssignStatement assign = new CodeAssignStatement (initialized,
160                                                                               new CodePrimitiveExpression (true));
161
162                         CodeConditionStatement cond = new CodeConditionStatement (bin, assign);
163                         if (trueStmt != null)
164                                 cond.TrueStatements.AddRange (trueStmt);
165                         
166                         ctor.Statements.Add (cond);
167                 }
168                 
169                 void AddScripts ()
170                 {
171                         if (parser.Scripts == null || parser.Scripts.Count == 0)
172                                 return;
173
174                         foreach (object o in parser.Scripts) {
175                                 if (o is string)
176                                         mainClass.Members.Add (new CodeSnippetTypeMember ((string) o));
177                         }
178                 }
179                 
180                 protected internal virtual void CreateMethods ()
181                 {
182                 }
183
184                 protected virtual void AddInterfaces ()
185                 {
186                         if (parser.Interfaces == null)
187                                 return;
188
189                         foreach (object o in parser.Interfaces) {
190                                 if (o is string)
191                                         mainClass.BaseTypes.Add (new CodeTypeReference ((string) o));
192                         }
193                 }
194
195                 protected virtual void AddClassAttributes ()
196                 {
197                 }
198                 
199                 protected virtual void AddApplicationAndSessionObjects ()
200                 {
201                 }
202
203                 /* Utility methods for <object> stuff */
204                 protected void CreateApplicationOrSessionPropertyForObject (Type type,
205                                                                             string propName,
206                                                                             bool isApplication,
207                                                                             bool isPublic)
208                 {
209                         /* if isApplication this generates (the 'cachedapp' field is created earlier):
210                         private MyNS.MyClass app {
211                                 get {
212                                         if ((this.cachedapp == null)) {
213                                                 this.cachedapp = ((MyNS.MyClass)
214                                                         (this.Application.StaticObjects.GetObject("app")));
215                                         }
216                                         return this.cachedapp;
217                                 }
218                         }
219
220                         else, this is for Session:
221                         private MyNS.MyClass ses {
222                                 get {
223                                         return ((MyNS.MyClass) (this.Session.StaticObjects.GetObject("ses")));
224                                 }
225                         }
226
227                         */
228
229                         CodeExpression result = null;
230
231                         CodeMemberProperty prop = new CodeMemberProperty ();
232                         prop.Type = new CodeTypeReference (type);
233                         prop.Name = propName;
234                         if (isPublic)
235                                 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
236                         else
237                                 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
238
239                         CodePropertyReferenceExpression p1;
240                         if (isApplication)
241                                 p1 = new CodePropertyReferenceExpression (thisRef, "Application");
242                         else
243                                 p1 = new CodePropertyReferenceExpression (thisRef, "Session");
244
245                         CodePropertyReferenceExpression p2;
246                         p2 = new CodePropertyReferenceExpression (p1, "StaticObjects");
247
248                         CodeMethodReferenceExpression getobject;
249                         getobject = new CodeMethodReferenceExpression (p2, "GetObject");
250
251                         CodeMethodInvokeExpression invoker;
252                         invoker = new CodeMethodInvokeExpression (getobject,
253                                                 new CodePrimitiveExpression (propName));
254
255                         CodeCastExpression cast = new CodeCastExpression (prop.Type, invoker);
256
257                         if (isApplication) {
258                                 CodeFieldReferenceExpression field;
259                                 field = new CodeFieldReferenceExpression (thisRef, "cached" + propName);
260
261                                 CodeConditionStatement stmt = new CodeConditionStatement();
262                                 stmt.Condition = new CodeBinaryOperatorExpression (field,
263                                                         CodeBinaryOperatorType.IdentityEquality,
264                                                         new CodePrimitiveExpression (null));
265
266                                 CodeAssignStatement assign = new CodeAssignStatement ();
267                                 assign.Left = field;
268                                 assign.Right = cast;
269                                 stmt.TrueStatements.Add (assign);
270                                 prop.GetStatements.Add (stmt);
271                                 result = field;
272                         } else {
273                                 result = cast;
274                         }
275                                                 
276                         prop.GetStatements.Add (new CodeMethodReturnStatement (result));
277                         mainClass.Members.Add (prop);
278                 }
279
280                 protected string CreateFieldForObject (Type type, string name)
281                 {
282                         string fieldName = "cached" + name;
283                         CodeMemberField f = new CodeMemberField (type, fieldName);
284                         f.Attributes = MemberAttributes.Private;
285                         mainClass.Members.Add (f);
286                         return fieldName;
287                 }
288
289                 protected void CreatePropertyForObject (Type type, string propName, string fieldName, bool isPublic)
290                 {
291                         CodeFieldReferenceExpression field = new CodeFieldReferenceExpression (thisRef, fieldName);
292                         CodeMemberProperty prop = new CodeMemberProperty ();
293                         prop.Type = new CodeTypeReference (type);
294                         prop.Name = propName;
295                         if (isPublic)
296                                 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
297                         else
298                                 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
299
300                         CodeConditionStatement stmt = new CodeConditionStatement();
301                         stmt.Condition = new CodeBinaryOperatorExpression (field,
302                                                 CodeBinaryOperatorType.IdentityEquality,
303                                                 new CodePrimitiveExpression (null));
304
305                         CodeObjectCreateExpression create = new CodeObjectCreateExpression (prop.Type); 
306                         stmt.TrueStatements.Add (new CodeAssignStatement (field, create));
307                         prop.GetStatements.Add (stmt);
308                         prop.GetStatements.Add (new CodeMethodReturnStatement (field));
309
310                         mainClass.Members.Add (prop);
311                 }
312                 /******/
313
314                 void CheckCompilerErrors (CompilerResults results)
315                 {
316                         if (results.NativeCompilerReturnValue == 0)
317                                 return;
318
319                         StringWriter writer = new StringWriter();
320                         provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
321                         throw new CompilationException (parser.InputFile, results.Errors, writer.ToString ());
322                 }
323
324                 protected string DynamicDir ()
325                 {
326                         return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
327                 }
328
329                 public virtual Type GetCompiledType () 
330                 {
331                         Type type = CachingCompiler.GetTypeFromCache (parser.InputFile);
332                         if (type != null)
333                                 return type;
334
335                         Init ();
336                         string lang = parser.Language;
337 #if NET_2_0
338                         CompilationSection config = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
339                         Compiler comp = config.Compilers[lang];
340                         if (comp == null) {
341                                 CompilerInfo info = CodeDomProvider.GetCompilerInfo (lang);
342                                 if (info != null)
343                                         provider = info.CreateProvider ();
344                         }
345                         else {
346                                 Type t = Type.GetType (comp.Type, true);
347                                 provider = Activator.CreateInstance (t) as CodeDomProvider;
348                         }
349
350                         string compilerOptions = comp.CompilerOptions;
351                         int warningLevel = comp.WarningLevel;
352 #else
353                         CompilationConfiguration config;
354
355                         config = CompilationConfiguration.GetInstance (parser.Context);
356                         provider = config.GetProvider (lang);
357
358                         string compilerOptions = config.GetCompilerOptions (lang);
359                         int warningLevel = config.GetWarningLevel (lang);
360 #endif
361                         if (provider == null)
362                                 throw new HttpException ("Configuration error. Language not supported: " +
363                                                           lang, 500);
364
365                         compiler = provider.CreateCompiler ();
366
367                         compilerParameters.IncludeDebugInformation = parser.Debug;
368                         compilerParameters.CompilerOptions = compilerOptions + " " + parser.CompilerOptions;
369
370                         compilerParameters.WarningLevel = warningLevel;
371                         bool keepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
372
373                         string tempdir = config.TempDirectory;
374                         if (tempdir == null || tempdir == "")
375                                 tempdir = DynamicDir ();
376                                 
377                         TempFileCollection tempcoll = new TempFileCollection (tempdir, keepFiles);
378                         compilerParameters.TempFiles = tempcoll;
379                         string dllfilename = Path.GetFileName (tempcoll.AddExtension ("dll", true));
380                         compilerParameters.OutputAssembly = Path.Combine (DynamicDir (), dllfilename);
381
382                         CompilerResults results = CachingCompiler.Compile (this);
383                         CheckCompilerErrors (results);
384                         Assembly assembly = results.CompiledAssembly;
385                         if (assembly == null) {
386                                 if (!File.Exists (compilerParameters.OutputAssembly)) {
387                                         results.TempFiles.Delete ();
388                                         throw new CompilationException (parser.InputFile, results.Errors,
389                                                 "No assembly returned after compilation!?");
390                                 }
391
392                                 assembly = Assembly.LoadFrom (compilerParameters.OutputAssembly);
393                         }
394
395                         results.TempFiles.Delete ();
396                         Type mainClassType = assembly.GetType (mainClassExpr.Type.BaseType, true);
397
398 #if NET_2_0
399                         if (parser.IsPartial) {
400                                 // With the partial classes, we need to make sure we
401                                 // don't have any methods that should have not been
402                                 // created (because they are accessible from the base
403                                 // types). We cannot do this normally because the
404                                 // codebehind file is actually a partial class and we
405                                 // have no way of identifying the partial class' base
406                                 // type until now.
407                                 if (!isRebuilding && CheckPartialBaseType (mainClassType)) {
408                                         isRebuilding = true;
409                                         parser.RootBuilder.ResetState ();
410                                         return GetCompiledType ();
411                                 }
412                         }
413 #endif
414
415                         return mainClassType;
416                 }
417
418 #if NET_2_0
419                 internal bool IsRebuildingPartial
420                 {
421                         get { return isRebuilding; }
422                 }
423
424                 internal bool CheckPartialBaseType (Type type)
425                 {
426                         // Get the base type. If we don't have any (bad thing), we
427                         // don't need to replace ourselves. Also check for the
428                         // core file, since that won't have any either.
429                         Type baseType = type.BaseType;
430                         if (baseType == null || baseType == typeof(System.Web.UI.Page))
431                                 return false;
432
433                         bool rebuild = false;
434
435                         if (CheckPartialBaseFields (type, baseType))
436                                 rebuild = true;
437
438                         if (CheckPartialBaseProperties (type, baseType))
439                                 rebuild = true;
440
441                         return rebuild;
442                 }
443
444                 internal bool CheckPartialBaseFields (Type type, Type baseType)
445                 {
446                         bool rebuild = false;
447
448                         foreach (FieldInfo baseInfo in baseType.GetFields (replaceableFlags)) {
449                                 if (baseInfo.IsPrivate)
450                                         continue;
451
452                                 FieldInfo typeInfo = type.GetField (baseInfo.Name, replaceableFlags);
453
454                                 if (typeInfo != null && typeInfo.DeclaringType == type) {
455                                         partialNameOverride [typeInfo.Name] = true;
456                                         rebuild = true;
457                                 }
458                         }
459
460                         return rebuild;
461                 }
462
463                 internal bool CheckPartialBaseProperties (Type type, Type baseType)
464                 {
465                         bool rebuild = false;
466
467                         foreach (PropertyInfo baseInfo in baseType.GetProperties ()) {
468                                 PropertyInfo typeInfo = type.GetProperty (baseInfo.Name);
469
470                                 if (typeInfo != null && typeInfo.DeclaringType == type) {
471                                         partialNameOverride [typeInfo.Name] = true;
472                                         rebuild = true;
473                                 }
474                         }
475
476                         return rebuild;
477                 }
478 #endif
479
480                 internal CompilerParameters CompilerParameters {
481                         get { return compilerParameters; }
482                 }
483
484                 internal CodeCompileUnit Unit {
485                         get { return unit; }
486                 }
487
488                 internal virtual ICodeCompiler Compiler {
489                         get { return compiler; }
490                 }
491
492                 internal TemplateParser Parser {
493                         get { return parser; }
494                 }
495         }
496 }
497