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