Make maxStackSize behavior compatible with MS.
[mono.git] / mcs / class / System.Xaml / System.Xaml / XamlWriterStateManager.cs
1 //
2 // Copyright (C) 2010 Novell Inc. http://novell.com
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
11 // 
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 // 
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 //
23 using System;
24 using System.Collections.Generic;
25 using System.IO;
26 using System.Linq;
27 using System.Xml;
28
29 /*
30
31 * State transition
32
33 Unlike XmlWriter, XAML nodes are not immediately writable because object
34 output has to be delayed to be determined whether it should write
35 an attribute or an element.
36
37 ** NamespaceDeclarations
38
39 NamespaceDeclaration does not immediately participate in the state transition
40 but some write methods reject stored namespaces (e.g. WriteEndObject cannot
41 handle them). In such cases, they throw InvalidOperationException, while the
42 writer throws XamlXmlWriterException for usual state transition.
43
44 Though they still seems to affect some outputs. If a member with simple
45 value is written after a namespace, then it becomes an element, not attribute.
46
47 ** state transition
48
49 states are: Initial, ObjectStarted, MemberStarted, ValueWritten, MemberDone, End
50
51 Initial + StartObject -> ObjectStarted : push(xt)
52 ObjectStarted + StartMember -> MemberStarted : push(xm)
53 ObjectStarted + EndObject -> ObjectWritten or End : pop()
54 MemberStarted + StartObject -> ObjectStarted : push(xt)
55 MemberStarted + Value -> ValueWritten
56 MemberStarted + GetObject -> MemberDone : pop()
57 ObjectWritten + StartObject -> ObjectStarted : push(x)
58 ObjectWritten + Value -> ValueWritten : pop()
59 ObjectWritten + EndMember -> MemberDone : pop()
60 ValueWritten + StartObject -> invalid - or - ObjectStarted : push(x)
61 ValueWritten + Value -> invalid - or - ValueWritten
62 ValueWritten + EndMember -> MemberDone : pop()
63 MemberDone + EndObject -> ObjectWritten or End : pop() // xt
64 MemberDone + StartMember -> MemberStarted : push(xm)
65
66 (in XamlObjectWriter, Value must be followed by EndMember.)
67
68 */
69
70 namespace System.Xaml
71 {
72         internal class XamlWriterStateManager<TError,TNSError> : XamlWriterStateManager
73                 where TError : Exception
74                 where TNSError : Exception
75         {
76                 public XamlWriterStateManager (bool isXmlWriter)
77                         : base (isXmlWriter)
78                 {
79                 }
80
81                 public override Exception CreateError (string msg)
82                 {
83                         return (Exception) Activator.CreateInstance (typeof (TError), new object [] {msg});
84                 }
85
86                 public override Exception CreateNamespaceError (string msg)
87                 {
88                         return (Exception) Activator.CreateInstance (typeof (TNSError), new object [] {msg});
89                 }
90         }
91
92         internal enum XamlWriteState
93         {
94                 Initial,
95                 ObjectStarted,
96                 MemberStarted,
97                 ObjectWritten,
98                 ValueWritten,
99                 MemberDone,
100                 End
101         }
102
103         internal abstract class XamlWriterStateManager
104         {
105                 public XamlWriterStateManager (bool isXmlWriter)
106                 {
107                         allow_ns_at_value = isXmlWriter;
108                         allow_object_after_value = isXmlWriter;
109                         allow_parallel_values = !isXmlWriter;
110                         allow_empty_member = !isXmlWriter;
111                         allow_multiple_results = !isXmlWriter;
112                 }
113
114                 // configuration
115                 bool allow_ns_at_value, allow_object_after_value, allow_parallel_values, allow_empty_member, allow_multiple_results;
116
117                 // state
118                 XamlWriteState state = XamlWriteState.Initial;
119                 bool ns_pushed;
120                 bool accept_multiple_values; // It is PositionalParameters-specific state.
121
122                 public XamlWriteState State {
123                         get { return state; }
124                 }
125                 
126                 // FIXME: actually this property is a hack. It should preserve stacked flag values for each nested member in current tree state.
127                 public bool AcceptMultipleValues {
128                         get { return accept_multiple_values; }
129                         set { accept_multiple_values = value; }
130                 }
131
132                 public void OnClosingItem ()
133                 {
134                         // somewhat hacky state change to not reject StartMember->EndMember.
135                         if (state == XamlWriteState.MemberStarted)
136                                 state = XamlWriteState.ValueWritten;
137                 }
138
139                 public void EndMember ()
140                 {
141                         RejectNamespaces (XamlNodeType.EndMember);
142                         CheckState (XamlNodeType.EndMember);
143                         state = XamlWriteState.MemberDone;
144                 }
145
146                 public void EndObject (bool hasMoreNodes)
147                 {
148                         RejectNamespaces (XamlNodeType.EndObject);
149                         CheckState (XamlNodeType.EndObject);
150                         state = hasMoreNodes ? XamlWriteState.ObjectWritten : allow_multiple_results ? XamlWriteState.Initial : XamlWriteState.End;
151                 }
152
153                 public void GetObject ()
154                 {
155                         CheckState (XamlNodeType.GetObject);
156                         RejectNamespaces (XamlNodeType.GetObject);
157                         state = XamlWriteState.MemberDone;
158                 }
159
160                 public void StartMember ()
161                 {
162                         CheckState (XamlNodeType.StartMember);
163                         state = XamlWriteState.MemberStarted;
164                         ns_pushed = false;
165                 }
166
167                 public void StartObject ()
168                 {
169                         CheckState (XamlNodeType.StartObject);
170                         state = XamlWriteState.ObjectStarted;
171                         ns_pushed = false;
172                 }
173
174                 public void Value ()
175                 {
176                         CheckState (XamlNodeType.Value);
177                         RejectNamespaces (XamlNodeType.Value);
178                         state = XamlWriteState.ValueWritten;
179                 }
180
181                 public void Namespace ()
182                 {
183                         if (!allow_ns_at_value && (state == XamlWriteState.ValueWritten || state == XamlWriteState.ObjectStarted))
184                                 throw CreateError (String.Format ("Namespace declarations cannot be written at {0} state", state));
185                         ns_pushed = true;
186                 }
187
188                 public void NamespaceCleanedUp ()
189                 {
190                         ns_pushed = false;
191                 }
192
193                 void CheckState (XamlNodeType next)
194                 {
195                         switch (state) {
196                         case XamlWriteState.Initial:
197                                 switch (next) {
198                                 case XamlNodeType.StartObject:
199                                         return;
200                                 }
201                                 break;
202                         case XamlWriteState.ObjectStarted:
203                                 switch (next) {
204                                 case XamlNodeType.StartMember:
205                                 case XamlNodeType.EndObject:
206                                         return;
207                                 }
208                                 break;
209                         case XamlWriteState.MemberStarted:
210                                 switch (next) {
211                                 case XamlNodeType.StartObject:
212                                 case XamlNodeType.Value:
213                                 case XamlNodeType.GetObject:
214                                         return;
215                                 case XamlNodeType.EndMember:
216                                         if (allow_empty_member)
217                                                 return;
218                                         break;
219                                 }
220                                 break;
221                         case XamlWriteState.ObjectWritten:
222                                 switch (next) {
223                                 case XamlNodeType.StartObject:
224                                 case XamlNodeType.Value:
225                                 case XamlNodeType.EndMember:
226                                         return;
227                                 }
228                                 break;
229                         case XamlWriteState.ValueWritten:
230                                 switch (next) {
231                                 case XamlNodeType.Value:
232                                         if (allow_parallel_values | accept_multiple_values)
233                                                 return;
234                                         break;
235                                 case XamlNodeType.StartObject:
236                                         if (allow_object_after_value)
237                                                 return;
238                                         break;
239                                 case XamlNodeType.EndMember:
240                                         return;
241                                 }
242                                 break;
243                         case XamlWriteState.MemberDone:
244                                 switch (next) {
245                                 case XamlNodeType.StartMember:
246                                 case XamlNodeType.EndObject:
247                                         return;
248                                 }
249                                 break;
250                         }
251                         throw CreateError (String.Format ("{0} is not allowed at current state {1}", next, state));
252                 }
253                 
254                 void RejectNamespaces (XamlNodeType next)
255                 {
256                         if (ns_pushed) {
257                                 // strange, but on WriteEndMember it throws XamlXmlWriterException, while for other nodes it throws IOE.
258                                 string msg = String.Format ("Namespace declarations cannot be written before {0}", next);
259                                 if (next == XamlNodeType.EndMember)
260                                         throw CreateError (msg);
261                                 else
262                                         throw CreateNamespaceError (msg);
263                         }
264                 }
265
266                 public abstract Exception CreateError (string msg);
267                 public abstract Exception CreateNamespaceError (string msg);
268         }
269 }