Fixing an issue with output parameters that are of type IntPtr
[castle.git] / MonoRail / Castle.MonoRail.Framework / Helpers / SetOperation.cs
bloba2beaee3bb32c965b06cfc0e91f907243c46d02d
1 // Copyright 2004-2008 Castle Project - http://www.castleproject.org/
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
15 namespace Castle.MonoRail.Framework.Helpers
17 using System;
18 using System.Collections;
19 using Castle.MonoRail.Framework.Internal;
21 /// <summary>
22 /// The SetOperation exposes an <see cref="IterateOnDataSource"/> that
23 /// extracts information from the attributes and creates a proper configured
24 /// Iterator.
25 /// <para>
26 /// It is shared by a handful of MonoRail operations related to sets.
27 /// </para>
28 /// </summary>
29 public static class SetOperation
31 /// <summary>
32 /// Combines a group of well thought rules to create
33 /// an <see cref="OperationState"/> instance.
34 /// </summary>
35 ///
36 /// <remarks>
37 /// The parameters read from the <paramref name="attributes"/> are
38 ///
39 /// <list type="bullet">
40 /// <item>
41 /// <term>value</term>
42 /// <description>The property name used to extract the value</description>
43 /// </item>
44 /// <item>
45 /// <term>text</term>
46 /// <description>The property name used to extract the display text</description>
47 /// </item>
48 /// <item>
49 /// <term>textformat</term>
50 /// <description>A format rule to apply to the text</description>
51 /// </item>
52 /// <item>
53 /// <term>valueformat</term>
54 /// <description>A format rule to apply to the value</description>
55 /// </item>
56 /// <item>
57 /// <term>suffix</term>
58 /// <description>If the types on both sets are different,
59 /// the suffix specifies a different target property</description>
60 /// </item>
61 /// <item>
62 /// <term>sourceProperty</term>
63 /// <description>
64 /// If the types on both sets are different,
65 /// the sourceProperty identifies a different source property to extract the value from.
66 /// </description>
67 /// </item>
68 ///
69 /// </list>
70 ///
71 /// </remarks>
72 ///
73 /// <param name="initialSelection">The initial selection.</param>
74 /// <param name="dataSource">The data source.</param>
75 /// <param name="attributes">The attributes.</param>
76 /// <returns></returns>
77 public static OperationState IterateOnDataSource(object initialSelection,
78 IEnumerable dataSource, IDictionary attributes)
80 // Extract necessary elements to know which "heuristic" to use
82 bool isInitialSelectionASet = IsSet(initialSelection);
84 Type initialSelectionType = ExtractType(initialSelection);
85 Type dataSourceType = ExtractType(dataSource);
87 String customSuffix = CommonUtils.ObtainEntryAndRemove(attributes, "suffix");
88 String valueProperty = CommonUtils.ObtainEntryAndRemove(attributes, "value");
89 String textProperty = CommonUtils.ObtainEntryAndRemove(attributes, "text");
90 String textFormat = CommonUtils.ObtainEntryAndRemove(attributes, "textformat");
91 String valueFormat = CommonUtils.ObtainEntryAndRemove(attributes, "valueformat");
93 bool emptyValueCase = CheckForEmpyTextValueCase(dataSourceType);
95 if (dataSourceType == null)
97 // If the dataSourceType could not be obtained
98 // then the datasource is empty or null
100 return NoIterationState.Instance;
102 else if (initialSelectionType == null)
104 if (customSuffix == null && valueProperty != null)
106 customSuffix = valueProperty;
109 return new ListDataSourceState(dataSourceType, dataSource,
110 valueProperty, textProperty, textFormat, valueFormat, customSuffix);
112 else if (initialSelectionType == dataSourceType)
114 return new SameTypeOperationState(dataSourceType,
115 initialSelection, dataSource,
116 emptyValueCase, valueProperty, textProperty, textFormat, valueFormat, isInitialSelectionASet);
118 else // types are different, most complex scenario
120 String sourceProperty = CommonUtils.ObtainEntryAndRemove(attributes, "sourceProperty");
122 return new DifferentTypeOperationState(initialSelectionType,
123 dataSourceType, initialSelection, dataSource,
124 sourceProperty, valueProperty, textProperty, textFormat, valueFormat,
125 isInitialSelectionASet);
129 private static bool CheckForEmpyTextValueCase(Type datasourceType)
131 if (typeof(Enum).IsAssignableFrom(datasourceType))
133 return true;
135 return false;
138 private static Type ExtractType(object source)
140 if (source == null)
142 return null;
144 else if (source is String)
146 return typeof(String);
148 else if (source.GetType().IsArray)
150 return source.GetType().GetElementType();
152 else if (source is IEnumerable)
154 return ExtractType(source as IEnumerable);
156 else
158 return source.GetType();
162 private static Type ExtractType(IEnumerable source)
164 if (source == null)
166 return null;
169 IEnumerator enumerator = source.GetEnumerator();
171 if (enumerator.MoveNext())
173 return ExtractType(enumerator.Current);
176 return null;
179 private static bool IsSet(object initialSelection)
181 if (initialSelection == null)
183 return false;
186 return initialSelection.GetType() != typeof(String) &&
187 initialSelection is IEnumerable;
191 /// <summary>
192 /// Represents a set element
193 /// </summary>
194 public class SetItem
196 private readonly string value;
197 private readonly object item;
198 private readonly string text;
199 private readonly bool isSelected;
201 /// <summary>
202 /// Initializes a new instance of the <see cref="SetItem"/> class.
203 /// </summary>
204 /// <param name="item">The item.</param>
205 /// <param name="value">The value.</param>
206 /// <param name="text">The text.</param>
207 /// <param name="isSelected">if set to <c>true</c> [is selected].</param>
208 public SetItem(object item, String value, String text, bool isSelected)
210 this.item = item;
211 this.value = value;
212 this.text = text;
213 this.isSelected = isSelected;
216 /// <summary>
217 /// Gets the item.
218 /// </summary>
219 /// <value>The item.</value>
220 public object Item
222 get { return item; }
225 /// <summary>
226 /// Gets the value.
227 /// </summary>
228 /// <value>The value.</value>
229 public string Value
231 get { return value; }
234 /// <summary>
235 /// Gets the text.
236 /// </summary>
237 /// <value>The text.</value>
238 public string Text
240 get { return text; }
243 /// <summary>
244 /// Gets a value indicating whether this instance is selected.
245 /// </summary>
246 /// <value>
247 /// <c>true</c> if this instance is selected; otherwise, <c>false</c>.
248 /// </value>
249 public bool IsSelected
251 get { return isSelected; }
255 /// <summary>
256 /// Base class for set iterators
257 /// </summary>
258 public abstract class OperationState : IEnumerable, IEnumerator
260 /// <summary>
261 /// source type
262 /// </summary>
263 protected readonly Type type;
264 /// <summary>
265 /// Value getter for value
266 /// </summary>
267 protected readonly FormHelper.ValueGetter valuePropInfo;
268 /// <summary>
269 /// Value getter for text
270 /// </summary>
271 protected readonly FormHelper.ValueGetter textPropInfo;
273 /// <summary>
274 /// Format rule for text
275 /// </summary>
276 protected readonly String textFormat;
277 /// <summary>
278 /// Format rule for value
279 /// </summary>
280 protected readonly String valueFormat;
282 /// <summary>
283 /// Source enumerator
284 /// </summary>
285 protected IEnumerator enumerator;
287 /// <summary>
288 /// Initializes a new instance of the <see cref="OperationState"/> class.
289 /// </summary>
290 /// <param name="type">The type.</param>
291 /// <param name="dataSource">The data source.</param>
292 /// <param name="emptyValueCase">if set to <c>true</c> [empty value case].</param>
293 /// <param name="valueProperty">The value property.</param>
294 /// <param name="textProperty">The text property.</param>
295 /// <param name="textFormat">The text format.</param>
296 /// <param name="valueFormat">The value format.</param>
297 protected OperationState(Type type, IEnumerable dataSource,
298 bool emptyValueCase,String valueProperty, String textProperty, String textFormat, String valueFormat)
300 if (dataSource != null)
302 enumerator = dataSource.GetEnumerator();
305 this.type = type;
306 this.textFormat = textFormat;
307 this.valueFormat = valueFormat;
309 if (valueProperty != null || emptyValueCase)
311 valuePropInfo = FormHelper.ValueGetterAbstractFactory.Create(type, valueProperty);
314 if (textProperty != null)
316 textPropInfo = FormHelper.ValueGetterAbstractFactory.Create(type, textProperty);
320 /// <summary>
321 /// Gets the target suffix.
322 /// </summary>
323 /// <value>The target suffix.</value>
324 public abstract String TargetSuffix { get; }
326 /// <summary>
327 /// Formats the text.
328 /// </summary>
329 /// <param name="value">The value to be formatted.</param>
330 /// <param name="format">The format to apply.</param>
331 protected static void FormatText(ref object value, string format)
333 if (format != null && value != null)
335 IFormattable formattable = value as IFormattable;
337 if (formattable != null)
339 value = formattable.ToString(format, null);
344 /// <summary>
345 /// Creates the item representation.
346 /// </summary>
347 /// <param name="current">The current.</param>
348 /// <returns></returns>
349 protected abstract SetItem CreateItemRepresentation(object current);
351 #region IEnumerator implementation
353 /// <summary>
354 /// Advances the enumerator to the next element of the collection.
355 /// </summary>
356 /// <returns>
357 /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
358 /// </returns>
359 /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
360 public bool MoveNext()
362 if (enumerator == null)
364 return false;
366 return enumerator.MoveNext();
369 /// <summary>
370 /// Sets the enumerator to its initial position, which is before the first element in the collection.
371 /// </summary>
372 /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
373 public void Reset()
375 if (enumerator != null)
377 enumerator.Reset();
381 /// <summary>
382 /// Gets the current element in the collection.
383 /// </summary>
384 /// <value></value>
385 /// <returns>The current element in the collection.</returns>
386 /// <exception cref="T:System.InvalidOperationException">The enumerator is positioned before the first element of the collection or after the last element. </exception>
387 public object Current
389 get { return CreateItemRepresentation(enumerator.Current); }
392 #endregion
394 #region IEnumerable implementation
396 /// <summary>
397 /// Returns an enumerator that iterates through a collection.
398 /// </summary>
399 /// <returns>
400 /// An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
401 /// </returns>
402 public IEnumerator GetEnumerator()
404 return this;
407 #endregion
410 /// <summary>
411 /// Used for empty/null datasources
412 /// </summary>
413 public class NoIterationState : OperationState
415 /// <summary>
416 /// Single instance for the iterator.
417 /// </summary>
418 public static readonly NoIterationState Instance = new NoIterationState();
420 private NoIterationState() : base(null, null, false, null, null, null, null)
424 /// <summary>
425 /// Gets the target suffix.
426 /// </summary>
427 /// <value>The target suffix.</value>
428 public override string TargetSuffix
430 get { return null; }
433 /// <summary>
434 /// Creates the item representation.
435 /// </summary>
436 /// <param name="current">The current.</param>
437 /// <returns></returns>
438 protected override SetItem CreateItemRepresentation(object current)
440 throw new NotImplementedException();
444 /// <summary>
445 /// Simple iterator
446 /// </summary>
447 public class ListDataSourceState : OperationState
449 private readonly string customSuffix;
451 /// <summary>
452 /// Initializes a new instance of the <see cref="ListDataSourceState"/> class.
453 /// </summary>
454 /// <param name="type">The type.</param>
455 /// <param name="dataSource">The data source.</param>
456 /// <param name="valueProperty">The value property.</param>
457 /// <param name="textProperty">The text property.</param>
458 /// <param name="textFormat">The text format.</param>
459 /// <param name="valueFormat">The value format.</param>
460 /// <param name="customSuffix">The custom suffix.</param>
461 public ListDataSourceState(Type type, IEnumerable dataSource, String valueProperty,
462 String textProperty, String textFormat, String valueFormat, String customSuffix)
463 : base(type, dataSource, false, valueProperty, textProperty, textFormat, valueFormat)
465 this.customSuffix = customSuffix;
468 /// <summary>
469 /// Gets the target suffix.
470 /// </summary>
471 /// <value>The target suffix.</value>
472 public override string TargetSuffix
474 get { return customSuffix; }
477 /// <summary>
478 /// Creates the item representation.
479 /// </summary>
480 /// <param name="current">The current.</param>
481 /// <returns></returns>
482 protected override SetItem CreateItemRepresentation(object current)
484 object value = current;
485 object text = current;
487 if (valuePropInfo != null)
489 value = valuePropInfo.GetValue(current);
492 if (textPropInfo != null)
494 text = textPropInfo.GetValue(current);
497 FormatText(ref text, textFormat);
498 FormatText(ref value, valueFormat);
500 return new SetItem(current, value != null ? value.ToString() : String.Empty, text != null ? text.ToString() : String.Empty, false);
504 /// <summary>
505 /// Iterator for sets type same type
506 /// </summary>
507 public class SameTypeOperationState : OperationState
509 private readonly object initialSelection;
510 private readonly bool isInitialSelectionASet;
512 /// <summary>
513 /// Initializes a new instance of the <see cref="SameTypeOperationState"/> class.
514 /// </summary>
515 /// <param name="type">The type.</param>
516 /// <param name="initialSelection">The initial selection.</param>
517 /// <param name="dataSource">The data source.</param>
518 /// <param name="emptyValueCase">if set to <c>true</c> [empty value case].</param>
519 /// <param name="valueProperty">The value property.</param>
520 /// <param name="textProperty">The text property.</param>
521 /// <param name="textFormat">The text format.</param>
522 /// <param name="valueFormat">The value format.</param>
523 /// <param name="isInitialSelectionASet">if set to <c>true</c> [is initial selection A set].</param>
524 public SameTypeOperationState(Type type, object initialSelection, IEnumerable dataSource,
525 bool emptyValueCase,String valueProperty, String textProperty, String textFormat, String valueFormat, bool isInitialSelectionASet)
526 : base(type, dataSource, emptyValueCase, valueProperty, textProperty, textFormat, valueFormat)
528 this.initialSelection = initialSelection;
529 this.isInitialSelectionASet = isInitialSelectionASet;
532 /// <summary>
533 /// Gets the target suffix.
534 /// </summary>
535 /// <value>The target suffix.</value>
536 public override string TargetSuffix
538 get { return valuePropInfo == null ? "" : valuePropInfo.Name; }
541 /// <summary>
542 /// Creates the item representation.
543 /// </summary>
544 /// <param name="current">The current.</param>
545 /// <returns></returns>
546 protected override SetItem CreateItemRepresentation(object current)
548 object value = current;
549 object text = current;
551 if (valuePropInfo != null)
553 value = valuePropInfo.GetValue(current);
556 if (textPropInfo != null)
558 text = textPropInfo.GetValue(current);
561 FormatText(ref text, textFormat);
562 FormatText(ref value, valueFormat);
564 bool isSelected = FormHelper.IsPresent(value, initialSelection, valuePropInfo, isInitialSelectionASet);
566 return new SetItem(current, value != null ? value.ToString() : String.Empty, text != null ? text.ToString() : String.Empty, isSelected);
570 /// <summary>
571 /// Iterator for different types on the set
572 /// </summary>
573 public class DifferentTypeOperationState : OperationState
575 private readonly object initialSelection;
576 private readonly bool isInitialSelectionASet;
577 private readonly FormHelper.ValueGetter sourcePropInfo;
579 /// <summary>
580 /// Initializes a new instance of the <see cref="DifferentTypeOperationState"/> class.
581 /// </summary>
582 /// <param name="initialSelectionType">Initial type of the selection.</param>
583 /// <param name="dataSourceType">Type of the data source.</param>
584 /// <param name="initialSelection">The initial selection.</param>
585 /// <param name="dataSource">The data source.</param>
586 /// <param name="sourceProperty">The source property.</param>
587 /// <param name="valueProperty">The value property.</param>
588 /// <param name="textProperty">The text property.</param>
589 /// <param name="textFormat">The text format.</param>
590 /// <param name="valueFormat">The value format.</param>
591 /// <param name="isInitialSelectionASet">if set to <c>true</c> [is initial selection A set].</param>
592 public DifferentTypeOperationState(Type initialSelectionType, Type dataSourceType,
593 object initialSelection, IEnumerable dataSource,
594 String sourceProperty, String valueProperty,
595 String textProperty, String textFormat, String valueFormat, bool isInitialSelectionASet)
596 : base(dataSourceType, dataSource, false, valueProperty, textProperty, textFormat, valueFormat)
598 this.initialSelection = initialSelection;
599 this.isInitialSelectionASet = isInitialSelectionASet;
601 if (sourceProperty != null)
603 sourcePropInfo = FormHelper.ValueGetterAbstractFactory.Create(initialSelectionType, sourceProperty); // FormHelper.GetMethod(initialSelectionType, sourceProperty);
605 else if (valueProperty != null)
607 sourcePropInfo = FormHelper.ValueGetterAbstractFactory.Create(initialSelectionType, valueProperty); // FormHelper.GetMethod(initialSelectionType, valueProperty);
611 /// <summary>
612 /// Gets the target suffix.
613 /// </summary>
614 /// <value>The target suffix.</value>
615 public override string TargetSuffix
617 get { return sourcePropInfo == null ? "" : sourcePropInfo.Name; }
620 /// <summary>
621 /// Creates the item representation.
622 /// </summary>
623 /// <param name="current">The current.</param>
624 /// <returns></returns>
625 protected override SetItem CreateItemRepresentation(object current)
627 object value = current;
628 object text = current;
630 if (valuePropInfo != null)
632 value = valuePropInfo.GetValue(current);
635 if (textPropInfo != null)
637 text = textPropInfo.GetValue(current);
640 FormatText(ref text, textFormat);
641 FormatText(ref value, valueFormat);
643 bool isSelected = FormHelper.IsPresent(value, initialSelection, sourcePropInfo, isInitialSelectionASet);
645 return new SetItem(current, value != null ? value.ToString() : String.Empty, text != null ? text.ToString() : String.Empty, isSelected);