2007-06-20 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                                         return typeof (Control);
186                                         
187                                 if (parentBuilder is TemplateBuilder && ((TemplateBuilder)parentBuilder).ContainerType != null)
188                                         return ((TemplateBuilder)parentBuilder).ContainerType;
189
190                                 Type ptype = parentBuilder.ControlType;
191                                 if (ptype == null)
192                                         return parentBuilder.BindingContainerType;
193
194                                 if (!typeof (INamingContainer).IsAssignableFrom (ptype))
195                                         return parentBuilder.BindingContainerType;
196
197                                 return ptype;
198                         }
199                 }
200
201                 internal TemplateBuilder ParentTemplateBuilder {
202                         get {
203                                 if (parentBuilder == null)
204                                         return null;
205                                 else if (parentBuilder is TemplateBuilder)
206                                         return (TemplateBuilder) parentBuilder;
207                                 else
208                                         return parentBuilder.ParentTemplateBuilder;
209                         }
210                 }
211
212                 protected TemplateParser Parser {
213                         get { return parser; }
214                 }
215
216                 public string TagName {
217                         get { return tagName; }
218                 }
219
220                 internal RootBuilder Root {
221                         get {
222                                 if (GetType () == typeof (RootBuilder))
223                                         return (RootBuilder) this;
224
225                                 return (RootBuilder) parentBuilder.Root;
226                         }
227                 }
228
229                 internal bool ChildrenAsProperties {
230                         get { return childrenAsProperties; }
231                 }
232                 
233                 public virtual bool AllowWhitespaceLiterals ()
234                 {
235                         return true;
236                 }
237
238                 public virtual void AppendLiteralString (string s)
239                 {
240                         if (s == null || s.Length == 0)
241                                 return;
242
243                         if (childrenAsProperties || !isIParserAccessor) {
244                                 if (defaultPropertyBuilder != null) {
245                                         defaultPropertyBuilder.AppendLiteralString (s);
246                                 } else if (s.Trim ().Length != 0) {
247                                         throw new HttpException (String.Format ("Literal content not allowed for '{0}' {1} \"{2}\"",
248                                                                                 tagName, GetType (), s));
249                                 }
250
251                                 return;
252                         }
253                         
254                         if (!AllowWhitespaceLiterals () && s.Trim ().Length == 0)
255                                 return;
256
257                         if (HtmlDecodeLiterals ())
258                                 s = HttpUtility.HtmlDecode (s);
259
260                         if (children == null)
261                                 children = new ArrayList ();
262
263                         children.Add (s);
264                 }
265
266                 public virtual void AppendSubBuilder (ControlBuilder subBuilder)
267                 {
268                         subBuilder.OnAppendToParentBuilder (this);
269                         
270                         subBuilder.parentBuilder = this;
271                         if (childrenAsProperties) {
272                                 AppendToProperty (subBuilder);
273                                 return;
274                         }
275
276                         if (typeof (CodeRenderBuilder).IsAssignableFrom (subBuilder.GetType ())) {
277                                 AppendCode (subBuilder);
278                                 return;
279                         }
280
281                         if (children == null)
282                                 children = new ArrayList ();
283
284                         children.Add (subBuilder);
285                 }
286
287                 void AppendToProperty (ControlBuilder subBuilder)
288                 {
289                         if (typeof (CodeRenderBuilder) == subBuilder.GetType ())
290                                 throw new HttpException ("Code render not supported here.");
291
292                         if (defaultPropertyBuilder != null) {
293                                 defaultPropertyBuilder.AppendSubBuilder (subBuilder);
294                                 return;
295                         }
296
297                         if (children == null)
298                                 children = new ArrayList ();
299
300                         children.Add (subBuilder);
301                 }
302
303                 void AppendCode (ControlBuilder subBuilder)
304                 {
305                         if (type != null && !(typeof (Control).IsAssignableFrom (type)))
306                                 throw new HttpException ("Code render not supported here.");
307
308                         if (typeof (CodeRenderBuilder) == subBuilder.GetType ())
309                                 hasAspCode = true;
310
311                         if (children == null)
312                                 children = new ArrayList ();
313
314                         children.Add (subBuilder);
315                 }
316
317                 public virtual void CloseControl ()
318                 {
319                 }
320
321 #if NET_2_0             
322                 static Type MapTagType (Type tagType)
323                 {
324                         if (tagType == null)
325                                 return null;
326                         
327                         PagesSection ps = WebConfigurationManager.GetSection ("system.web/pages") as PagesSection;
328                         if (ps == null)
329                                 return tagType;
330
331                         TagMapCollection tags = ps.TagMapping;
332                         if (tags == null || tags.Count == 0)
333                                 return tagType;
334                         
335                         string tagTypeName = tagType.ToString ();
336                         Type mappedType, originalType;
337                         string originalTypeName = String.Empty, mappedTypeName = String.Empty;
338                         bool missingType;
339                         Exception error;
340                         
341                         foreach (TagMapInfo tmi in tags) {
342                                 error = null;
343                                 originalType = null;
344                                 
345                                 try {
346                                         originalTypeName = tmi.TagType;
347                                         originalType = HttpApplication.LoadType (originalTypeName);
348                                         if (originalType == null)
349                                                 missingType = true;
350                                         else
351                                                 missingType = false;
352                                 } catch (Exception ex) {
353                                         missingType = true;
354                                         error = ex;
355                                 }
356                                 if (missingType)
357                                         throw new HttpException (String.Format ("Could not load type {0}", originalTypeName), error);
358                                 
359                                 if (originalTypeName == tagTypeName) {
360                                         mappedTypeName = tmi.MappedTagType;
361                                         error = null;
362                                         mappedType = null;
363                                         
364                                         try {
365                                                 mappedType = HttpApplication.LoadType (mappedTypeName);
366                                                 if (mappedType == null)
367                                                         missingType = true;
368                                                 else
369                                                         missingType = false;
370                                         } catch (Exception ex) {
371                                                 missingType = true;
372                                                 error = ex;
373                                         }
374
375                                         if (missingType)
376                                                 throw new HttpException (String.Format ("Could not load type {0}", mappedTypeName),
377                                                                          error);
378                                         
379                                         if (!mappedType.IsSubclassOf (originalType))
380                                                 throw new ConfigurationErrorsException (
381                                                         String.Format ("The specified type '{0}' used for mapping must inherit from the original type '{1}'.", mappedTypeName, originalTypeName));
382
383                                         return mappedType;
384                                 }
385                         }
386                         
387                         return tagType;
388                 }
389 #endif
390
391                 public static ControlBuilder CreateBuilderFromType (TemplateParser parser,
392                                                                     ControlBuilder parentBuilder,
393                                                                     Type type,
394                                                                     string tagName,
395                                                                     string id,
396                                                                     IDictionary attribs,
397                                                                     int line,
398                                                                     string sourceFileName)
399                 {
400
401                         Type tagType;
402 #if NET_2_0
403                         tagType = MapTagType (type);
404 #else
405                         tagType = type;
406 #endif
407                         ControlBuilder  builder;
408                         object [] atts = tagType.GetCustomAttributes (typeof (ControlBuilderAttribute), true);
409                         if (atts != null && atts.Length > 0) {
410                                 ControlBuilderAttribute att = (ControlBuilderAttribute) atts [0];
411                                 builder = (ControlBuilder) Activator.CreateInstance (att.BuilderType);
412                         } else {
413                                 builder = new ControlBuilder ();
414                         }
415
416                         builder.Init (parser, parentBuilder, tagType, tagName, id, attribs);
417                         builder.line = line;
418                         builder.fileName = sourceFileName;
419                         return builder;
420                 }
421
422                 public virtual Type GetChildControlType (string tagName, IDictionary attribs)
423                 {
424                         return null;
425                 }
426
427                 public virtual bool HasBody ()
428                 {
429                         return true;
430                 }
431
432                 public virtual bool HtmlDecodeLiterals ()
433                 {
434                         return false;
435                 }
436
437                 ControlBuilder CreatePropertyBuilder (string propName, TemplateParser parser, IDictionary atts)
438                 {
439                         PropertyInfo prop = type.GetProperty (propName, flagsNoCase);
440                         if (prop == null) {
441                                 string msg = String.Format ("Property {0} not found in type {1}", propName, type);
442                                 throw new HttpException (msg);
443                         }
444
445                         Type propType = prop.PropertyType;
446                         ControlBuilder builder = null;
447                         if (typeof (ICollection).IsAssignableFrom (propType)) {
448                                 builder = new CollectionBuilder ();
449                         } else if (typeof (ITemplate).IsAssignableFrom (propType)) {
450                                 builder = new TemplateBuilder (prop);
451                         } else if (typeof (string) == propType) {
452                                 builder = new StringPropertyBuilder (prop.Name);
453                         } else {
454                                 builder = CreateBuilderFromType (parser, parentBuilder, propType, prop.Name,
455                                                                  null, atts, line, fileName);
456                                 builder.isProperty = true;
457                                 return builder;
458                         }
459
460                         builder.Init (parser, this, null, prop.Name, null, atts);
461                         builder.fileName = fileName;
462                         builder.line = line;
463                         builder.isProperty = true;
464                         return builder;
465                 }
466                 
467                 public virtual void Init (TemplateParser parser,
468                                           ControlBuilder parentBuilder,
469                                           Type type,
470                                           string tagName,
471                                           string id,
472                                           IDictionary attribs)
473                 {
474                         this.parser = parser;
475                         if (parser != null)
476                                 this.location = parser.Location;
477
478                         this.parentBuilder = parentBuilder;
479                         this.type = type;
480                         this.tagName = tagName;
481                         this.id = id;
482                         this.attribs = attribs;
483                         if (type == null)
484                                 return;
485
486                         if (this is TemplateBuilder)
487                                 return;
488
489                         object [] atts = type.GetCustomAttributes (typeof (ParseChildrenAttribute), true);
490                         
491                         if (!typeof (IParserAccessor).IsAssignableFrom (type) && atts.Length == 0) {
492                                 isIParserAccessor = false;
493                                 childrenAsProperties = true;
494                         } else if (atts.Length > 0) {
495                                 ParseChildrenAttribute att = (ParseChildrenAttribute) atts [0];
496                                 childrenAsProperties = att.ChildrenAsProperties;
497                                 if (childrenAsProperties && att.DefaultProperty.Length != 0)
498                                         defaultPropertyBuilder = CreatePropertyBuilder (att.DefaultProperty,
499                                                                                         parser, null);
500                         }
501                 }
502
503                 public virtual bool NeedsTagInnerText ()
504                 {
505                         return false;
506                 }
507
508                 public virtual void OnAppendToParentBuilder (ControlBuilder parentBuilder)
509                 {
510                         if (defaultPropertyBuilder == null)
511                                 return;
512
513                         ControlBuilder old = defaultPropertyBuilder;
514                         defaultPropertyBuilder = null;
515                         AppendSubBuilder (old);
516                 }
517
518                 internal void SetTagName (string name)
519                 {
520                         tagName = name;
521                 }
522                 
523                 public virtual void SetTagInnerText (string text)
524                 {
525                 }
526
527                 internal string GetNextID (string proposedID)
528                 {
529                         if (proposedID != null && proposedID.Trim ().Length != 0)
530                                 return proposedID;
531
532                         return "_bctrl_" + nextID++;
533                 }
534
535                 internal virtual ControlBuilder CreateSubBuilder (string tagid,
536                                                                   Hashtable atts,
537                                                                   Type childType,
538                                                                   TemplateParser parser,
539                                                                   ILocation location)
540                 {
541                         ControlBuilder childBuilder = null;
542                         if (childrenAsProperties) {
543                                 if (defaultPropertyBuilder == null)
544                                         childBuilder = CreatePropertyBuilder (tagid, parser, atts);
545                                 else {
546                                         if (defaultPropertyBuilder.TagName == tagid) {
547                                                 // The child tag is the same what our default property name. Act as if there was
548                                                 // no default property builder, or otherwise we'll end up with invalid nested
549                                                 // builder call.
550                                                 defaultPropertyBuilder = null;
551                                                 childBuilder = CreatePropertyBuilder (tagid, parser, atts);
552                                         } else
553                                                 childBuilder = defaultPropertyBuilder.CreateSubBuilder (tagid, atts,
554                                                                                                         null, parser,
555                                                                                                         location);
556                                 }
557                                 return childBuilder;
558                         }
559
560                         if (tagName == tagid)
561                                 return null;
562                         
563                         childType = GetChildControlType (tagid, atts);
564                         if (childType == null)
565                                 return null;
566
567                         childBuilder = CreateBuilderFromType (parser, this, childType, tagid, id, atts,
568                                                               location.BeginLine, location.Filename);
569
570                         return childBuilder;
571                 }
572
573                 internal virtual object CreateInstance ()
574                 {
575                         // HtmlGenericControl, HtmlTableCell...
576                         object [] atts = type.GetCustomAttributes (typeof (ConstructorNeedsTagAttribute), true);
577                         object [] args = null;
578                         if (atts != null && atts.Length > 0) {
579                                 ConstructorNeedsTagAttribute att = (ConstructorNeedsTagAttribute) atts [0];
580                                 if (att.NeedsTag)
581                                         args = new object [] {tagName};
582                         }
583
584                         return Activator.CreateInstance (type, args);
585                 }
586
587                 internal virtual void CreateChildren (object parent) 
588                 {
589                         if (children == null || children.Count == 0)
590                                 return;
591
592                         IParserAccessor parser = parent as IParserAccessor;
593                         if (parser == null)
594                                 return;
595
596                         foreach (object o in children) {
597                                 if (o is string) {
598                                         parser.AddParsedSubObject (new LiteralControl ((string) o));
599                                 } else {
600                                         parser.AddParsedSubObject (((ControlBuilder) o).CreateInstance ());
601                                 }
602                         }
603                 }
604 #if NET_2_0
605                 [MonoTODO ("unsure, lack documentation")]
606                 public virtual object BuildObject ()
607                 {
608                         return CreateInstance ();
609                 }
610
611                 internal void ResetState()
612                 {
613                         renderIndex = 0;
614                         haveParserVariable = false;
615
616                         if (Children != null) {
617                                 foreach (object child in Children) {
618                                         ControlBuilder cb = child as ControlBuilder;
619                                         if (cb != null)
620                                                 cb.ResetState ();
621                                 }
622                         }
623                 }
624 #endif
625         }
626 }