Merge pull request #2962 from marek-safar/referencesource-submodule-move
[mono.git] / mcs / class / referencesource / System.Activities.Presentation / System.Activities.Presentation / System / Activities / Presentation / Validation / ValidationService.cs
1 // <copyright>
2 //   Copyright (c) Microsoft Corporation.  All rights reserved.
3 // </copyright>
4
5 namespace System.Activities.Presentation.Validation
6 {
7     using System.Activities.Expressions;
8     using System.Activities.Presentation.Model;
9     using System.Activities.Presentation.Services;
10     using System.Activities.Presentation.View;
11     using System.Activities.Validation;
12     using System.Collections.Generic;
13     using System.Diagnostics.CodeAnalysis;
14     using System.Globalization;
15     using System.Reflection;
16     using System.Runtime;
17     using System.ServiceModel.Activities;
18     using System.Text;
19     using System.Threading;
20     using System.Windows.Threading;
21     using Microsoft.Activities.Presentation.Xaml;
22     using Microsoft.Win32;
23
24     [Fx.Tag.XamlVisible(false)]
25     public class ValidationService
26     {
27         static PropertyInfo parentPropertyInfo;
28
29         Dictionary<Type, IValidationErrorSourceLocator> validationErrorSourceLocators;
30         List<Guid> acquiredObjectReferences;
31
32         EditingContext context;
33         ModelService modelService;
34         ModelSearchServiceImpl modelSearchService;
35         ModelTreeManager modelTreeManager;
36         WorkflowViewService viewService;
37         IValidationErrorService errorService;
38         ObjectReferenceService objectReferenceService;
39         TaskDispatcher validationTaskDispatcher;
40         ValidationSynchronizer validationSynchronizer;
41
42         // Dictionary which maps the object to their error messages and indicators
43         // NOTE: Valid objects do not appear in this dictionary,
44         // only elements which violated a constraint (Errors or Warnings or Child Validation Issues)
45         Dictionary<object, ValidationErrorState> validationErrors;
46
47         // Attached properties for error visuals
48         AttachedProperty<ValidationState> validationStateProperty;
49         AttachedProperty<string> validationMessageProperty;
50
51         internal event EventHandler ValidationCompleted;
52
53         internal class ErrorsMarkedEventArgs : EventArgs
54         {
55             ICollection<ValidationError> errors;
56             ValidationReason reason;
57             ModelTreeManager modelTreeManager;
58             EditingContext context;
59
60             public ErrorsMarkedEventArgs(ICollection<ValidationError> errors,
61                 ValidationReason reason,
62                 ModelTreeManager modelTreeManager,
63                 EditingContext context)
64             {
65                 this.errors = errors;
66                 this.reason = reason;
67                 this.modelTreeManager = modelTreeManager;
68                 this.context = context;
69             }
70
71             public ICollection<ValidationError> Errors
72             {
73                 get { return this.errors; }
74             }
75
76             public ValidationReason Reason
77             {
78                 get { return this.reason; }
79             }
80
81             public ModelTreeManager ModelTreeManager
82             {
83                 get { return this.modelTreeManager; }
84             }
85
86             public bool Handled
87             {
88                 get;
89                 set;
90             }
91
92             public EditingContext Context
93             {
94                 get { return this.context; }
95             }
96         }
97
98         internal event EventHandler<ErrorsMarkedEventArgs> ErrorsMarked;
99
100         DynamicActivity dynamicActivityWrapper;
101
102         ValidationSettings settings;
103
104         bool isValidationDisabled = false;
105         const string ValidationRegKeyName = "DisableValidateOnModelItemChanged";
106         const string ValidationRegKeyInitialPath = "Software\\Microsoft\\.NETFramework\\";
107         [ThreadStatic]
108         static StringBuilder errorBuilder;
109
110         private static StringBuilder ErrorBuilder
111         {
112             get
113             {
114                 if (errorBuilder == null)
115                 {
116                     errorBuilder = new StringBuilder();
117                 }
118                 return errorBuilder;
119             }
120         }
121
122         public ValidationService(EditingContext context)
123             : this(context, new TaskDispatcher())
124         {
125         }
126
127         internal ValidationService(EditingContext context, TaskDispatcher validationTaskDispatcher)
128         {
129             Fx.Assert(validationTaskDispatcher != null, "validationTaskDispatcher cannot be null.");
130             this.validationTaskDispatcher = validationTaskDispatcher;
131             this.context = context;
132             this.settings = new ValidationSettings { SkipValidatingRootConfiguration = true };
133             this.context.Services.Subscribe<ModelService>(new SubscribeServiceCallback<ModelService>(OnModelServiceAvailable));
134             this.context.Services.Subscribe<ModelSearchService>(new SubscribeServiceCallback<ModelSearchService>(OnModelSearchServiceAvailable));
135             this.context.Services.Subscribe<ObjectReferenceService>(new SubscribeServiceCallback<ObjectReferenceService>(OnObjectReferenceServiceAvailable));
136             this.context.Services.Subscribe<ModelTreeManager>(new SubscribeServiceCallback<ModelTreeManager>(OnModelTreeManagerAvailable));
137             this.context.Services.Subscribe<IValidationErrorService>(new SubscribeServiceCallback<IValidationErrorService>(OnErrorServiceAvailable));
138             this.context.Services.Subscribe<AttachedPropertiesService>(new SubscribeServiceCallback<AttachedPropertiesService>(OnAttachedPropertiesServiceAvailable));
139             AssemblyName currentAssemblyName = Assembly.GetExecutingAssembly().GetName();
140             StringBuilder validationKeyPath = new StringBuilder(90);
141             validationKeyPath.Append(ValidationRegKeyInitialPath);
142             validationKeyPath.AppendFormat("{0}{1}{2}", "v", currentAssemblyName.Version.ToString(), "\\");
143             validationKeyPath.Append(currentAssemblyName.Name);
144
145             RegistryKey validationRegistryKey = Registry.CurrentUser.OpenSubKey(validationKeyPath.ToString());
146             if (validationRegistryKey != null)
147             {
148                 object value = validationRegistryKey.GetValue(ValidationRegKeyName);
149
150                 this.isValidationDisabled = (value != null && string.Equals("1", value.ToString()));
151
152                 validationRegistryKey.Close();
153             }
154         }
155
156         private ValidationSynchronizer ValidationSynchronizer
157         {
158             get
159             {
160                 if (this.validationSynchronizer == null)
161                 {
162                     if (DesignerConfigurationServiceUtilities.IsBackgroundValidationEnabled(context))
163                     {
164                         this.validationSynchronizer = new BackgroundValidationSynchronizer<Tuple<ValidationReason, ValidationResults, Exception>>(validationTaskDispatcher, this.CoreValidationWork, this.OnValidationWorkCompleted);
165                     }
166                     else
167                     {
168                         this.validationSynchronizer = new ForegroundValidationSynchronizer<Tuple<ValidationReason, ValidationResults, Exception>>(validationTaskDispatcher, this.CoreValidationWork, this.OnValidationWorkCompleted);
169                     }
170                 }
171
172                 return this.validationSynchronizer;
173             }
174         }
175
176         internal DynamicActivity DynamicActivityWrapper
177         {
178             get
179             {
180                 if (null == this.dynamicActivityWrapper)
181                 {
182                     this.dynamicActivityWrapper = new DynamicActivity();
183                 }
184                 return this.dynamicActivityWrapper;
185             }
186         }
187
188         public ValidationSettings Settings
189         {
190             get
191             {
192                 return this.settings;
193             }
194         }
195
196         WorkflowViewService ViewService
197         {
198             get
199             {
200                 if (null == this.viewService)
201                 {
202                     this.viewService = (WorkflowViewService)this.context.Services.GetService<ViewService>();
203                 }
204                 return this.viewService;
205             }
206         }
207
208         void OnAttachedPropertiesServiceAvailable(AttachedPropertiesService attachedPropertiesService)
209         {
210             this.validationStateProperty = new AttachedProperty<ValidationState>()
211             {
212                 Getter = (modelItem) => GetValidationState(modelItem),
213                 Name = "ValidationState",
214                 OwnerType = typeof(object)
215             };
216
217             attachedPropertiesService.AddProperty(this.validationStateProperty);
218
219             this.validationMessageProperty = new AttachedProperty<string>()
220             {
221                 Getter = (modelItem) => GetValidationMessage(modelItem),
222                 Name = "ValidationMessage",
223                 OwnerType = typeof(object)
224             };
225
226             attachedPropertiesService.AddProperty(this.validationMessageProperty);
227         }
228
229         ValidationState GetValidationState(ModelItem modelItem)
230         {
231             ValidationState validationState = ValidationState.Valid;
232             ValidationErrorState validationError = GetValidationError(modelItem);
233
234             if (validationError != null)
235             {
236                 validationState = validationError.ValidationState;
237             }
238             return validationState;
239         }
240
241         string GetValidationMessage(ModelItem modelItem)
242         {
243             string errorMessage = string.Empty;
244             ValidationErrorState validationError = GetValidationError(modelItem);
245
246             if (validationError != null)
247             {
248                 if (validationError.ErrorMessages != null)
249                 {
250                     ValidationService.ErrorBuilder.Clear();
251                     foreach (string message in validationError.ErrorMessages)
252                     {
253                         ValidationService.ErrorBuilder.AppendLine(message.Trim());
254                     }
255                     errorMessage = ValidationService.ErrorBuilder.ToString().Trim();
256                 }
257             }
258             return errorMessage;
259         }
260
261         ValidationErrorState GetValidationError(ModelItem modelItem)
262         {
263             ValidationErrorState validationError = null;
264             this.ValidationErrors.TryGetValue(modelItem.GetCurrentValue(), out validationError);
265             return validationError;
266         }
267
268         void OnModelServiceAvailable(ModelService modelService)
269         {
270             if (modelService != null)
271             {
272                 this.modelService = modelService;
273             }
274         }
275
276         void OnModelSearchServiceAvailable(ModelSearchService modelSearchService)
277         {
278             if (modelSearchService != null)
279             {
280                 this.modelSearchService = modelSearchService as ModelSearchServiceImpl;
281             }
282         }
283
284         void OnObjectReferenceServiceAvailable(ObjectReferenceService objectReferenceService)
285         {
286             if (objectReferenceService != null)
287             {
288                 this.objectReferenceService = objectReferenceService;
289             }
290         }
291
292         void OnModelTreeManagerAvailable(ModelTreeManager modelTreeManager)
293         {
294             if (modelTreeManager != null)
295             {
296                 this.modelTreeManager = modelTreeManager;
297             }
298         }
299
300         void OnErrorServiceAvailable(IValidationErrorService errorService)
301         {
302             if (errorService != null)
303             {
304                 this.errorService = errorService;
305                 if (this.isValidationDisabled)
306                 {
307                     this.errorService.ShowValidationErrors(new List<ValidationErrorInfo> { new ValidationErrorInfo(new ValidationError(SR.ValidationDisabledWarning, true)) });
308                 }
309             }
310         }
311
312         public void ValidateWorkflow()
313         {
314             ValidateWorkflow(ValidationReason.Unknown);
315         }
316
317         private ValidationRoot GetRootElement()
318         {
319             Activity rootElement = null;
320
321             Fx.Assert(this.modelService != null, "ModelService is null."); // ModelService should not be null
322
323             ModelItem rootItem = this.modelService.Root;
324             object root = rootItem.GetCurrentValue();
325             // special case for WorkflowService - it will be returned directly
326             WorkflowService workflowService = root as WorkflowService;
327             if (workflowService != null)
328             {
329                 return new ValidationRoot(workflowService);
330             }
331             //special case for ActivityBuilder - its will be converted to a DynamicActivity before validation.
332             ActivityBuilder activityBuilder = root as ActivityBuilder;
333             if (activityBuilder != null)
334             {
335                 ActivityBuilderExtensions.ConvertActivityBuilderToDynamicActivity(activityBuilder, this.DynamicActivityWrapper);
336                 rootElement = this.DynamicActivityWrapper;
337             }
338             else
339             {
340                 rootElement = rootItem.GetRootActivity();
341             }
342
343             IList<AssemblyReference> references;
344             IList<string> namespaces = NamespaceHelper.GetTextExpressionNamespaces(root, out references);
345             NamespaceHelper.SetTextExpressionNamespaces(rootElement, namespaces, references);
346
347             if (rootElement != null)
348             {
349                 return new ValidationRoot(rootElement);
350             }
351             else
352             {
353                 return null;
354             }
355         }
356
357         internal void ValidateWorkflow(ValidationReason reason)
358         {
359             if (this.isValidationDisabled)
360             {
361                 return;
362             }
363
364             this.validationTaskDispatcher.DispatchWorkOnUIThread(DispatcherPriority.ApplicationIdle, new Action(() =>
365                 {
366                     this.ValidationSynchronizer.Validate(reason);
367                 }));
368         }
369
370         internal void DeactivateValidation()
371         {
372             this.ValidationSynchronizer.DeactivateValidation();
373         }
374
375         internal void ActivateValidation()
376         {
377             this.ValidationSynchronizer.ActivateValidation();
378         }
379
380         [SuppressMessage(FxCop.Category.Design, FxCop.Rule.DoNotCatchGeneralExceptionTypes)]
381         internal Tuple<ValidationReason, ValidationResults, Exception> CoreValidationWork(ValidationReason reason, CancellationToken cancellationToken)
382         {
383             this.settings.CancellationToken = cancellationToken;
384             ValidationResults results = null;
385             Exception exception = null;
386             try
387             {
388                 ValidationRoot rootElement = this.GetRootElement();
389                 if (rootElement != null)
390                 {
391                     results = rootElement.Validate(this.Settings);
392                 }
393             }
394             catch (Exception e)
395             {
396                 if (Fx.IsFatal(e) || e is OperationCanceledException)
397                 {
398                     throw;
399                 }
400
401                 exception = e;
402             }
403
404             return Tuple.Create(reason, results, exception);
405         }
406
407         private void OnValidationWorkCompleted(Tuple<ValidationReason, ValidationResults, Exception> input)
408         {
409             ValidationReason reason = input.Item1;
410             ValidationResults results = input.Item2;
411             Exception exception = input.Item3;
412
413             Fx.Assert(results != null ^ exception != null, "result and exception should not both be null");
414
415             bool needsToMarkValidationErrors = false;
416             ValidationErrorInfo validationErrorInfo = null;
417             if (exception != null)
418             {
419                 ModelItem rootModelItem = this.modelService.Root;
420                 Activity rootActivity = rootModelItem.GetRootActivity();
421
422                 if (rootActivity != null)
423                 {
424                     // We don't want any crash propagating from here as it causes VS to crash.
425                     if (!this.ValidationErrors.ContainsKey(rootActivity))
426                     {
427                         ValidationErrorState validationError = new ValidationErrorState(new List<string>(), ValidationState.Error);
428                         this.ValidationErrors.Add(rootActivity, validationError);
429                     }
430                     else
431                     {
432                         this.ValidationErrors[rootActivity].ValidationState = ValidationState.Error;
433                     }
434
435                     this.ValidationErrors[rootActivity].ErrorMessages.Add(exception.ToString());
436
437                     // Notify an update to the attached properties
438                     this.NotifyValidationPropertiesChanged(rootModelItem);
439                 }
440
441                 validationErrorInfo = new ValidationErrorInfo(exception.ToString());
442                 needsToMarkValidationErrors = true;
443             }
444
445             DesignerPerfEventProvider perfProvider = this.context.Services.GetService<DesignerPerfEventProvider>();
446             perfProvider.WorkflowDesignerValidationStart();
447
448             List<ValidationError> validationErrors = null;
449             if (results != null)
450             {
451                 validationErrors = new List<ValidationError>(results.Errors);
452                 validationErrors.AddRange(results.Warnings);
453                 Activity rootActivity = this.modelService.Root.GetRootActivity();
454                 needsToMarkValidationErrors = this.MarkErrors(validationErrors, reason, rootActivity);
455             }
456
457             if (this.errorService != null && needsToMarkValidationErrors) // Error service could be null if no implementation has been provided
458             {
459                 List<ValidationErrorInfo> errors = new List<ValidationErrorInfo>();
460
461                 if (validationErrors != null)
462                 {
463                     foreach (ValidationError validationError in validationErrors)
464                     {
465                         Activity currentActivity = validationError.Source;
466                         ValidationErrorInfo error = new ValidationErrorInfo(validationError);
467
468                         // The acquired activity reference will be release in the Main AppDomain when it clear the error list
469                         if (validationError.SourceDetail != null)
470                         {
471                             error.SourceReferenceId = this.objectReferenceService.AcquireObjectReference(validationError.SourceDetail);
472                         }
473                         else if (validationError.Source != null)
474                         {
475                             error.SourceReferenceId = this.objectReferenceService.AcquireObjectReference(validationError.Source);
476                         }
477                         else
478                         {
479                             error.SourceReferenceId = Guid.Empty;
480                         }
481                         errors.Add(error);
482                     }
483                 }
484
485                 if (validationErrorInfo != null)
486                 {
487                     errors.Add(validationErrorInfo);
488                 }
489
490                 foreach (Guid acquiredObjectReference in this.AcquiredObjectReferences)
491                 {
492                     this.objectReferenceService.ReleaseObjectReference(acquiredObjectReference);
493                 }
494
495                 this.AcquiredObjectReferences.Clear();
496
497                 foreach (ValidationErrorInfo error in errors)
498                 {
499                     if (error.SourceReferenceId != Guid.Empty)
500                     {
501                         this.AcquiredObjectReferences.Add(error.SourceReferenceId);
502                     }
503                 }
504
505                 this.errorService.ShowValidationErrors(errors);
506             }
507
508             perfProvider.WorkflowDesignerValidationEnd();
509             this.OnValidationCompleted();
510         }
511
512         protected virtual void OnValidationCompleted()
513         {
514             if (this.ValidationCompleted != null)
515             {
516                 this.ValidationCompleted(this, new EventArgs());
517             }
518         }
519         
520         //  Find model item and properly create it if necessary.
521         internal static ModelItem FindModelItem(ModelTreeManager modelTreeManager, object sourceDetail)
522         {
523             if (sourceDetail == null)
524             {
525                 return null;
526             }
527
528             Fx.Assert(modelTreeManager != null, "modelTreeManager != null");
529
530             Activity element = sourceDetail as Activity;
531             object errorTarget = sourceDetail;
532
533             // if source detail is not an Activity, we just expand the model tree to search it.
534             if (element == null)
535             {
536                 return ModelTreeManager.FindFirst(modelTreeManager.Root, (modelItem) => (modelItem.GetCurrentValue() == errorTarget));
537             }
538             else
539             {
540                 return FindActivityModelItem(modelTreeManager, element);
541             }
542         }
543         
544         internal static Activity GetParent(Activity childActivity)
545         {
546             // Obtaining the parent from childActivity using (private) reflection.
547             if (parentPropertyInfo == null)
548             {
549                 parentPropertyInfo = typeof(Activity).GetProperty("Parent", BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic);
550             }
551             Fx.Assert(parentPropertyInfo != null, "Activity.Parent is not defined");
552             return parentPropertyInfo.GetValue(childActivity, null) as Activity;
553         }
554
555         // Get the parent chain of this activity.
556         // Can't use GetParentChain activity because it can only be used in a Constraint.
557         internal static List<Activity> GetParentChain(Activity activity)
558         {
559             List<Activity> parentChain = new List<Activity>();
560             while (activity != null)
561             {
562                 activity = GetParent(activity);
563                 if (activity != null)
564                 {
565                     parentChain.Add(activity);
566                 }
567             }
568             return parentChain;
569         }
570
571         private List<object> GetParentChainWithSource(Activity activity)
572         {
573             List<object> parentChain = new List<object>();
574             parentChain.Add(activity);
575             while (activity != null)
576             {
577                 activity = GetParent(activity);
578                 if (activity != null)
579                 {
580                     IValidationErrorSourceLocator validationErrorSourceLocator = this.GetValidationErrorSourceLocator(activity.GetType());
581                     if (validationErrorSourceLocator != null)
582                     {
583                         validationErrorSourceLocator.ReplaceParentChainWithSource(activity, parentChain);
584                     }
585                     else
586                     {
587                         parentChain.Add(activity);
588                     }
589                 }
590             }
591
592             parentChain.RemoveAt(0);
593             return parentChain;
594         }
595
596         // Mark all the errors including their parent chains
597         private bool MarkErrors(ICollection<ValidationError> errors, ValidationReason reason, Activity rootActivity)
598         {
599             // Clear the previous errors/warnings and update the visuals
600             ClearErrors();
601             Fx.Assert(this.modelTreeManager != null, "ModelTreeManager is null."); // ModelTreeManager should not be null
602
603             if (this.HandleErrorsMarked(errors, reason))
604             {
605                 return false;
606             }
607
608             // Iterate through the new violation list and mark errors/warnings
609             foreach (ValidationError error in errors)
610             {
611                 if (error.Source != null)
612                 {
613                     List<object> errorSourcePath = this.GetValidationErrorSourcePath(error.Source, error.SourceDetail);
614                     MarkError(error, errorSourcePath);
615                 }
616                 else if (error.SourceDetail != null && error.SourceDetail is Receive)
617                 {
618                     // special-case:
619                     // WorkflowService.Validate() may produce ValidationError { isWarning = true, Source = null, SourceDetail = Receive activity }
620
621                     List<object> errorSourcePath = this.GetValidationErrorSourcePath((Activity)error.SourceDetail, null);
622                     MarkError(error, errorSourcePath);
623                 }
624                 else if (rootActivity != null)
625                 {
626                     List<object> errorSourcePath = this.GetValidationErrorSourcePath(rootActivity, error.SourceDetail);
627                     MarkError(error, errorSourcePath);
628                 }
629             }
630
631             return true;
632         }
633
634         // Mark a single error including its parent chain
635         private void MarkError(ValidationError validationError, List<object> errorSourcePath)
636         {
637             object errorSource = errorSourcePath[0];
638             this.MarkCulprit(errorSource, validationError);
639
640             // Intentionally skipping the zeroth errorSourcePath because that is the culprit and is marked above.
641             for (int errorSourcePathIndex = 1; errorSourcePathIndex < errorSourcePath.Count; errorSourcePathIndex++)
642             {
643                 this.MarkParent(validationError, errorSourcePath[errorSourcePathIndex]);
644             }
645
646             foreach (object parent in this.GetParentChainWithSource(validationError.Source))
647             {
648                 this.MarkParent(validationError, parent);
649             }
650         }
651
652         // Mark a single error on the culprit
653         private void MarkCulprit(object errorSource, ValidationError validationError)
654         {
655             ValidationErrorState currentError;
656             if (!this.ValidationErrors.TryGetValue(errorSource, out currentError))
657             {
658                 currentError = new ValidationErrorState(new List<string>(), ValidationState.Valid);
659                 this.ValidationErrors.Add(errorSource, currentError);
660             }
661             MergeValidationError(currentError, validationError);
662             this.NotifyValidationPropertiesChanged(errorSource);
663         }
664
665         // Mark a single "child has an error" on a parent
666         private void MarkParent(ValidationError validationError, object errorParent)
667         {
668             ValidationState childValidationState = GetValidationState(validationError);
669             ValidationErrorState currentError;
670             if (!this.ValidationErrors.TryGetValue(errorParent, out currentError))
671             {
672                 currentError = ChildInvalidError();
673                 this.ValidationErrors.Add(errorParent, currentError);
674             }
675
676             if (currentError.ValidationState < childValidationState)
677             {
678                 currentError.ValidationState = childValidationState;
679             }
680
681             this.NotifyValidationPropertiesChanged(errorParent);
682         }
683
684         private void NotifyValidationPropertiesChanged(object errorItem)
685         {
686             ModelItem errorModelItem = this.modelTreeManager.GetModelItem(errorItem);
687             if (errorModelItem != null)
688             {
689                 this.NotifyValidationPropertiesChanged(errorModelItem);
690             }
691         }
692
693         private void NotifyValidationPropertiesChanged(ModelItem modelItem)
694         {
695             // Notify an update to the attached properties
696             this.validationStateProperty.NotifyPropertyChanged(modelItem);
697             this.validationMessageProperty.NotifyPropertyChanged(modelItem);
698         }
699
700         private bool HandleErrorsMarked(ICollection<ValidationError> errors, ValidationReason reason)
701         {
702             if (this.ErrorsMarked != null)
703             {
704                 ErrorsMarkedEventArgs arg = new ErrorsMarkedEventArgs(errors, reason, this.modelTreeManager, this.context);
705                 this.ErrorsMarked(this, arg);
706                 return arg.Handled;
707             }
708
709             return false;
710         }
711
712         private static ModelItem FindActivityModelItem(ModelTreeManager modelTreeManager, Activity errorTarget)
713         {
714             // Search the lowest Activity
715             ModelItem lowestModelItem = null;
716             List<Activity> parentChain = GetParentChain(errorTarget);
717             Fx.Assert(parentChain != null, "Cannot find parent chain for " + errorTarget.DisplayName);
718
719             foreach (Activity parent in parentChain)
720             {
721                 lowestModelItem = modelTreeManager.GetModelItem(parent);
722                 if (lowestModelItem != null)
723                 {
724                     break;
725                 }
726             }
727
728             ModelItem foundItem = null;
729             // Find in nearest parent first.
730             if (lowestModelItem != null)
731             {
732                 // The foundItem could be null because lowestModelItem is not errorTarget's parent any more.
733                 // This happens if background validation hasn't finished updating errorTarget's parent.
734                 foundItem = ModelTreeManager.FindFirst(lowestModelItem, (modelItem) => (modelItem.GetCurrentValue() == errorTarget));
735             }
736
737             // Not found, search from root.
738             if (foundItem == null)
739             {
740                 foundItem = FindActivityModelItemFromRoot(modelTreeManager, errorTarget);
741             }
742
743             return foundItem;
744         }
745
746         private static ModelItem FindActivityModelItemFromRoot(ModelTreeManager modelTreeManager, Activity errorTarget)
747         {
748             ModelItem root = modelTreeManager.Root;
749             Fx.Assert(root != null && errorTarget != null, "root != null && errorTarget != null");
750             ModelProperty property = root.Properties["Properties"];
751
752             ModelItem propertiesModelItem = property == null ? null : property.Value;
753             ModelItem foundItem = null;
754             if (propertiesModelItem != null)
755             {
756                 // So,search "Properties" first to delay expanding "Implementation" and other properties.
757                 foundItem = ModelTreeManager.FindFirst(propertiesModelItem, (modelItem) => (modelItem.GetCurrentValue() == errorTarget));
758             }
759
760             // If activity is not in Properties, expand others except Properties.
761             foundItem = foundItem ?? ModelTreeManager.FindFirst(
762                 root, 
763                 (modelItem) => (modelItem.GetCurrentValue() == errorTarget),
764                 (modelItem) => { return modelItem != propertiesModelItem; });
765
766             return foundItem;
767         }
768
769         private static ValidationState GetValidationState(ValidationError validationError)
770         {
771             return validationError.IsWarning ? ValidationState.Warning : ValidationState.Error;
772         }
773
774         private static ValidationErrorState ChildInvalidError()
775         {
776             return new ValidationErrorState(new List<string> { SR.ChildValidationError }, ValidationState.ChildInvalid);
777         }
778
779         private static void MergeValidationError(ValidationErrorState originalError, ValidationError newError)
780         {
781             if (originalError.ValidationState == ValidationState.ChildInvalid)
782             {
783                 // If original error is due to child's issue, clear the error list, 
784                 // as we don't care about its child's issues anymore and want to add its own issues.
785                 originalError.ErrorMessages.Clear();
786             }
787
788             ValidationState errorState = GetValidationState(newError);
789             if (originalError.ValidationState < errorState)
790             {
791                 // Promote to the higher level of violation.
792                 originalError.ValidationState = errorState;
793             }
794
795             if (newError.IsWarning)
796             {
797                 originalError.ErrorMessages.Add(string.Format(CultureInfo.CurrentUICulture, SR.WarningFormat, newError.Message));
798             }
799             else
800             {
801                 originalError.ErrorMessages.Add(newError.Message);
802             }
803         }
804
805         void ClearErrors()
806         {
807             // Copy over the previously marked model items before you clear the dictionaries
808             object[] oldErrorList = new object[this.ValidationErrors.Count];
809             this.ValidationErrors.Keys.CopyTo(oldErrorList, 0);
810
811             this.ValidationErrors.Clear();
812
813             // Iterate through the previously marked model items and notify an update to the attached properties
814             ModelItem modelItem;
815             foreach (object workflowElement in oldErrorList)
816             {
817                 modelItem = this.modelTreeManager.GetModelItem(workflowElement);
818                 if (modelItem != null)
819                 {
820                     NotifyValidationPropertiesChanged(modelItem);
821                 }
822             }
823         }
824
825         public void NavigateToError(ValidationErrorInfo validationErrorInfo)
826         {
827             if (validationErrorInfo == null)
828             {
829                 throw FxTrace.Exception.ArgumentNull("validationErrorInfo");
830             }
831
832             object sourceDetail = this.GetSourceDetail(validationErrorInfo);
833             this.NavigateToErrorOnDispatcherThread(sourceDetail);
834         }
835
836         private object GetSourceDetail(ValidationErrorInfo validationErrorInfo)
837         {
838             Fx.Assert(validationErrorInfo != null, "validationErrorInfo should not be null and is checked by caller.");
839             Guid sourceReferenceId = validationErrorInfo.SourceReferenceId;
840             object sourceDetail = null;
841
842             if (sourceReferenceId == Guid.Empty)
843             {
844                 if (this.modelTreeManager.Root != null)
845                 {
846                     sourceDetail = modelTreeManager.Root.GetCurrentValue();
847                 }
848             }
849             else
850             {
851                 if (!this.objectReferenceService.TryGetObject(sourceReferenceId, out sourceDetail))
852                 {
853                     throw FxTrace.Exception.Argument("validationErrorInfo", string.Format(CultureInfo.CurrentUICulture, SR.SourceReferenceIdNotFoundInWorkflow, sourceReferenceId));
854                 }
855             }
856             return sourceDetail;
857         }
858
859         private void NavigateToErrorOnDispatcherThread(object sourceDetail)
860         {
861             this.validationTaskDispatcher.DispatchWorkOnUIThread(DispatcherPriority.ApplicationIdle, new Action(
862             () =>
863             {
864                 this.NavigateToError(sourceDetail);
865             }));
866         }
867
868         public void NavigateToError(string id)
869         {
870             if (id == null)
871             {
872                 throw FxTrace.Exception.ArgumentNull("id");
873             }
874
875             ValidationRoot rootElement = this.GetRootElement();
876             if (rootElement != null)
877             {
878                 Activity errorElement = rootElement.Resolve(id);
879                 this.NavigateToErrorOnDispatcherThread(errorElement);
880             }
881         }
882
883         void NavigateToError(object sourceDetail)
884         {
885             Fx.Assert(this.modelTreeManager != null, "ModelTreeManager is null.");
886             ModelItem modelItem = this.modelTreeManager.GetModelItem(sourceDetail) ?? FindModelItem(this.modelTreeManager, sourceDetail);
887
888             if (modelItem != null)
889             {
890                 if (this.modelSearchService != null)
891                 {
892                     this.modelSearchService.NavigateTo(modelItem);
893                 }
894                 else
895                 {
896                     // For any Expression, need to focus to its parent instead.
897                     Activity activity = modelItem.GetCurrentValue() as Activity;
898                     if (activity != null && (activity.IsExpression()))
899                     {
900                         ModelItem parent = modelItem.Parent;
901                         while (parent != null)
902                         {
903                             bool hasDesignerAttribute = this.ViewService.GetDesignerType(parent.ItemType) != null;
904
905                             // ModelItemKeyValuePair type also has DesignerAttribute.
906                             // Since we do not want to put a focus on that type, special-casing it here.
907                             bool isModelItemKeyValuePair = parent.ItemType.IsGenericType &&
908                             parent.ItemType.GetGenericTypeDefinition() == typeof(ModelItemKeyValuePair<,>);
909
910                             if (hasDesignerAttribute && !isModelItemKeyValuePair)
911                             {
912                                 break;
913                             }
914
915                             parent = parent.Parent;
916                         }
917
918                         if (parent != null)
919                         {
920                             modelItem = parent;
921                         }
922                     }
923                     modelItem.Focus();
924                 }
925             }
926         }
927
928         internal void RegisterValidationErrorSourceLocator(Type activityType, IValidationErrorSourceLocator validationErrorSourceLocator)
929         {
930             if (validationErrorSourceLocator == null)
931             {
932                 throw FxTrace.Exception.ArgumentNull("validationErrorSourceLocator");
933             }
934             this.ValidationErrorSourceLocators.Add(activityType, validationErrorSourceLocator);
935         }
936
937         List<object> GetValidationErrorSourcePath(Activity violatingActivity, object sourceDetail)
938         {
939             IValidationErrorSourceLocator validationErrorSourceLocator = GetValidationErrorSourceLocator(violatingActivity.GetType());
940             if (validationErrorSourceLocator == null)
941             {
942                 return new List<object> { violatingActivity };
943             }
944             else
945             {
946                 return validationErrorSourceLocator.FindSourceDetailFromActivity(violatingActivity, sourceDetail);
947             }
948         }
949
950         IValidationErrorSourceLocator GetValidationErrorSourceLocator(Type typeOfActivityWithValidationError)
951         {
952             IValidationErrorSourceLocator validationErrorSourceLocator;
953             if (this.ValidationErrorSourceLocators.TryGetValue(typeOfActivityWithValidationError, out validationErrorSourceLocator))
954             {
955                 Fx.Assert(validationErrorSourceLocator != null, "Ensured by RegisterValidationErrorSourceLocator");
956                 return validationErrorSourceLocator;
957             }
958             else if (typeOfActivityWithValidationError.IsGenericType && !typeOfActivityWithValidationError.IsGenericTypeDefinition)
959             {
960                 return this.GetValidationErrorSourceLocator(typeOfActivityWithValidationError.GetGenericTypeDefinition());
961             }
962             else
963             {
964                 return null;
965             }
966         }
967
968         // Properties
969         Dictionary<object, ValidationErrorState> ValidationErrors
970         {
971             get
972             {
973                 if (this.validationErrors == null)
974                 {
975                     this.validationErrors = new Dictionary<object, ValidationErrorState>();
976                 }
977                 return this.validationErrors;
978             }
979         }
980
981         Dictionary<Type, IValidationErrorSourceLocator> ValidationErrorSourceLocators
982         {
983             get
984             {
985                 if (this.validationErrorSourceLocators == null)
986                 {
987                     this.validationErrorSourceLocators = new Dictionary<Type, IValidationErrorSourceLocator>();
988                 }
989                 return this.validationErrorSourceLocators;
990             }
991         }
992
993         List<Guid> AcquiredObjectReferences
994         {
995             get
996             {
997                 if (this.acquiredObjectReferences == null)
998                 {
999                     this.acquiredObjectReferences = new List<Guid>();
1000                 }
1001
1002                 return this.acquiredObjectReferences;
1003             }
1004         }
1005
1006         internal AttachedProperty<ValidationState> ValidationStateProperty
1007         {
1008             get
1009             {
1010                 return this.validationStateProperty;
1011             }
1012         }
1013
1014         internal AttachedProperty<string> ValidationMessageProperty
1015         {
1016             get
1017             {
1018                 return this.validationMessageProperty;
1019             }
1020         }
1021
1022         class ValidationErrorState
1023         {
1024             internal ValidationErrorState(List<string> errorMessages, ValidationState validationState)
1025             {
1026                 this.ErrorMessages = errorMessages;
1027                 this.ValidationState = validationState;
1028             }
1029
1030             internal List<string> ErrorMessages { get; set; }
1031             internal ValidationState ValidationState { get; set; }
1032         }
1033     }
1034 }