2008-01-02 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Compilation / BaseCompiler.cs
index 18ef9895b9d4f2a1b86e6996ffc51d11209708aa..802bd37b7597f43d84152196d53a1e1ec9c88e5d 100644 (file)
@@ -32,6 +32,7 @@ using System;
 using System.CodeDom;
 using System.CodeDom.Compiler;
 using System.Collections;
+using System.Collections.Specialized;
 using System.Reflection;
 using System.Text;
 using System.Web.UI;
@@ -42,45 +43,201 @@ namespace System.Web.Compilation
 {
        abstract class BaseCompiler
        {
-               protected static string dynamicBase = AppDomain.CurrentDomain.SetupInformation.DynamicBase;
+#if NET_2_0
+               static BindingFlags replaceableFlags = BindingFlags.Public | BindingFlags.NonPublic |
+                                                 BindingFlags.Instance;
+#endif
+
                TemplateParser parser;
                CodeDomProvider provider;
                ICodeCompiler compiler;
                CodeCompileUnit unit;
                CodeNamespace mainNS;
                CompilerParameters compilerParameters;
+#if NET_2_0
+               bool isRebuilding = false;
+               protected Hashtable partialNameOverride = new Hashtable();
+               protected CodeTypeDeclaration partialClass;
+               protected CodeTypeReferenceExpression partialClassExpr;
+#endif
                protected CodeTypeDeclaration mainClass;
                protected CodeTypeReferenceExpression mainClassExpr;
                protected static CodeThisReferenceExpression thisRef = new CodeThisReferenceExpression ();
 
                protected BaseCompiler (TemplateParser parser)
                {
-                       compilerParameters = new CompilerParameters ();
                        this.parser = parser;
                }
 
+               internal CodeStatement AddLinePragma (CodeExpression expression, ControlBuilder builder)
+               {
+                       return AddLinePragma (new CodeExpressionStatement (expression), builder);
+               }
+               
+               internal CodeStatement AddLinePragma (CodeStatement statement, ControlBuilder builder)
+               {
+                       if (builder == null || statement == null)
+                               return statement;
+
+                       ILocation location = null;
+
+                       if (!(builder is CodeRenderBuilder))
+                               location = builder.location;
+                       
+                       if (location != null)
+                               return AddLinePragma (statement, location);
+                       else
+                               return AddLinePragma (statement, builder.line, builder.fileName);
+               }
+
+               internal CodeStatement AddLinePragma (CodeStatement statement, ILocation location)
+               {
+                       if (location == null || statement == null)
+                               return statement;
+                       
+                       return AddLinePragma (statement, location.BeginLine, location.Filename);
+               }
+
+               internal CodeStatement AddLinePragma (CodeStatement statement, int line, string fileName)
+               {
+                       if (statement == null)
+                               return null;
+                       
+                       statement.LinePragma = new CodeLinePragma (fileName, line);
+                       return statement;                       
+               }
+
+               internal CodeTypeMember AddLinePragma (CodeTypeMember member, ControlBuilder builder)
+               {
+                       if (builder == null || member == null)
+                               return member;
+
+                       ILocation location = builder.location;
+                       
+                       if (location != null)
+                               return AddLinePragma (member, location);
+                       else
+                               return AddLinePragma (member, builder.line, builder.fileName);
+               }
+               
+               internal CodeTypeMember AddLinePragma (CodeTypeMember member, ILocation location)
+               {
+                       if (location == null || member == null)
+                               return member;
+
+                       return AddLinePragma (member, location.BeginLine, location.Filename);
+               }
+               
+               internal CodeTypeMember AddLinePragma (CodeTypeMember member, int line, string fileName)
+               {
+                       if (member == null)
+                               return null;
+                       
+                       member.LinePragma = new CodeLinePragma (fileName, line);
+                       return member;
+               }
+               
                void Init ()
                {
                        unit = new CodeCompileUnit ();
-                       mainNS = new CodeNamespace ("ASP");
+#if NET_2_0
+                       if (parser.IsPartial) {
+                               string partialns = null;
+                               string partialclasstype = parser.PartialClassName;
+
+                               int partialdot = partialclasstype.LastIndexOf ('.');
+                               if (partialdot != -1) {
+                                       partialns = partialclasstype.Substring (0, partialdot);
+                                       partialclasstype = partialclasstype.Substring (partialdot + 1);
+                               }
+                               
+                               CodeNamespace partialNS = new CodeNamespace (partialns);
+                               partialClass = new CodeTypeDeclaration (partialclasstype);
+                               partialClass.IsPartial = true;
+                               partialClassExpr = new CodeTypeReferenceExpression (parser.PartialClassName);
+                               
+                               unit.Namespaces.Add (partialNS);
+                               partialClass.TypeAttributes = TypeAttributes.Public;
+                               partialNS.Types.Add (partialClass);
+                       }
+#endif
+
+                       string mainclasstype = parser.ClassName;
+                       string mainns = "ASP";
+
+#if NET_2_0
+                       int maindot = mainclasstype.LastIndexOf ('.');
+                       if (maindot != -1) {
+                               mainns = mainclasstype.Substring (0, maindot);
+                               mainclasstype = mainclasstype.Substring (maindot + 1);
+                       }
+#endif
+
+                       mainNS = new CodeNamespace (mainns);
+                       mainClass = new CodeTypeDeclaration (mainclasstype);
+                       CodeTypeReference baseTypeRef;
+#if NET_2_0
+                       if (partialClass != null) {
+                               baseTypeRef = new CodeTypeReference (parser.PartialClassName);
+                               baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
+                       } else {
+                               baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
+                               if (parser.BaseTypeIsGlobal)
+                                       baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
+                       }
+#else
+                       baseTypeRef = new CodeTypeReference (parser.BaseType.FullName);
+#endif
+                       mainClass.BaseTypes.Add (baseTypeRef);
+
+                       mainClassExpr = new CodeTypeReferenceExpression (mainns + "." + mainclasstype);
+
                        unit.Namespaces.Add (mainNS);
-                       mainClass = new CodeTypeDeclaration (parser.ClassName);
                        mainClass.TypeAttributes = TypeAttributes.Public;
                        mainNS.Types.Add (mainClass);
-                       mainClass.BaseTypes.Add (new CodeTypeReference (parser.BaseType.FullName));
-                       mainClassExpr = new CodeTypeReferenceExpression ("ASP." + parser.ClassName);
+
                        foreach (object o in parser.Imports) {
                                if (o is string)
                                        mainNS.Imports.Add (new CodeNamespaceImport ((string) o));
                        }
 
+                       // StringCollection.Contains has O(n) complexity, but
+                       // considering the number of comparisons we make on
+                       // average and the fact that using an intermediate array
+                       // would be even more costly, this is fine here.
+                       StringCollection refAsm = unit.ReferencedAssemblies;
+                       string asmName;
                        if (parser.Assemblies != null) {
                                foreach (object o in parser.Assemblies) {
-                                       if (o is string)
-                                               unit.ReferencedAssemblies.Add ((string) o);
+                                       asmName = o as string;
+                                       if (asmName != null && !refAsm.Contains (asmName))
+                                               refAsm.Add (asmName);
+                               }
+                       }
+
+#if NET_2_0
+                       ArrayList al = WebConfigurationManager.ExtraAssemblies;
+                       if (al != null && al.Count > 0) {
+                               foreach (object o in al) {
+                                       asmName = o as string;
+                                       if (asmName != null && !refAsm.Contains (asmName))
+                                               refAsm.Add (asmName);
                                }
                        }
 
+                       IList list = BuildManager.CodeAssemblies;
+                       if (list != null && list.Count > 0) {
+                               Assembly asm;
+                               foreach (object o in list) {
+                                       asm = o as Assembly;
+                                       if (o == null)
+                                               continue;
+                                       asmName = asm.Location;
+                                       if (asmName != null && !refAsm.Contains (asmName))
+                                               refAsm.Add (asmName);
+                               }
+                       }
+#endif
                        // Late-bound generators specifics (as for MonoBASIC/VB.NET)
                        unit.UserData["RequireVariableDeclaration"] = parser.ExplicitOn;
                        unit.UserData["AllowLateBound"] = !parser.StrictOn;
@@ -90,17 +247,59 @@ namespace System.Web.Compilation
                        CreateStaticFields ();
                        AddApplicationAndSessionObjects ();
                        AddScripts ();
+                       CreateMethods ();
                        CreateConstructor (null, null);
                }
 
+               internal CodeFieldReferenceExpression GetMainClassFieldReferenceExpression (string fieldName)
+               {
+                       CodeTypeReference mainClassTypeRef;
+                       mainClassTypeRef = new CodeTypeReference (mainNS.Name + "." + mainClass.Name);
+
+#if NET_2_0
+                       mainClassTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
+#endif
+                       return new CodeFieldReferenceExpression (
+                               new CodeTypeReferenceExpression (mainClassTypeRef), fieldName);
+               }
+               
                protected virtual void CreateStaticFields ()
                {
-                       CodeMemberField fld = new CodeMemberField (typeof (bool), "__intialized");
+                       CodeMemberField fld = new CodeMemberField (typeof (bool), "__initialized");
                        fld.Attributes = MemberAttributes.Private | MemberAttributes.Static;
                        fld.InitExpression = new CodePrimitiveExpression (false);
                        mainClass.Members.Add (fld);
                }
 
+#if NET_2_0
+               void AssignAppRelativeVirtualPath (CodeConstructor ctor)
+               {
+                       Type baseType = parser.CodeFileBaseClassType;
+
+                       if (baseType == null)
+                               baseType = parser.BaseType;
+                       if (baseType == null)
+                               return;
+                       if (!baseType.IsSubclassOf (typeof (System.Web.UI.TemplateControl)))
+                               return;
+
+                       string arvp = Path.Combine (parser.BaseVirtualDir, Path.GetFileName (parser.InputFile));
+                       if (VirtualPathUtility.IsAbsolute (arvp))
+                               arvp = "~" + arvp;
+
+                       CodeTypeReference baseTypeRef = new CodeTypeReference (baseType.FullName);
+                       if (parser.BaseTypeIsGlobal)
+                               baseTypeRef.Options |= CodeTypeReferenceOptions.GlobalReference;
+                       
+                       CodeExpression cast = new CodeCastExpression (baseTypeRef, new CodeThisReferenceExpression ());
+                       CodePropertyReferenceExpression arvpProp = new CodePropertyReferenceExpression (cast, "AppRelativeVirtualPath");
+                       CodeAssignStatement arvpAssign = new CodeAssignStatement ();
+                       arvpAssign.Left = arvpProp;
+                       arvpAssign.Right = new CodePrimitiveExpression (VirtualPathUtility.RemoveTrailingSlash (arvp));
+                       ctor.Statements.Add (arvpAssign);
+               }
+#endif
+               
                protected virtual void CreateConstructor (CodeStatementCollection localVars,
                                                          CodeStatementCollection trueStmt)
                {
@@ -111,22 +310,26 @@ namespace System.Web.Compilation
                        if (localVars != null)
                                ctor.Statements.AddRange (localVars);
 
-                       CodeTypeReferenceExpression r;
-                       r = new CodeTypeReferenceExpression (mainNS.Name + "." + mainClass.Name);
-                       CodeFieldReferenceExpression intialized;
-                       intialized = new CodeFieldReferenceExpression (r, "__intialized");
+#if NET_2_0
+                       AssignAppRelativeVirtualPath (ctor);
+#endif
+
+                       CodeFieldReferenceExpression initialized = GetMainClassFieldReferenceExpression ("__initialized");
                        
                        CodeBinaryOperatorExpression bin;
-                       bin = new CodeBinaryOperatorExpression (intialized,
+                       bin = new CodeBinaryOperatorExpression (initialized,
                                                                CodeBinaryOperatorType.ValueEquality,
                                                                new CodePrimitiveExpression (false));
 
-                       CodeAssignStatement assign = new CodeAssignStatement (intialized,
+                       CodeAssignStatement assign = new CodeAssignStatement (initialized,
                                                                              new CodePrimitiveExpression (true));
 
-                       CodeConditionStatement cond = new CodeConditionStatement (bin, assign);
+                       CodeConditionStatement cond = new CodeConditionStatement ();
+                       cond.Condition = bin;
+                       
                        if (trueStmt != null)
                                cond.TrueStatements.AddRange (trueStmt);
+                       cond.TrueStatements.Add (assign);
                        
                        ctor.Statements.Add (cond);
                }
@@ -136,16 +339,59 @@ namespace System.Web.Compilation
                        if (parser.Scripts == null || parser.Scripts.Count == 0)
                                return;
 
+                       ServerSideScript sss;
+                       
                        foreach (object o in parser.Scripts) {
-                               if (o is string)
-                                       mainClass.Members.Add (new CodeSnippetTypeMember ((string) o));
+                               sss = o as ServerSideScript;
+
+                               if (sss == null)
+                                       continue;
+                               
+                               mainClass.Members.Add (AddLinePragma (new CodeSnippetTypeMember (sss.Script), sss.Location));
                        }
                }
                
-               protected virtual void CreateMethods ()
+               protected internal virtual void CreateMethods ()
                {
                }
 
+#if NET_2_0
+               void InternalCreatePageProperty (string retType, string name, string contextProperty)
+               {
+                       CodeMemberProperty property = new CodeMemberProperty ();
+                       property.Name = name;
+                       property.Type = new CodeTypeReference (retType);
+                       property.Attributes = MemberAttributes.Family | MemberAttributes.Final;
+
+                       CodeMethodReturnStatement ret = new CodeMethodReturnStatement ();
+                       CodeCastExpression cast = new CodeCastExpression ();
+                       ret.Expression = cast;
+                       
+                       CodePropertyReferenceExpression refexp = new CodePropertyReferenceExpression ();
+                       refexp.TargetObject = new CodePropertyReferenceExpression (new CodeThisReferenceExpression (), "Context");
+                       refexp.PropertyName = contextProperty;
+                       
+                       cast.TargetType = new CodeTypeReference (retType);
+                       cast.Expression = refexp;
+                       
+                       property.GetStatements.Add (ret);
+                       if (partialClass == null)
+                               mainClass.Members.Add (property);
+                       else
+                               partialClass.Members.Add (property);
+               }
+               
+               protected void CreateProfileProperty ()
+               {
+                       string retType;
+                       if (AppCodeCompiler.HaveCustomProfile (WebConfigurationManager.GetSection ("system.web/profile") as ProfileSection))
+                               retType = "ProfileCommon";
+                       else
+                               retType = "System.Web.Profile.DefaultProfile";
+                       InternalCreatePageProperty (retType, "Profile", "Profile");
+               }
+#endif
+               
                protected virtual void AddInterfaces ()
                {
                        if (parser.Interfaces == null)
@@ -281,11 +527,75 @@ namespace System.Web.Compilation
                        if (results.NativeCompilerReturnValue == 0)
                                return;
 
-                       StringWriter writer = new StringWriter();
-                       provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
-                       throw new CompilationException (parser.InputFile, results.Errors, writer.ToString ());
+                       string fileText = null;
+                       CompilerErrorCollection errors = results.Errors;
+                       CompilerError ce = (errors != null && errors.Count > 0) ? errors [0] : null;
+                       string inFile = (ce != null) ? ce.FileName : null;
+                       
+                       if (inFile != null && File.Exists (inFile)) {
+                               using (StreamReader sr = File.OpenText (inFile)) {
+                                       fileText = sr.ReadToEnd ();
+                               }
+                       } else {
+                               StringWriter writer = new StringWriter();
+                               provider.CreateGenerator().GenerateCodeFromCompileUnit (unit, writer, null);
+                               fileText = writer.ToString ();
+                       }
+                       throw new CompilationException (parser.InputFile, errors, fileText);
+               }
+
+               protected string DynamicDir ()
+               {
+                       return AppDomain.CurrentDomain.SetupInformation.DynamicBase;
                }
 
+               internal static CodeDomProvider CreateProvider (string lang, out string compilerOptions, out int warningLevel, out string tempdir)
+               {
+                       return CreateProvider (HttpContext.Current, lang, out compilerOptions, out warningLevel, out tempdir);
+               }
+               
+               internal static CodeDomProvider CreateProvider (HttpContext context, string lang, out string compilerOptions, out int warningLevel, out string tempdir)
+               {
+                       CodeDomProvider ret = null;
+                       
+#if NET_2_0
+                       CompilationSection config = (CompilationSection) WebConfigurationManager.GetSection ("system.web/compilation");
+                       Compiler comp = config.Compilers[lang];
+                       
+                       if (comp == null) {
+                               CompilerInfo info = CodeDomProvider.GetCompilerInfo (lang);
+                               if (info != null && info.IsCodeDomProviderTypeValid) {
+                                       ret = info.CreateProvider ();
+
+                                       CompilerParameters cp = info.CreateDefaultCompilerParameters ();
+                                       compilerOptions = cp.CompilerOptions;
+                                       warningLevel = cp.WarningLevel;
+                               } else {
+                                       compilerOptions = String.Empty;
+                                       warningLevel = 2;
+                               }
+                       } else {
+                               Type t = HttpApplication.LoadType (comp.Type, true);
+                               ret = Activator.CreateInstance (t) as CodeDomProvider;
+
+                               compilerOptions = comp.CompilerOptions;
+                               warningLevel = comp.WarningLevel;
+                       }
+#else
+                       CompilationConfiguration config;
+
+                       config = CompilationConfiguration.GetInstance (context);
+                       ret = config.GetProvider (lang);
+
+                       compilerOptions = config.GetCompilerOptions (lang);
+                       warningLevel = config.GetWarningLevel (lang);
+#endif
+                       tempdir = config.TempDirectory;
+
+                       return ret;
+               }
+               
+               [MonoTODO ("find out how to extract the warningLevel and compilerOptions in the <system.codedom> case")]
                public virtual Type GetCompiledType () 
                {
                        Type type = CachingCompiler.GetTypeFromCache (parser.InputFile);
@@ -294,51 +604,155 @@ namespace System.Web.Compilation
 
                        Init ();
                        string lang = parser.Language;
-                       CompilationConfiguration config;
+                       string tempdir;
+                       string compilerOptions;
+                       int warningLevel;
 
-                       config = CompilationConfiguration.GetInstance (parser.Context);
-                       provider = config.GetProvider (lang);
-                       if (provider == null)
+                       Provider = CreateProvider (parser.Context, lang, out compilerOptions, out warningLevel, out tempdir);
+                       if (Provider == null)
                                throw new HttpException ("Configuration error. Language not supported: " +
                                                          lang, 500);
 
+#if !NET_2_0
                        compiler = provider.CreateCompiler ();
+#endif
 
-                       CreateMethods ();
-                       compilerParameters.IncludeDebugInformation = parser.Debug;
-                       compilerParameters.CompilerOptions = config.GetCompilerOptions (lang) + " " +
-                                                            parser.CompilerOptions;
-
-                       compilerParameters.WarningLevel = config.GetWarningLevel (lang);
+                       CompilerParameters parameters = CompilerParameters;
+                       parameters.IncludeDebugInformation = parser.Debug;
+                       parameters.CompilerOptions = compilerOptions + " " + parser.CompilerOptions;
+                       parameters.WarningLevel = warningLevel;
+                       
                        bool keepFiles = (Environment.GetEnvironmentVariable ("MONO_ASPNET_NODELETE") != null);
-                       if (!Directory.Exists (dynamicBase))
-                               Directory.CreateDirectory (dynamicBase);
 
-                       TempFileCollection tempcoll = new TempFileCollection (config.TempDirectory, keepFiles);
-                       compilerParameters.TempFiles = tempcoll;
+                       if (tempdir == null || tempdir == "")
+                               tempdir = DynamicDir ();
+                               
+                       TempFileCollection tempcoll = new TempFileCollection (tempdir, keepFiles);
+                       parameters.TempFiles = tempcoll;
                        string dllfilename = Path.GetFileName (tempcoll.AddExtension ("dll", true));
-                       compilerParameters.OutputAssembly = Path.Combine (dynamicBase, dllfilename);
+                       parameters.OutputAssembly = Path.Combine (DynamicDir (), dllfilename);
 
                        CompilerResults results = CachingCompiler.Compile (this);
                        CheckCompilerErrors (results);
-                       if (results.CompiledAssembly == null)
-                               throw new CompilationException (parser.InputFile, results.Errors,
-                                       "No assembly returned after compilation!?");
+                       Assembly assembly = results.CompiledAssembly;
+                       if (assembly == null) {
+                               if (!File.Exists (parameters.OutputAssembly)) {
+                                       results.TempFiles.Delete ();
+                                       throw new CompilationException (parser.InputFile, results.Errors,
+                                               "No assembly returned after compilation!?");
+                               }
+
+                               assembly = Assembly.LoadFrom (parameters.OutputAssembly);
+                       }
 
                        results.TempFiles.Delete ();
-                       return results.CompiledAssembly.GetType (mainClassExpr.Type.BaseType, true);
+                       Type mainClassType = assembly.GetType (mainClassExpr.Type.BaseType, true);
+
+#if NET_2_0
+                       if (parser.IsPartial) {
+                               // With the partial classes, we need to make sure we
+                               // don't have any methods that should have not been
+                               // created (because they are accessible from the base
+                               // types). We cannot do this normally because the
+                               // codebehind file is actually a partial class and we
+                               // have no way of identifying the partial class' base
+                               // type until now.
+                               if (!isRebuilding && CheckPartialBaseType (mainClassType)) {
+                                       isRebuilding = true;
+                                       parser.RootBuilder.ResetState ();
+                                       return GetCompiledType ();
+                               }
+                       }
+#endif
+
+                       return mainClassType;
                }
 
-               internal CompilerParameters CompilerParameters {
-                       get { return compilerParameters; }
+#if NET_2_0
+               internal bool IsRebuildingPartial
+               {
+                       get { return isRebuilding; }
                }
 
-               internal CodeCompileUnit Unit {
-                       get { return unit; }
+               internal bool CheckPartialBaseType (Type type)
+               {
+                       // Get the base type. If we don't have any (bad thing), we
+                       // don't need to replace ourselves. Also check for the
+                       // core file, since that won't have any either.
+                       Type baseType = type.BaseType;
+                       if (baseType == null || baseType == typeof(System.Web.UI.Page))
+                               return false;
+
+                       bool rebuild = false;
+
+                       if (CheckPartialBaseFields (type, baseType))
+                               rebuild = true;
+
+                       if (CheckPartialBaseProperties (type, baseType))
+                               rebuild = true;
+
+                       return rebuild;
+               }
+
+               internal bool CheckPartialBaseFields (Type type, Type baseType)
+               {
+                       bool rebuild = false;
+
+                       foreach (FieldInfo baseInfo in baseType.GetFields (replaceableFlags)) {
+                               if (baseInfo.IsPrivate)
+                                       continue;
+
+                               FieldInfo typeInfo = type.GetField (baseInfo.Name, replaceableFlags);
+
+                               if (typeInfo != null && typeInfo.DeclaringType == type) {
+                                       partialNameOverride [typeInfo.Name] = true;
+                                       rebuild = true;
+                               }
+                       }
+
+                       return rebuild;
+               }
+
+               internal bool CheckPartialBaseProperties (Type type, Type baseType)
+               {
+                       bool rebuild = false;
+
+                       foreach (PropertyInfo baseInfo in baseType.GetProperties ()) {
+                               PropertyInfo typeInfo = type.GetProperty (baseInfo.Name);
+
+                               if (typeInfo != null && typeInfo.DeclaringType == type) {
+                                       partialNameOverride [typeInfo.Name] = true;
+                                       rebuild = true;
+                               }
+                       }
+
+                       return rebuild;
                }
+#endif
 
-               internal virtual ICodeCompiler Compiler {
+               internal CodeDomProvider Provider {
+                       get { return provider; }
+                       set { provider = value; }
+               }
+
+               internal ICodeCompiler Compiler {
                        get { return compiler; }
+                       set { compiler = value; }
+               }               
+
+               internal CompilerParameters CompilerParameters {
+                       get {
+                               if (compilerParameters == null)
+                                       compilerParameters = new CompilerParameters ();
+                               
+                               return compilerParameters;
+                       }
+                       
+                       set { compilerParameters = value; }
+               }
+
+               internal CodeCompileUnit CompileUnit {
+                       get { return unit; }
                }
 
                internal TemplateParser Parser {