2007-07-18 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.UI / ControlBuilder.cs
1 //
2 // System.Web.UI.ControlBuilder.cs
3 //
4 // Authors:
5 //      Duncan Mak  (duncan@ximian.com)
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
7 //
8 // (C) 2002, 2003 Ximian, Inc. (http://www.ximian.com)
9 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
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.Collections;
32 using System.Configuration;
33 using System.CodeDom;
34 using System.Reflection;
35 using System.Security.Permissions;
36 using System.Web.Compilation;
37 using System.Web.Configuration;
38 using System.IO;
39
40 namespace System.Web.UI {
41
42         // CAS
43         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
44         [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
45         public class ControlBuilder
46         {
47                 internal static BindingFlags flagsNoCase = BindingFlags.Public |
48                         BindingFlags.Instance |
49                         BindingFlags.Static |
50                         BindingFlags.IgnoreCase;
51
52                 TemplateParser parser;
53                 internal ControlBuilder parentBuilder;
54                 Type type;             
55                 string tagName;
56                 string id;
57                 internal IDictionary attribs;
58                 internal int line;
59                 internal string fileName;
60                 bool childrenAsProperties;
61                 bool isIParserAccessor = true;
62                 bool hasAspCode;
63                 internal ControlBuilder defaultPropertyBuilder;
64                 ArrayList children;
65                 static int nextID;
66
67                 internal bool haveParserVariable;
68                 internal CodeMemberMethod method;
69                 internal CodeStatementCollection methodStatements;
70                 internal CodeMemberMethod renderMethod;
71                 internal int renderIndex;
72                 internal bool isProperty;
73                 internal ILocation location;
74                 ArrayList otherTags;
75 #if NET_2_0
76                 static string privateBinPath;
77 #endif
78                 
79                 public ControlBuilder ()
80                 {
81                 }
82
83                 internal ControlBuilder (TemplateParser parser,
84                                          ControlBuilder parentBuilder,
85                                          Type type,
86                                          string tagName,
87                                          string id,
88                                          IDictionary attribs,
89                                          int line,
90                                          string sourceFileName)
91
92                 {
93                         this.parser = parser;
94                         this.parentBuilder = parentBuilder;
95                         this.type = type;
96                         this.tagName = tagName;
97                         this.id = id;
98                         this.attribs = attribs;
99                         this.line = line;
100                         this.fileName = sourceFileName;
101                 }
102
103                 internal void EnsureOtherTags ()
104                 {
105                         if (otherTags == null)
106                                 otherTags = new ArrayList ();
107                 }
108                 
109                 internal ArrayList OtherTags {
110                         get { return otherTags; }
111                 }
112
113                 public Type ControlType {
114                         get { return type; }
115                 }
116
117                 protected bool FChildrenAsProperties {
118                         get { return childrenAsProperties; }
119                 }
120
121                 protected bool FIsNonParserAccessor {
122                         get { return !isIParserAccessor; }
123                 }
124
125                 public bool HasAspCode {
126                         get { return hasAspCode; }
127                 }
128
129                 public string ID {
130                         get { return id; }
131                         set { id = value; }
132                 }
133
134                 internal ArrayList Children {
135                         get { return children; }
136                 }
137
138                 internal void SetControlType (Type t)
139                 {
140                         type = t;
141                 }
142                 
143                 protected bool InDesigner {
144                         get { return false; }
145                 }
146
147                 public Type NamingContainerType {
148                         get {
149                                 if (parentBuilder == null)
150                                         return typeof (Control);
151
152                                 Type ptype = parentBuilder.ControlType;
153                                 if (ptype == null)
154                                         return parentBuilder.NamingContainerType;
155
156                                 if (!typeof (INamingContainer).IsAssignableFrom (ptype))
157                                         return parentBuilder.NamingContainerType;
158
159                                 return ptype;
160                         }
161                 }
162
163 #if NET_2_0
164                 private static string PrivateBinPath {
165                         get {
166                                 if (privateBinPath != null)
167                                         return privateBinPath;
168
169                                 AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
170                                 privateBinPath = Path.Combine (setup.ApplicationBase, setup.PrivateBinPath);
171
172                                 return privateBinPath;
173                         }
174                 }
175 #endif
176                 
177 #if NET_2_0
178                 public virtual
179 #else
180                 internal
181 #endif
182                 Type BindingContainerType {
183                         get {                           
184                                 if (parentBuilder == null) {
185 #if NET_2_0
186                                         Type bt = Parser.BaseType;
187                                         if (bt != null)
188                                                 return bt;
189 #endif
190
191                                         return typeof (Control);
192                                 }
193
194                                 if (parentBuilder is TemplateBuilder && ((TemplateBuilder)parentBuilder).ContainerType != null)
195                                         return ((TemplateBuilder)parentBuilder).ContainerType;
196                                 
197                                 Type ptype = parentBuilder.ControlType;
198                                 if (ptype == null)
199                                         return parentBuilder.BindingContainerType;
200                                 
201                                 if (!typeof (INamingContainer).IsAssignableFrom (ptype))
202                                         return parentBuilder.BindingContainerType;
203
204 #if NET_2_0
205                                 return typeof (Control);
206 #else
207                                 return ptype;
208 #endif
209                         }
210                 }
211
212                 internal TemplateBuilder ParentTemplateBuilder {
213                         get {
214                                 if (parentBuilder == null)
215                                         return null;
216                                 else if (parentBuilder is TemplateBuilder)
217                                         return (TemplateBuilder) parentBuilder;
218                                 else
219                                         return parentBuilder.ParentTemplateBuilder;
220                         }
221                 }
222
223                 protected TemplateParser Parser {
224                         get { return parser; }
225                 }
226
227                 public string TagName {
228                         get { return tagName; }
229                 }
230
231                 internal RootBuilder Root {
232                         get {
233                                 if (GetType () == typeof (RootBuilder))
234                                         return (RootBuilder) this;
235
236                                 return (RootBuilder) parentBuilder.Root;
237                         }
238                 }
239
240                 internal bool ChildrenAsProperties {
241                         get { return childrenAsProperties; }
242                 }
243                 
244                 public virtual bool AllowWhitespaceLiterals ()
245                 {
246                         return true;
247                 }
248
249                 public virtual void AppendLiteralString (string s)
250                 {
251                         if (s == null || s.Length == 0)
252                                 return;
253
254                         if (childrenAsProperties || !isIParserAccessor) {
255                                 if (defaultPropertyBuilder != null) {
256                                         defaultPropertyBuilder.AppendLiteralString (s);
257                                 } else if (s.Trim ().Length != 0) {
258                                         throw new HttpException (String.Format ("Literal content not allowed for '{0}' {1} \"{2}\"",
259                                                                                 tagName, GetType (), s));
260                                 }
261
262                                 return;
263                         }
264                         
265                         if (!AllowWhitespaceLiterals () && s.Trim ().Length == 0)
266                                 return;
267
268                         if (HtmlDecodeLiterals ())
269                                 s = HttpUtility.HtmlDecode (s);
270
271                         if (children == null)
272                                 children = new ArrayList ();
273
274                         children.Add (s);
275                 }
276
277                 public virtual void AppendSubBuilder (ControlBuilder subBuilder)
278                 {
279                         subBuilder.OnAppendToParentBuilder (this);
280                         
281                         subBuilder.parentBuilder = this;
282                         if (childrenAsProperties) {
283                                 AppendToProperty (subBuilder);
284                                 return;
285                         }
286
287                         if (typeof (CodeRenderBuilder).IsAssignableFrom (subBuilder.GetType ())) {
288                                 AppendCode (subBuilder);
289                                 return;
290                         }
291
292                         if (children == null)
293                                 children = new ArrayList ();
294
295                         children.Add (subBuilder);
296                 }
297
298                 void AppendToProperty (ControlBuilder subBuilder)
299                 {
300                         if (typeof (CodeRenderBuilder) == subBuilder.GetType ())
301                                 throw new HttpException ("Code render not supported here.");
302
303                         if (defaultPropertyBuilder != null) {
304                                 defaultPropertyBuilder.AppendSubBuilder (subBuilder);
305                                 return;
306                         }
307
308                         if (children == null)
309                                 children = new ArrayList ();
310
311                         children.Add (subBuilder);
312                 }
313
314                 void AppendCode (ControlBuilder subBuilder)
315                 {
316                         if (type != null && !(typeof (Control).IsAssignableFrom (type)))
317                                 throw new HttpException ("Code render not supported here.");
318
319                         if (typeof (CodeRenderBuilder) == subBuilder.GetType ())
320                                 hasAspCode = true;
321
322                         if (children == null)
323                                 children = new ArrayList ();
324
325                         children.Add (subBuilder);
326                 }
327
328                 public virtual void CloseControl ()
329                 {
330                 }
331
332 #if NET_2_0             
333                 static Type MapTagType (Type tagType)
334                 {
335                         if (tagType == null)
336                                 return null;
337                         
338                         PagesSection ps = WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
339                         if (ps == null)
340                                 return tagType;
341
342                         TagMapCollection tags = ps.TagMapping;
343                         if (tags == null || tags.Count == 0)
344                                 return tagType;
345                         
346                         string tagTypeName = tagType.ToString ();
347                         Type mappedType, originalType;
348                         string originalTypeName = String.Empty, mappedTypeName = String.Empty;
349                         bool missingType;
350                         Exception error;
351                         
352                         foreach (TagMapInfo tmi in tags) {
353                                 error = null;
354                                 originalType = null;
355                                 
356                                 try {
357                                         originalTypeName = tmi.TagType;
358                                         originalType = HttpApplication.LoadType (originalTypeName);
359                                         if (originalType == null)
360                                                 missingType = true;
361                                         else
362                                                 missingType = false;
363                                 } catch (Exception ex) {
364                                         missingType = true;
365                                         error = ex;
366                                 }
367                                 if (missingType)
368                                         throw new HttpException (String.Format ("Could not load type {0}", originalTypeName), error);
369                                 
370                                 if (originalTypeName == tagTypeName) {
371                                         mappedTypeName = tmi.MappedTagType;
372                                         error = null;
373                                         mappedType = null;
374                                         
375                                         try {
376                                                 mappedType = HttpApplication.LoadType (mappedTypeName);
377                                                 if (mappedType == null)
378                                                         missingType = true;
379                                                 else
380                                                         missingType = false;
381                                         } catch (Exception ex) {
382                                                 missingType = true;
383                                                 error = ex;
384                                         }
385
386                                         if (missingType)
387                                                 throw new HttpException (String.Format ("Could not load type {0}", mappedTypeName),
388                                                                          error);
389                                         
390                                         if (!mappedType.IsSubclassOf (originalType))
391                                                 throw new ConfigurationErrorsException (
392                                                         String.Format ("The specified type '{0}' used for mapping must inherit from the original type '{1}'.", mappedTypeName, originalTypeName));
393
394                                         return mappedType;
395                                 }
396                         }
397                         
398                         return tagType;
399                 }
400 #endif
401
402                 public static ControlBuilder CreateBuilderFromType (TemplateParser parser,
403                                                                     ControlBuilder parentBuilder,
404                                                                     Type type,
405                                                                     string tagName,
406                                                                     string id,
407                                                                     IDictionary attribs,
408                                                                     int line,
409                                                                     string sourceFileName)
410                 {
411
412                         Type tagType;
413 #if NET_2_0
414                         tagType = MapTagType (type);
415 #else
416                         tagType = type;
417 #endif
418                         ControlBuilder  builder;
419                         object [] atts = tagType.GetCustomAttributes (typeof (ControlBuilderAttribute), true);
420                         if (atts != null && atts.Length > 0) {
421                                 ControlBuilderAttribute att = (ControlBuilderAttribute) atts [0];
422                                 builder = (ControlBuilder) Activator.CreateInstance (att.BuilderType);
423                         } else {
424                                 builder = new ControlBuilder ();
425                         }
426
427                         builder.Init (parser, parentBuilder, tagType, tagName, id, attribs);
428                         builder.line = line;
429                         builder.fileName = sourceFileName;
430                         return builder;
431                 }
432
433                 public virtual Type GetChildControlType (string tagName, IDictionary attribs)
434                 {
435                         return null;
436                 }
437
438                 public virtual bool HasBody ()
439                 {
440                         return true;
441                 }
442
443                 public virtual bool HtmlDecodeLiterals ()
444                 {
445                         return false;
446                 }
447
448                 ControlBuilder CreatePropertyBuilder (string propName, TemplateParser parser, IDictionary atts)
449                 {
450                         PropertyInfo prop = type.GetProperty (propName, flagsNoCase);
451                         if (prop == null) {
452                                 string msg = String.Format ("Property {0} not found in type {1}", propName, type);
453                                 throw new HttpException (msg);
454                         }
455
456                         Type propType = prop.PropertyType;
457                         ControlBuilder builder = null;
458                         if (typeof (ICollection).IsAssignableFrom (propType)) {
459                                 builder = new CollectionBuilder ();
460                         } else if (typeof (ITemplate).IsAssignableFrom (propType)) {
461                                 builder = new TemplateBuilder (prop);
462                         } else if (typeof (string) == propType) {
463                                 builder = new StringPropertyBuilder (prop.Name);
464                         } else {
465                                 builder = CreateBuilderFromType (parser, parentBuilder, propType, prop.Name,
466                                                                  null, atts, line, fileName);
467                                 builder.isProperty = true;
468                                 return builder;
469                         }
470
471                         builder.Init (parser, this, null, prop.Name, null, atts);
472                         builder.fileName = fileName;
473                         builder.line = line;
474                         builder.isProperty = true;
475                         return builder;
476                 }
477                 
478                 public virtual void Init (TemplateParser parser,
479                                           ControlBuilder parentBuilder,
480                                           Type type,
481                                           string tagName,
482                                           string id,
483                                           IDictionary attribs)
484                 {
485                         this.parser = parser;
486                         if (parser != null)
487                                 this.location = parser.Location;
488
489                         this.parentBuilder = parentBuilder;
490                         this.type = type;
491                         this.tagName = tagName;
492                         this.id = id;
493                         this.attribs = attribs;
494                         if (type == null)
495                                 return;
496
497                         if (this is TemplateBuilder)
498                                 return;
499
500                         object [] atts = type.GetCustomAttributes (typeof (ParseChildrenAttribute), true);
501                         
502                         if (!typeof (IParserAccessor).IsAssignableFrom (type) && atts.Length == 0) {
503                                 isIParserAccessor = false;
504                                 childrenAsProperties = true;
505                         } else if (atts.Length > 0) {
506                                 ParseChildrenAttribute att = (ParseChildrenAttribute) atts [0];
507                                 childrenAsProperties = att.ChildrenAsProperties;
508                                 if (childrenAsProperties && att.DefaultProperty.Length != 0)
509                                         defaultPropertyBuilder = CreatePropertyBuilder (att.DefaultProperty,
510                                                                                         parser, null);
511                         }
512                 }
513
514                 public virtual bool NeedsTagInnerText ()
515                 {
516                         return false;
517                 }
518
519                 public virtual void OnAppendToParentBuilder (ControlBuilder parentBuilder)
520                 {
521                         if (defaultPropertyBuilder == null)
522                                 return;
523
524                         ControlBuilder old = defaultPropertyBuilder;
525                         defaultPropertyBuilder = null;
526                         AppendSubBuilder (old);
527                 }
528
529                 internal void SetTagName (string name)
530                 {
531                         tagName = name;
532                 }
533                 
534                 public virtual void SetTagInnerText (string text)
535                 {
536                 }
537
538                 internal string GetNextID (string proposedID)
539                 {
540                         if (proposedID != null && proposedID.Trim ().Length != 0)
541                                 return proposedID;
542
543                         return "_bctrl_" + nextID++;
544                 }
545
546                 internal virtual ControlBuilder CreateSubBuilder (string tagid,
547                                                                   Hashtable atts,
548                                                                   Type childType,
549                                                                   TemplateParser parser,
550                                                                   ILocation location)
551                 {
552                         ControlBuilder childBuilder = null;
553                         if (childrenAsProperties) {
554                                 if (defaultPropertyBuilder == null)
555                                         childBuilder = CreatePropertyBuilder (tagid, parser, atts);
556                                 else {
557                                         if (defaultPropertyBuilder.TagName == tagid) {
558                                                 // The child tag is the same what our default property name. Act as if there was
559                                                 // no default property builder, or otherwise we'll end up with invalid nested
560                                                 // builder call.
561                                                 defaultPropertyBuilder = null;
562                                                 childBuilder = CreatePropertyBuilder (tagid, parser, atts);
563                                         } else
564                                                 childBuilder = defaultPropertyBuilder.CreateSubBuilder (tagid, atts,
565                                                                                                         null, parser,
566                                                                                                         location);
567                                 }
568                                 return childBuilder;
569                         }
570
571                         if (tagName == tagid)
572                                 return null;
573                         
574                         childType = GetChildControlType (tagid, atts);
575                         if (childType == null)
576                                 return null;
577
578                         childBuilder = CreateBuilderFromType (parser, this, childType, tagid, id, atts,
579                                                               location.BeginLine, location.Filename);
580
581                         return childBuilder;
582                 }
583
584                 internal virtual object CreateInstance ()
585                 {
586                         // HtmlGenericControl, HtmlTableCell...
587                         object [] atts = type.GetCustomAttributes (typeof (ConstructorNeedsTagAttribute), true);
588                         object [] args = null;
589                         if (atts != null && atts.Length > 0) {
590                                 ConstructorNeedsTagAttribute att = (ConstructorNeedsTagAttribute) atts [0];
591                                 if (att.NeedsTag)
592                                         args = new object [] {tagName};
593                         }
594
595                         return Activator.CreateInstance (type, args);
596                 }
597
598                 internal virtual void CreateChildren (object parent) 
599                 {
600                         if (children == null || children.Count == 0)
601                                 return;
602
603                         IParserAccessor parser = parent as IParserAccessor;
604                         if (parser == null)
605                                 return;
606
607                         foreach (object o in children) {
608                                 if (o is string) {
609                                         parser.AddParsedSubObject (new LiteralControl ((string) o));
610                                 } else {
611                                         parser.AddParsedSubObject (((ControlBuilder) o).CreateInstance ());
612                                 }
613                         }
614                 }
615 #if NET_2_0
616                 [MonoTODO ("unsure, lack documentation")]
617                 public virtual object BuildObject ()
618                 {
619                         return CreateInstance ();
620                 }
621
622                 internal void ResetState()
623                 {
624                         renderIndex = 0;
625                         haveParserVariable = false;
626
627                         if (Children != null) {
628                                 foreach (object child in Children) {
629                                         ControlBuilder cb = child as ControlBuilder;
630                                         if (cb != null)
631                                                 cb.ResetState ();
632                                 }
633                         }
634                 }
635 #endif
636         }
637 }