2009-01-23 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.Globalization;
35 using System.Reflection;
36 using System.Security.Permissions;
37 using System.Web.Compilation;
38 using System.Web.Configuration;
39 using System.IO;
40 using System.Web.UI.WebControls;
41
42 namespace System.Web.UI {
43
44         // CAS
45         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
46         [AspNetHostingPermission (SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
47         public class ControlBuilder
48         {
49                 internal static readonly BindingFlags FlagsNoCase = BindingFlags.Public |
50                         BindingFlags.Instance |
51                         BindingFlags.Static |
52                         BindingFlags.IgnoreCase;
53
54                 ControlBuilder myNamingContainer;
55                 TemplateParser parser;
56                 Type parserType;
57                 ControlBuilder parentBuilder;
58                 Type type;             
59                 string tagName;
60                 string originalTagName;
61                 string id;
62                 IDictionary attribs;
63                 int line;
64                 string fileName;
65                 bool childrenAsProperties;
66                 bool isIParserAccessor = true;
67                 bool hasAspCode;
68                 ControlBuilder defaultPropertyBuilder;
69                 ArrayList children;
70                 ArrayList templateChildren;
71                 static int nextID;
72
73                 bool haveParserVariable;
74                 CodeMemberMethod method;
75                 CodeStatementCollection methodStatements;
76                 CodeMemberMethod renderMethod;
77                 int renderIndex;
78                 bool isProperty;
79                 ILocation location;
80                 ArrayList otherTags;
81                 
82                 public ControlBuilder ()
83                 {
84                 }
85
86                 internal ControlBuilder (TemplateParser parser,
87                                          ControlBuilder parentBuilder,
88                                          Type type,
89                                          string tagName,
90                                          string id,
91                                          IDictionary attribs,
92                                          int line,
93                                          string sourceFileName)
94
95                 {
96                         this.parser = parser;
97                         this.parserType = parser != null ? parser.GetType () : null;
98                         this.parentBuilder = parentBuilder;
99                         this.type = type;
100                         this.tagName = tagName;
101                         this.id = id;
102                         this.attribs = attribs;
103                         this.line = line;
104                         this.fileName = sourceFileName;
105                 }
106
107                 internal void EnsureOtherTags ()
108                 {
109                         if (otherTags == null)
110                                 otherTags = new ArrayList ();
111                 }
112
113                 internal ControlBuilder ParentBuilder {
114                         get { return parentBuilder; }
115                 }
116
117                 internal IDictionary Attributes {
118                         get { return attribs; }
119                 }
120
121                 internal int Line {
122                         get { return line; }
123                         set { line = value; }
124                 }
125
126                 internal string FileName {
127                         get { return fileName; }
128                         set { fileName = value; }
129                 }
130
131                 internal ControlBuilder DefaultPropertyBuilder {
132                         get { return defaultPropertyBuilder; }
133                 }
134
135                 internal bool HaveParserVariable {
136                         get { return haveParserVariable; }
137                         set { haveParserVariable = value; }
138                 }
139
140                 internal CodeMemberMethod Method {
141                         get { return method; }
142                         set { method = value; }
143                 }
144
145                 internal CodeStatementCollection MethodStatements {
146                         get { return methodStatements; }
147                         set { methodStatements = value; }
148                 }
149
150                 internal CodeMemberMethod RenderMethod {
151                         get { return renderMethod; }
152                         set { renderMethod = value; }
153                 }
154
155                 internal int RenderIndex {
156                         get { return renderIndex; }
157                 }
158
159                 internal bool IsProperty {
160                         get { return isProperty; }
161                 }
162
163                 internal ILocation Location {
164                         get { return location; }
165                         set { location = value; }
166                 }
167         
168                 internal ArrayList OtherTags {
169                         get { return otherTags; }
170                 }
171
172                 public Type ControlType {
173                         get { return type; }
174                 }
175
176                 protected bool FChildrenAsProperties {
177                         get { return childrenAsProperties; }
178                 }
179
180                 protected bool FIsNonParserAccessor {
181                         get { return !isIParserAccessor; }
182                 }
183
184                 public bool HasAspCode {
185                         get { return hasAspCode; }
186                 }
187
188                 public string ID {
189                         get { return id; }
190                         set { id = value; }
191                 }
192
193                 internal ArrayList Children {
194                         get { return children; }
195                 }
196
197                 internal ArrayList TemplateChildren {
198                         get { return templateChildren; }
199                 }
200                 
201                 internal void SetControlType (Type t)
202                 {
203                         type = t;
204                 }
205                 
206                 protected bool InDesigner {
207                         get { return false; }
208                 }
209
210                 public Type NamingContainerType {
211                         get {
212                                 ControlBuilder cb = myNamingContainer;
213                                 
214                                 if (cb == null)
215                                         return typeof (Control);
216
217                                 return cb.ControlType;
218                         }
219                 }
220
221                 ControlBuilder MyNamingContainer {
222                         get {
223                                 if (myNamingContainer == null) {
224                                         Type controlType = parentBuilder != null ? parentBuilder.ControlType : null;
225                                         
226                                         if (parentBuilder == null && controlType == null)
227                                                 myNamingContainer = null;
228                                         else if (parentBuilder is TemplateBuilder)
229                                                 myNamingContainer = parentBuilder;
230                                         else if (controlType != null && typeof (INamingContainer).IsAssignableFrom (controlType))
231                                                 myNamingContainer = parentBuilder;
232                                         else
233                                                 myNamingContainer = parentBuilder.MyNamingContainer;
234                                 }
235
236                                 return myNamingContainer;
237                         }
238                 }
239                         
240 #if NET_2_0
241                 public virtual
242 #else
243                 internal
244 #endif
245                 Type BindingContainerType {
246                         get {
247                                 ControlBuilder cb = (this is TemplateBuilder && !(this is RootBuilder)) ? this : MyNamingContainer;
248                                 
249                                 if (cb == null) {
250                                         if (this is RootBuilder && parserType == typeof (PageParser)) 
251                                                 return typeof (Page);
252                                         
253                                         return typeof (Control);
254                                 }
255
256 #if NET_2_0
257                                 if (cb != this && cb is ContentBuilderInternal && !typeof (INonBindingContainer).IsAssignableFrom (cb.BindingContainerType))
258                                         return cb.BindingContainerType;
259 #endif
260
261                                 Type ct;
262                                 if (cb is TemplateBuilder) {
263                                         ct = ((TemplateBuilder) cb).ContainerType;
264                                         if (typeof (INonBindingContainer).IsAssignableFrom (ct))
265                                                 return MyNamingContainer.BindingContainerType;
266                                         
267                                         if (ct != null)
268                                                 return ct;
269
270                                         ct = cb.ControlType;
271                                         if (typeof (INonBindingContainer).IsAssignableFrom (ct) || !typeof (INamingContainer).IsAssignableFrom (ct))
272                                                 return MyNamingContainer.BindingContainerType;
273
274                                         return ct;
275                                 }
276
277                                 ct = cb.ControlType;
278                                 if (typeof (INonBindingContainer).IsAssignableFrom (ct) || !typeof (INamingContainer).IsAssignableFrom (ct))
279                                         return MyNamingContainer.BindingContainerType;
280                                 
281                                 return cb.ControlType;
282                         }
283                 }
284
285                 internal TemplateBuilder ParentTemplateBuilder {
286                         get {
287                                 if (parentBuilder == null)
288                                         return null;
289                                 else if (parentBuilder is TemplateBuilder)
290                                         return (TemplateBuilder) parentBuilder;
291                                 else
292                                         return parentBuilder.ParentTemplateBuilder;
293                         }
294                 }
295
296                 protected TemplateParser Parser {
297                         get { return parser; }
298                 }
299
300                 public string TagName {
301                         get { return tagName; }
302                 }
303
304                 internal string OriginalTagName {
305                         get {
306                                 if (originalTagName == null || originalTagName.Length == 0)
307                                         return TagName;
308                                 return originalTagName;
309                         }
310                 }
311                 
312                 internal RootBuilder Root {
313                         get {
314                                 if (GetType () == typeof (RootBuilder))
315                                         return (RootBuilder) this;
316
317                                 return (RootBuilder) parentBuilder.Root;
318                         }
319                 }
320
321                 internal bool ChildrenAsProperties {
322                         get { return childrenAsProperties; }
323                 }
324
325                 internal string GetAttribute (string name)
326                 {
327                         if (attribs == null)
328                                 return null;
329
330                         return attribs [name] as string;
331                 }
332
333                 internal void IncreaseRenderIndex ()
334                 {
335                         renderIndex++;
336                 }
337                 
338                 void AddChild (object child)
339                 {
340                         if (children == null)
341                                 children = new ArrayList ();
342
343                         children.Add (child);
344                         ControlBuilder cb = child as ControlBuilder;
345                         if (cb != null && cb is TemplateBuilder) {
346                                 if (templateChildren == null)
347                                         templateChildren = new ArrayList ();
348                                 templateChildren.Add (child);
349                         }
350
351 #if NET_2_0
352                         if (parser == null)
353                                 return;
354                         
355                         string tag = cb != null ? cb.TagName : null;
356                         if (String.IsNullOrEmpty (tag))
357                                 return;
358
359                         RootBuilder rb = Root;
360                         AspComponentFoundry foundry = rb != null ? rb.Foundry : null;
361                         if (foundry == null)
362                                 return;
363                         AspComponent component = foundry.GetComponent (tag);
364                         if (component == null || !component.FromConfig)
365                                 return;
366                         
367                         parser.AddImport (component.Namespace);
368                         parser.AddDependency (component.Source);
369 #endif
370                 }
371                 
372                 public virtual bool AllowWhitespaceLiterals ()
373                 {
374                         return true;
375                 }
376
377                 public virtual void AppendLiteralString (string s)
378                 {
379                         if (s == null || s.Length == 0)
380                                 return;
381
382                         if (childrenAsProperties || !isIParserAccessor) {
383                                 if (defaultPropertyBuilder != null) {
384                                         defaultPropertyBuilder.AppendLiteralString (s);
385                                 } else if (s.Trim ().Length != 0) {
386                                         throw new HttpException (String.Format ("Literal content not allowed for '{0}' {1} \"{2}\"",
387                                                                                 tagName, GetType (), s));
388                                 }
389
390                                 return;
391                         }
392                         
393                         if (!AllowWhitespaceLiterals () && s.Trim ().Length == 0)
394                                 return;
395
396                         if (HtmlDecodeLiterals ())
397                                 s = HttpUtility.HtmlDecode (s);
398
399                         AddChild (s);
400                 }
401
402                 public virtual void AppendSubBuilder (ControlBuilder subBuilder)
403                 {
404                         subBuilder.OnAppendToParentBuilder (this);
405                         
406                         subBuilder.parentBuilder = this;
407                         if (childrenAsProperties) {
408                                 AppendToProperty (subBuilder);
409                                 return;
410                         }
411
412                         if (typeof (CodeRenderBuilder).IsAssignableFrom (subBuilder.GetType ())) {
413                                 AppendCode (subBuilder);
414                                 return;
415                         }
416
417                         AddChild (subBuilder);
418                 }
419
420                 void AppendToProperty (ControlBuilder subBuilder)
421                 {
422                         if (typeof (CodeRenderBuilder) == subBuilder.GetType ())
423                                 throw new HttpException ("Code render not supported here.");
424
425                         if (defaultPropertyBuilder != null) {
426                                 defaultPropertyBuilder.AppendSubBuilder (subBuilder);
427                                 return;
428                         }
429
430                         AddChild (subBuilder);
431                 }
432
433                 void AppendCode (ControlBuilder subBuilder)
434                 {
435                         if (type != null && !(typeof (Control).IsAssignableFrom (type)))
436                                 throw new HttpException ("Code render not supported here.");
437
438                         if (typeof (CodeRenderBuilder) == subBuilder.GetType ())
439                                 hasAspCode = true;
440
441                         AddChild (subBuilder);
442                 }
443
444                 public virtual void CloseControl ()
445                 {
446                 }
447
448 #if NET_2_0             
449                 static Type MapTagType (Type tagType)
450                 {
451                         if (tagType == null)
452                                 return null;
453                         
454                         PagesSection ps = WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
455                         if (ps == null)
456                                 return tagType;
457
458                         TagMapCollection tags = ps.TagMapping;
459                         if (tags == null || tags.Count == 0)
460                                 return tagType;
461                         
462                         string tagTypeName = tagType.ToString ();
463                         Type mappedType, originalType;
464                         string originalTypeName = String.Empty, mappedTypeName = String.Empty;
465                         bool missingType;
466                         Exception error;
467                         
468                         foreach (TagMapInfo tmi in tags) {
469                                 error = null;
470                                 originalType = null;
471                                 
472                                 try {
473                                         originalTypeName = tmi.TagType;
474                                         originalType = HttpApplication.LoadType (originalTypeName);
475                                         if (originalType == null)
476                                                 missingType = true;
477                                         else
478                                                 missingType = false;
479                                 } catch (Exception ex) {
480                                         missingType = true;
481                                         error = ex;
482                                 }
483                                 if (missingType)
484                                         throw new HttpException (String.Format ("Could not load type {0}", originalTypeName), error);
485                                 
486                                 if (originalTypeName == tagTypeName) {
487                                         mappedTypeName = tmi.MappedTagType;
488                                         error = null;
489                                         mappedType = null;
490                                         
491                                         try {
492                                                 mappedType = HttpApplication.LoadType (mappedTypeName);
493                                                 if (mappedType == null)
494                                                         missingType = true;
495                                                 else
496                                                         missingType = false;
497                                         } catch (Exception ex) {
498                                                 missingType = true;
499                                                 error = ex;
500                                         }
501
502                                         if (missingType)
503                                                 throw new HttpException (String.Format ("Could not load type {0}", mappedTypeName),
504                                                                          error);
505                                         
506                                         if (!mappedType.IsSubclassOf (originalType))
507                                                 throw new ConfigurationErrorsException (
508                                                         String.Format ("The specified type '{0}' used for mapping must inherit from the original type '{1}'.", mappedTypeName, originalTypeName));
509
510                                         return mappedType;
511                                 }
512                         }
513                         
514                         return tagType;
515                 }
516 #endif
517
518                 public static ControlBuilder CreateBuilderFromType (TemplateParser parser,
519                                                                     ControlBuilder parentBuilder,
520                                                                     Type type,
521                                                                     string tagName,
522                                                                     string id,
523                                                                     IDictionary attribs,
524                                                                     int line,
525                                                                     string sourceFileName)
526                 {
527
528                         Type tagType;
529 #if NET_2_0
530                         tagType = MapTagType (type);
531 #else
532                         tagType = type;
533 #endif
534                         ControlBuilder  builder;
535                         object [] atts = tagType.GetCustomAttributes (typeof (ControlBuilderAttribute), true);
536                         if (atts != null && atts.Length > 0) {
537                                 ControlBuilderAttribute att = (ControlBuilderAttribute) atts [0];
538                                 builder = (ControlBuilder) Activator.CreateInstance (att.BuilderType);
539                         } else {
540                                 builder = new ControlBuilder ();
541                         }
542
543                         builder.Init (parser, parentBuilder, tagType, tagName, id, attribs);
544                         builder.line = line;
545                         builder.fileName = sourceFileName;
546                         return builder;
547                 }
548
549                 public virtual Type GetChildControlType (string tagName, IDictionary attribs)
550                 {
551                         return null;
552                 }
553
554                 public virtual bool HasBody ()
555                 {
556                         return true;
557                 }
558
559                 public virtual bool HtmlDecodeLiterals ()
560                 {
561                         return false;
562                 }
563                 
564                 ControlBuilder CreatePropertyBuilder (string propName, TemplateParser parser, IDictionary atts)
565                 {
566                         int idx;
567                         string propertyName;
568                         
569                         if ((idx = propName.IndexOf (':')) >= 0)
570                                 propertyName = propName.Substring (idx + 1);
571                         else
572                                 propertyName = propName;
573                         
574                         PropertyInfo prop = type.GetProperty (propertyName, FlagsNoCase);
575                         if (prop == null) {
576                                 string msg = String.Format ("Property {0} not found in type {1}", propertyName, type);
577                                 throw new HttpException (msg);
578                         }
579
580                         Type propType = prop.PropertyType;
581                         ControlBuilder builder = null;
582                         if (typeof (ICollection).IsAssignableFrom (propType)) {
583                                 builder = new CollectionBuilder ();
584                         } else if (typeof (ITemplate).IsAssignableFrom (propType)) {
585                                 builder = new TemplateBuilder (prop);
586                         } else if (typeof (string) == propType) {
587                                 builder = new StringPropertyBuilder (prop.Name);
588                         } else {
589                                 builder = CreateBuilderFromType (parser, parentBuilder, propType, prop.Name,
590                                                                  null, atts, line, fileName);
591                                 builder.isProperty = true;
592                                 if (idx >= 0)
593                                         builder.originalTagName = propName;
594                                 return builder;
595                         }
596
597                         builder.Init (parser, this, null, prop.Name, null, atts);
598                         builder.fileName = fileName;
599                         builder.line = line;
600                         builder.isProperty = true;
601                         if (idx >= 0)
602                                 builder.originalTagName = propName;
603                         return builder;
604                 }
605                 
606                 public virtual void Init (TemplateParser parser,
607                                           ControlBuilder parentBuilder,
608                                           Type type,
609                                           string tagName,
610                                           string id,
611                                           IDictionary attribs)
612                 {
613                         this.parser = parser;
614                         if (parser != null)
615                                 this.location = parser.Location;
616
617                         this.parentBuilder = parentBuilder;
618                         this.type = type;
619                         this.tagName = tagName;
620                         this.id = id;
621                         this.attribs = attribs;
622                         if (type == null)
623                                 return;
624
625                         if (this is TemplateBuilder)
626                                 return;
627
628                         object [] atts = type.GetCustomAttributes (typeof (ParseChildrenAttribute), true);
629                         
630                         if (!typeof (IParserAccessor).IsAssignableFrom (type) && atts.Length == 0) {
631                                 isIParserAccessor = false;
632                                 childrenAsProperties = true;
633                         } else if (atts.Length > 0) {
634                                 ParseChildrenAttribute att = (ParseChildrenAttribute) atts [0];
635                                 childrenAsProperties = att.ChildrenAsProperties;
636                                 if (childrenAsProperties && att.DefaultProperty.Length != 0)
637                                         defaultPropertyBuilder = CreatePropertyBuilder (att.DefaultProperty,
638                                                                                         parser, null);
639                         }
640                 }
641
642                 public virtual bool NeedsTagInnerText ()
643                 {
644                         return false;
645                 }
646
647                 public virtual void OnAppendToParentBuilder (ControlBuilder parentBuilder)
648                 {
649                         if (defaultPropertyBuilder == null)
650                                 return;
651
652                         ControlBuilder old = defaultPropertyBuilder;
653                         defaultPropertyBuilder = null;
654                         AppendSubBuilder (old);
655                 }
656
657                 internal void SetTagName (string name)
658                 {
659                         tagName = name;
660                 }
661                 
662                 public virtual void SetTagInnerText (string text)
663                 {
664                 }
665
666                 internal string GetNextID (string proposedID)
667                 {
668                         if (proposedID != null && proposedID.Trim ().Length != 0)
669                                 return proposedID;
670
671                         return "_bctrl_" + nextID++;
672                 }
673
674                 internal virtual ControlBuilder CreateSubBuilder (string tagid,
675                                                                   Hashtable atts,
676                                                                   Type childType,
677                                                                   TemplateParser parser,
678                                                                   ILocation location)
679                 {
680                         ControlBuilder childBuilder = null;
681                         if (childrenAsProperties) {
682                                 if (defaultPropertyBuilder == null)
683                                         childBuilder = CreatePropertyBuilder (tagid, parser, atts);
684                                 else {
685                                         if (String.Compare (defaultPropertyBuilder.TagName, tagid, true, CultureInfo.InvariantCulture) == 0) {
686                                                 // The child tag is the same what our default property name. Act as if there was
687                                                 // no default property builder, or otherwise we'll end up with invalid nested
688                                                 // builder call.
689                                                 defaultPropertyBuilder = null;
690                                                 childBuilder = CreatePropertyBuilder (tagid, parser, atts);
691                                         } else
692                                                 childBuilder = defaultPropertyBuilder.CreateSubBuilder (tagid, atts,
693                                                                                                         null, parser,
694                                                                                                         location);
695                                 }
696
697                                 return childBuilder;
698                         }
699
700                         if (String.Compare (tagName, tagid, true, CultureInfo.InvariantCulture) == 0)
701                                 return null;
702                         
703                         childType = GetChildControlType (tagid, atts);
704                         if (childType == null)
705                                 return null;
706
707                         childBuilder = CreateBuilderFromType (parser, this, childType, tagid, id, atts,
708                                                               location.BeginLine, location.Filename);
709
710                         return childBuilder;
711                 }
712
713                 internal virtual object CreateInstance ()
714                 {
715                         // HtmlGenericControl, HtmlTableCell...
716                         object [] atts = type.GetCustomAttributes (typeof (ConstructorNeedsTagAttribute), true);
717                         object [] args = null;
718                         if (atts != null && atts.Length > 0) {
719                                 ConstructorNeedsTagAttribute att = (ConstructorNeedsTagAttribute) atts [0];
720                                 if (att.NeedsTag)
721                                         args = new object [] {tagName};
722                         }
723
724                         return Activator.CreateInstance (type, args);
725                 }
726
727                 internal virtual void CreateChildren (object parent) 
728                 {
729                         if (children == null || children.Count == 0)
730                                 return;
731
732                         IParserAccessor parser = parent as IParserAccessor;
733                         if (parser == null)
734                                 return;
735
736                         foreach (object o in children) {
737                                 if (o is string) {
738                                         parser.AddParsedSubObject (new LiteralControl ((string) o));
739                                 } else {
740                                         parser.AddParsedSubObject (((ControlBuilder) o).CreateInstance ());
741                                 }
742                         }
743                 }
744 #if NET_2_0
745                 [MonoTODO ("unsure, lack documentation")]
746                 public virtual object BuildObject ()
747                 {
748                         return CreateInstance ();
749                 }
750
751                 internal void ResetState()
752                 {
753                         renderIndex = 0;
754                         haveParserVariable = false;
755
756                         if (Children != null) {
757                                 foreach (object child in Children) {
758                                         ControlBuilder cb = child as ControlBuilder;
759                                         if (cb != null)
760                                                 cb.ResetState ();
761                                 }
762                         }
763                 }
764 #endif
765         }
766 }