2 // System.Web.Compilation.BaseCompiler
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 // (c) Copyright 2002,2003 Ximian, Inc (http://www.ximian.com)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
33 using System.CodeDom.Compiler;
34 using System.Collections;
35 using System.Collections.Specialized;
36 using System.Reflection;
39 using System.Web.Configuration;
42 namespace System.Web.Compilation
44 abstract class BaseCompiler
47 static BindingFlags replaceableFlags = BindingFlags.Public | BindingFlags.NonPublic |
48 BindingFlags.Instance;
51 TemplateParser parser;
52 CodeDomProvider provider;
53 ICodeCompiler compiler;
56 CompilerParameters compilerParameters;
58 bool isRebuilding = false;
59 protected Hashtable partialNameOverride = new Hashtable();
61 protected CodeTypeDeclaration mainClass;
62 protected CodeTypeReferenceExpression mainClassExpr;
63 protected static CodeThisReferenceExpression thisRef = new CodeThisReferenceExpression ();
65 protected BaseCompiler (TemplateParser parser)
67 compilerParameters = new CompilerParameters ();
73 unit = new CodeCompileUnit ();
75 if (parser.IsPartial) {
77 string classtype = parser.PartialClassName;
79 if (classtype.Contains (".")) {
80 int dot = classtype.LastIndexOf (".");
81 ns = classtype.Substring (0, dot);
82 classtype = classtype.Substring (dot + 1);
85 mainNS = new CodeNamespace (ns);
86 mainClass = new CodeTypeDeclaration (classtype);
87 mainClass.IsPartial = true;
88 mainClassExpr = new CodeTypeReferenceExpression (parser.PartialClassName);
91 mainNS = new CodeNamespace ("ASP");
92 mainClass = new CodeTypeDeclaration (parser.ClassName);
93 CodeTypeReference baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
95 if (parser.BaseTypeIsGlobal)
96 baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
98 mainClass.BaseTypes.Add (baseTypeRef);
99 mainClassExpr = new CodeTypeReferenceExpression ("ASP." + parser.ClassName);
103 unit.Namespaces.Add (mainNS);
104 mainClass.TypeAttributes = TypeAttributes.Public;
105 mainNS.Types.Add (mainClass);
107 foreach (object o in parser.Imports) {
109 mainNS.Imports.Add (new CodeNamespaceImport ((string) o));
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;
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);
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);
136 IList list = BuildManager.CodeAssemblies;
137 if (list != null && list.Count > 0) {
139 foreach (object o in list) {
143 asmName = asm.Location;
144 if (asmName != null && !refAsm.Contains (asmName))
145 refAsm.Add (asmName);
149 // Late-bound generators specifics (as for MonoBASIC/VB.NET)
150 unit.UserData["RequireVariableDeclaration"] = parser.ExplicitOn;
151 unit.UserData["AllowLateBound"] = !parser.StrictOn;
154 AddClassAttributes ();
155 CreateStaticFields ();
156 AddApplicationAndSessionObjects ();
159 CreateConstructor (null, null);
163 internal CodeDomProvider Provider {
164 get { return provider; }
167 internal CodeCompileUnit CompileUnit {
171 protected virtual void CreateStaticFields ()
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);
179 protected virtual void CreateConstructor (CodeStatementCollection localVars,
180 CodeStatementCollection trueStmt)
182 CodeConstructor ctor = new CodeConstructor ();
183 ctor.Attributes = MemberAttributes.Public;
184 mainClass.Members.Add (ctor);
186 if (localVars != null)
187 ctor.Statements.AddRange (localVars);
189 CodeTypeReferenceExpression r;
191 if (parser.IsPartial)
192 r = new CodeTypeReferenceExpression (mainClass.Name);
195 r = new CodeTypeReferenceExpression (mainNS.Name + "." + mainClass.Name);
196 CodeFieldReferenceExpression initialized;
197 initialized = new CodeFieldReferenceExpression (r, "__initialized");
199 CodeBinaryOperatorExpression bin;
200 bin = new CodeBinaryOperatorExpression (initialized,
201 CodeBinaryOperatorType.ValueEquality,
202 new CodePrimitiveExpression (false));
204 CodeAssignStatement assign = new CodeAssignStatement (initialized,
205 new CodePrimitiveExpression (true));
207 CodeConditionStatement cond = new CodeConditionStatement (bin, assign);
208 if (trueStmt != null)
209 cond.TrueStatements.AddRange (trueStmt);
211 ctor.Statements.Add (cond);
216 if (parser.Scripts == null || parser.Scripts.Count == 0)
219 foreach (object o in parser.Scripts) {
221 mainClass.Members.Add (new CodeSnippetTypeMember ((string) o));
225 protected internal virtual void CreateMethods ()
230 void InternalCreatePageProperty (string retType, string name, string contextProperty)
232 CodeMemberProperty property = new CodeMemberProperty ();
233 property.Name = name;
234 property.Type = new CodeTypeReference (retType);
235 property.Attributes = MemberAttributes.Family | MemberAttributes.Final;
237 CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
238 CodeCastExpression cast = new CodeCastExpression ();
239 ret.Expression = cast;
241 CodePropertyReferenceExpression refexp = new CodePropertyReferenceExpression ();
242 refexp.TargetObject = new CodePropertyReferenceExpression (new CodeThisReferenceExpression (), "Context");
243 refexp.PropertyName = contextProperty;
245 cast.TargetType = new CodeTypeReference (retType);
246 cast.Expression = refexp;
248 property.GetStatements.Add (ret);
249 mainClass.Members.Add (property);
252 protected void CreateProfileProperty ()
255 ProfileSection ps = WebConfigurationManager.GetSection ("system.web/profile") as ProfileSection;
256 if (ps != null && ps.PropertySettings.Count > 0)
257 retType = "ProfileCommon";
259 retType = "System.Web.Profile.DefaultProfile";
260 InternalCreatePageProperty (retType, "Profile", "Profile");
264 protected virtual void AddInterfaces ()
266 if (parser.Interfaces == null)
269 foreach (object o in parser.Interfaces) {
271 mainClass.BaseTypes.Add (new CodeTypeReference ((string) o));
275 protected virtual void AddClassAttributes ()
279 protected virtual void AddApplicationAndSessionObjects ()
283 /* Utility methods for <object> stuff */
284 protected void CreateApplicationOrSessionPropertyForObject (Type type,
289 /* if isApplication this generates (the 'cachedapp' field is created earlier):
290 private MyNS.MyClass app {
292 if ((this.cachedapp == null)) {
293 this.cachedapp = ((MyNS.MyClass)
294 (this.Application.StaticObjects.GetObject("app")));
296 return this.cachedapp;
300 else, this is for Session:
301 private MyNS.MyClass ses {
303 return ((MyNS.MyClass) (this.Session.StaticObjects.GetObject("ses")));
309 CodeExpression result = null;
311 CodeMemberProperty prop = new CodeMemberProperty ();
312 prop.Type = new CodeTypeReference (type);
313 prop.Name = propName;
315 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
317 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
319 CodePropertyReferenceExpression p1;
321 p1 = new CodePropertyReferenceExpression (thisRef, "Application");
323 p1 = new CodePropertyReferenceExpression (thisRef, "Session");
325 CodePropertyReferenceExpression p2;
326 p2 = new CodePropertyReferenceExpression (p1, "StaticObjects");
328 CodeMethodReferenceExpression getobject;
329 getobject = new CodeMethodReferenceExpression (p2, "GetObject");
331 CodeMethodInvokeExpression invoker;
332 invoker = new CodeMethodInvokeExpression (getobject,
333 new CodePrimitiveExpression (propName));
335 CodeCastExpression cast = new CodeCastExpression (prop.Type, invoker);
338 CodeFieldReferenceExpression field;
339 field = new CodeFieldReferenceExpression (thisRef, "cached" + propName);
341 CodeConditionStatement stmt = new CodeConditionStatement();
342 stmt.Condition = new CodeBinaryOperatorExpression (field,
343 CodeBinaryOperatorType.IdentityEquality,
344 new CodePrimitiveExpression (null));
346 CodeAssignStatement assign = new CodeAssignStatement ();
349 stmt.TrueStatements.Add (assign);
350 prop.GetStatements.Add (stmt);
356 prop.GetStatements.Add (new CodeMethodReturnStatement (result));
357 mainClass.Members.Add (prop);
360 protected string CreateFieldForObject (Type type, string name)
362 string fieldName = "cached" + name;
363 CodeMemberField f = new CodeMemberField (type, fieldName);
364 f.Attributes = MemberAttributes.Private;
365 mainClass.Members.Add (f);
369 protected void CreatePropertyForObject (Type type, string propName, string fieldName, bool isPublic)
371 CodeFieldReferenceExpression field = new CodeFieldReferenceExpression (thisRef, fieldName);
372 CodeMemberProperty prop = new CodeMemberProperty ();
373 prop.Type = new CodeTypeReference (type);
374 prop.Name = propName;
376 prop.Attributes = MemberAttributes.Public | MemberAttributes.Final;
378 prop.Attributes = MemberAttributes.Private | MemberAttributes.Final;
380 CodeConditionStatement stmt = new CodeConditionStatement();
381 stmt.Condition = new CodeBinaryOperatorExpression (field,
382 CodeBinaryOperatorType.IdentityEquality,
383 new CodePrimitiveExpression (null));
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));
390 mainClass.Members.Add (prop);
394 void CheckCompilerErrors (CompilerResults results)
396 if (results.NativeCompilerReturnValue == 0)
399 StringWriter writer = new StringWriter();
400 provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
401 throw new CompilationException (parser.InputFile, results.Errors, writer.ToString ());
404 protected string DynamicDir ()
406 return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
409 [MonoTODO ("find out how to extract the warningLevel and compilerOptions in the <system.codedom> case")]
410 public virtual Type GetCompiledType ()
412 Type type = CachingCompiler.GetTypeFromCache (parser.InputFile);
417 string lang = parser.Language;
419 CompilationSection config = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
420 Compiler comp = config.Compilers[lang];
422 string compilerOptions = "";
423 int warningLevel = 0;
426 CompilerInfo info = CodeDomProvider.GetCompilerInfo (lang);
427 if (info != null && info.IsCodeDomProviderTypeValid)
428 provider = info.CreateProvider ();
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.
436 Type t = Type.GetType (comp.Type, true);
437 provider = Activator.CreateInstance (t) as CodeDomProvider;
439 compilerOptions = comp.CompilerOptions;
440 warningLevel = comp.WarningLevel;
444 CompilationConfiguration config;
446 config = CompilationConfiguration.GetInstance (parser.Context);
447 provider = config.GetProvider (lang);
449 string compilerOptions = config.GetCompilerOptions (lang);
450 int warningLevel = config.GetWarningLevel (lang);
452 if (provider == null)
453 throw new HttpException ("Configuration error. Language not supported: " +
456 compiler = provider.CreateCompiler ();
458 compilerParameters.IncludeDebugInformation = parser.Debug;
459 compilerParameters.CompilerOptions = compilerOptions + " " + parser.CompilerOptions;
461 compilerParameters.WarningLevel = warningLevel;
462 bool keepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
464 string tempdir = config.TempDirectory;
465 if (tempdir == null || tempdir == "")
466 tempdir = DynamicDir ();
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);
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!?");
483 assembly = Assembly.LoadFrom (compilerParameters.OutputAssembly);
486 results.TempFiles.Delete ();
487 Type mainClassType = assembly.GetType (mainClassExpr.Type.BaseType, true);
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
498 if (!isRebuilding && CheckPartialBaseType (mainClassType)) {
500 parser.RootBuilder.ResetState ();
501 return GetCompiledType ();
506 return mainClassType;
510 internal bool IsRebuildingPartial
512 get { return isRebuilding; }
515 internal bool CheckPartialBaseType (Type type)
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))
524 bool rebuild = false;
526 if (CheckPartialBaseFields (type, baseType))
529 if (CheckPartialBaseProperties (type, baseType))
535 internal bool CheckPartialBaseFields (Type type, Type baseType)
537 bool rebuild = false;
539 foreach (FieldInfo baseInfo in baseType.GetFields (replaceableFlags)) {
540 if (baseInfo.IsPrivate)
543 FieldInfo typeInfo = type.GetField (baseInfo.Name, replaceableFlags);
545 if (typeInfo != null && typeInfo.DeclaringType == type) {
546 partialNameOverride [typeInfo.Name] = true;
554 internal bool CheckPartialBaseProperties (Type type, Type baseType)
556 bool rebuild = false;
558 foreach (PropertyInfo baseInfo in baseType.GetProperties ()) {
559 PropertyInfo typeInfo = type.GetProperty (baseInfo.Name);
561 if (typeInfo != null && typeInfo.DeclaringType == type) {
562 partialNameOverride [typeInfo.Name] = true;
571 internal CompilerParameters CompilerParameters {
572 get { return compilerParameters; }
575 internal CodeCompileUnit Unit {
579 internal virtual ICodeCompiler Compiler {
580 get { return compiler; }
583 internal TemplateParser Parser {
584 get { return parser; }