Renamed RailsException to MonoRailException
[castle.git] / MonoRail / Castle.MonoRail.Framework / Helpers / SetOperation.cs
blob7937e00d73545c9ed38e6991afb0425ed023cbd3
1 // Copyright 2004-2007 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 is IEnumerable)
150 return ExtractType(source as IEnumerable);
152 else
154 return source.GetType();
158 private static Type ExtractType(IEnumerable source)
160 if (source == null)
162 return null;
165 IEnumerator enumerator = source.GetEnumerator();
167 if (enumerator.MoveNext())
169 return ExtractType(enumerator.Current);
172 return null;
175 private static bool IsSet(object initialSelection)
177 if (initialSelection == null)
179 return false;
182 return initialSelection.GetType() != typeof(String) &&
183 initialSelection is IEnumerable;
187 /// <summary>
188 /// Represents a set element
189 /// </summary>
190 public class SetItem
192 private readonly string value;
193 private readonly object item;
194 private readonly string text;
195 private readonly bool isSelected;
197 /// <summary>
198 /// Initializes a new instance of the <see cref="SetItem"/> class.
199 /// </summary>
200 /// <param name="item">The item.</param>
201 /// <param name="value">The value.</param>
202 /// <param name="text">The text.</param>
203 /// <param name="isSelected">if set to <c>true</c> [is selected].</param>
204 public SetItem(object item, String value, String text, bool isSelected)
206 this.item = item;
207 this.value = value;
208 this.text = text;
209 this.isSelected = isSelected;
212 /// <summary>
213 /// Gets the item.
214 /// </summary>
215 /// <value>The item.</value>
216 public object Item
218 get { return item; }
221 /// <summary>
222 /// Gets the value.
223 /// </summary>
224 /// <value>The value.</value>
225 public string Value
227 get { return value; }
230 /// <summary>
231 /// Gets the text.
232 /// </summary>
233 /// <value>The text.</value>
234 public string Text
236 get { return text; }
239 /// <summary>
240 /// Gets a value indicating whether this instance is selected.
241 /// </summary>
242 /// <value>
243 /// <c>true</c> if this instance is selected; otherwise, <c>false</c>.
244 /// </value>
245 public bool IsSelected
247 get { return isSelected; }
251 /// <summary>
252 /// Base class for set iterators
253 /// </summary>
254 public abstract class OperationState : IEnumerable, IEnumerator
256 /// <summary>
257 /// source type
258 /// </summary>
259 protected readonly Type type;
260 /// <summary>
261 /// Value getter for value
262 /// </summary>
263 protected readonly FormHelper.ValueGetter valuePropInfo;
264 /// <summary>
265 /// Value getter for text
266 /// </summary>
267 protected readonly FormHelper.ValueGetter textPropInfo;
269 /// <summary>
270 /// Format rule for text
271 /// </summary>
272 protected readonly String textFormat;
273 /// <summary>
274 /// Format rule for value
275 /// </summary>
276 protected readonly String valueFormat;
278 /// <summary>
279 /// Source enumerator
280 /// </summary>
281 protected IEnumerator enumerator;
283 /// <summary>
284 /// Initializes a new instance of the <see cref="OperationState"/> class.
285 /// </summary>
286 /// <param name="type">The type.</param>
287 /// <param name="dataSource">The data source.</param>
288 /// <param name="emptyValueCase">if set to <c>true</c> [empty value case].</param>
289 /// <param name="valueProperty">The value property.</param>
290 /// <param name="textProperty">The text property.</param>
291 /// <param name="textFormat">The text format.</param>
292 /// <param name="valueFormat">The value format.</param>
293 protected OperationState(Type type, IEnumerable dataSource,
294 bool emptyValueCase,String valueProperty, String textProperty, String textFormat, String valueFormat)
296 if (dataSource != null)
298 enumerator = dataSource.GetEnumerator();
301 this.type = type;
302 this.textFormat = textFormat;
303 this.valueFormat = valueFormat;
305 if (valueProperty != null || emptyValueCase)
307 valuePropInfo = FormHelper.ValueGetterAbstractFactory.Create(type, valueProperty); // FormHelper.GetMethod(type, valueProperty);
310 if (textProperty != null)
312 textPropInfo = FormHelper.ValueGetterAbstractFactory.Create(type, textProperty);
316 /// <summary>
317 /// Gets the target suffix.
318 /// </summary>
319 /// <value>The target suffix.</value>
320 public abstract String TargetSuffix { get; }
322 /// <summary>
323 /// Formats the text.
324 /// </summary>
325 /// <param name="value">The value to be formatted.</param>
326 /// <param name="format">The format to apply.</param>
327 protected static void FormatText(ref object value, string format)
329 if (format != null && value != null)
331 IFormattable formattable = value as IFormattable;
333 if (formattable != null)
335 value = formattable.ToString(format, null);
340 /// <summary>
341 /// Creates the item representation.
342 /// </summary>
343 /// <param name="current">The current.</param>
344 /// <returns></returns>
345 protected abstract SetItem CreateItemRepresentation(object current);
347 #region IEnumerator implementation
349 /// <summary>
350 /// Advances the enumerator to the next element of the collection.
351 /// </summary>
352 /// <returns>
353 /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
354 /// </returns>
355 /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
356 public bool MoveNext()
358 if (enumerator == null)
360 return false;
362 return enumerator.MoveNext();
365 /// <summary>
366 /// Sets the enumerator to its initial position, which is before the first element in the collection.
367 /// </summary>
368 /// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created. </exception>
369 public void Reset()
371 if (enumerator != null)
373 enumerator.Reset();
377 /// <summary>
378 /// Gets the current element in the collection.
379 /// </summary>
380 /// <value></value>
381 /// <returns>The current element in the collection.</returns>
382 /// <exception cref="T:System.InvalidOperationException">The enumerator is positioned before the first element of the collection or after the last element. </exception>
383 public object Current
385 get { return CreateItemRepresentation(enumerator.Current); }
388 #endregion
390 #region IEnumerable implementation
392 /// <summary>
393 /// Returns an enumerator that iterates through a collection.
394 /// </summary>
395 /// <returns>
396 /// An <see cref="T:System.Collections.IEnumerator"></see> object that can be used to iterate through the collection.
397 /// </returns>
398 public IEnumerator GetEnumerator()
400 return this;
403 #endregion
406 /// <summary>
407 /// Used for empty/null datasources
408 /// </summary>
409 public class NoIterationState : OperationState
411 /// <summary>
412 /// Single instance for the iterator.
413 /// </summary>
414 public static readonly NoIterationState Instance = new NoIterationState();
416 private NoIterationState() : base(null, null, false, null, null, null, null)
420 /// <summary>
421 /// Gets the target suffix.
422 /// </summary>
423 /// <value>The target suffix.</value>
424 public override string TargetSuffix
426 get { return null; }
429 /// <summary>
430 /// Creates the item representation.
431 /// </summary>
432 /// <param name="current">The current.</param>
433 /// <returns></returns>
434 protected override SetItem CreateItemRepresentation(object current)
436 throw new NotImplementedException();
440 /// <summary>
441 /// Simple iterator
442 /// </summary>
443 public class ListDataSourceState : OperationState
445 private readonly string customSuffix;
447 /// <summary>
448 /// Initializes a new instance of the <see cref="ListDataSourceState"/> class.
449 /// </summary>
450 /// <param name="type">The type.</param>
451 /// <param name="dataSource">The data source.</param>
452 /// <param name="valueProperty">The value property.</param>
453 /// <param name="textProperty">The text property.</param>
454 /// <param name="textFormat">The text format.</param>
455 /// <param name="valueFormat">The value format.</param>
456 /// <param name="customSuffix">The custom suffix.</param>
457 public ListDataSourceState(Type type, IEnumerable dataSource, String valueProperty,
458 String textProperty, String textFormat, String valueFormat, String customSuffix)
459 : base(type, dataSource, false, valueProperty, textProperty, textFormat, valueFormat)
461 this.customSuffix = customSuffix;
464 /// <summary>
465 /// Gets the target suffix.
466 /// </summary>
467 /// <value>The target suffix.</value>
468 public override string TargetSuffix
470 get { return customSuffix; }
473 /// <summary>
474 /// Creates the item representation.
475 /// </summary>
476 /// <param name="current">The current.</param>
477 /// <returns></returns>
478 protected override SetItem CreateItemRepresentation(object current)
480 object value = current;
481 object text = current;
483 if (valuePropInfo != null)
485 value = valuePropInfo.GetValue(current);
488 if (textPropInfo != null)
490 text = textPropInfo.GetValue(current);
493 FormatText(ref text, textFormat);
494 FormatText(ref value, valueFormat);
496 return new SetItem(current, value != null ? value.ToString() : String.Empty, text != null ? text.ToString() : String.Empty, false);
500 /// <summary>
501 /// Iterator for sets type same type
502 /// </summary>
503 public class SameTypeOperationState : OperationState
505 private readonly object initialSelection;
506 private readonly bool isInitialSelectionASet;
508 /// <summary>
509 /// Initializes a new instance of the <see cref="SameTypeOperationState"/> class.
510 /// </summary>
511 /// <param name="type">The type.</param>
512 /// <param name="initialSelection">The initial selection.</param>
513 /// <param name="dataSource">The data source.</param>
514 /// <param name="emptyValueCase">if set to <c>true</c> [empty value case].</param>
515 /// <param name="valueProperty">The value property.</param>
516 /// <param name="textProperty">The text property.</param>
517 /// <param name="textFormat">The text format.</param>
518 /// <param name="valueFormat">The value format.</param>
519 /// <param name="isInitialSelectionASet">if set to <c>true</c> [is initial selection A set].</param>
520 public SameTypeOperationState(Type type, object initialSelection, IEnumerable dataSource,
521 bool emptyValueCase,String valueProperty, String textProperty, String textFormat, String valueFormat, bool isInitialSelectionASet)
522 : base(type, dataSource, emptyValueCase, valueProperty, textProperty, textFormat, valueFormat)
524 this.initialSelection = initialSelection;
525 this.isInitialSelectionASet = isInitialSelectionASet;
528 /// <summary>
529 /// Gets the target suffix.
530 /// </summary>
531 /// <value>The target suffix.</value>
532 public override string TargetSuffix
534 get { return valuePropInfo == null ? "" : valuePropInfo.Name; }
537 /// <summary>
538 /// Creates the item representation.
539 /// </summary>
540 /// <param name="current">The current.</param>
541 /// <returns></returns>
542 protected override SetItem CreateItemRepresentation(object current)
544 object value = current;
545 object text = current;
547 if (valuePropInfo != null)
549 value = valuePropInfo.GetValue(current);
552 if (textPropInfo != null)
554 text = textPropInfo.GetValue(current);
557 FormatText(ref text, textFormat);
558 FormatText(ref value, valueFormat);
560 bool isSelected = FormHelper.IsPresent(value, initialSelection, valuePropInfo, isInitialSelectionASet);
562 return new SetItem(current, value != null ? value.ToString() : String.Empty, text != null ? text.ToString() : String.Empty, isSelected);
566 /// <summary>
567 /// Iterator for different types on the set
568 /// </summary>
569 public class DifferentTypeOperationState : OperationState
571 private readonly object initialSelection;
572 private readonly bool isInitialSelectionASet;
573 private readonly FormHelper.ValueGetter sourcePropInfo;
575 /// <summary>
576 /// Initializes a new instance of the <see cref="DifferentTypeOperationState"/> class.
577 /// </summary>
578 /// <param name="initialSelectionType">Initial type of the selection.</param>
579 /// <param name="dataSourceType">Type of the data source.</param>
580 /// <param name="initialSelection">The initial selection.</param>
581 /// <param name="dataSource">The data source.</param>
582 /// <param name="sourceProperty">The source property.</param>
583 /// <param name="valueProperty">The value property.</param>
584 /// <param name="textProperty">The text property.</param>
585 /// <param name="textFormat">The text format.</param>
586 /// <param name="valueFormat">The value format.</param>
587 /// <param name="isInitialSelectionASet">if set to <c>true</c> [is initial selection A set].</param>
588 public DifferentTypeOperationState(Type initialSelectionType, Type dataSourceType,
589 object initialSelection, IEnumerable dataSource,
590 String sourceProperty, String valueProperty,
591 String textProperty, String textFormat, String valueFormat, bool isInitialSelectionASet)
592 : base(dataSourceType, dataSource, false, valueProperty, textProperty, textFormat, valueFormat)
594 this.initialSelection = initialSelection;
595 this.isInitialSelectionASet = isInitialSelectionASet;
597 if (sourceProperty != null)
599 sourcePropInfo = FormHelper.ValueGetterAbstractFactory.Create(initialSelectionType, sourceProperty); // FormHelper.GetMethod(initialSelectionType, sourceProperty);
601 else if (valueProperty != null)
603 sourcePropInfo = FormHelper.ValueGetterAbstractFactory.Create(initialSelectionType, valueProperty); // FormHelper.GetMethod(initialSelectionType, valueProperty);
607 /// <summary>
608 /// Gets the target suffix.
609 /// </summary>
610 /// <value>The target suffix.</value>
611 public override string TargetSuffix
613 get { return sourcePropInfo == null ? "" : sourcePropInfo.Name; }
616 /// <summary>
617 /// Creates the item representation.
618 /// </summary>
619 /// <param name="current">The current.</param>
620 /// <returns></returns>
621 protected override SetItem CreateItemRepresentation(object current)
623 object value = current;
624 object text = current;
626 if (valuePropInfo != null)
628 value = valuePropInfo.GetValue(current);
631 if (textPropInfo != null)
633 text = textPropInfo.GetValue(current);
636 FormatText(ref text, textFormat);
637 FormatText(ref value, valueFormat);
639 bool isSelected = FormHelper.IsPresent(value, initialSelection, sourcePropInfo, isInitialSelectionASet);
641 return new SetItem(current, value != null ? value.ToString() : String.Empty, text != null ? text.ToString() : String.Empty, isSelected);