// ----------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. // ----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Composition.Primitives; using System.Globalization; using System.Linq; using System.Runtime.Serialization; using System.Security.Permissions; using System.Text; using Microsoft.Internal; using Microsoft.Internal.Collections; namespace System.ComponentModel.Composition { /// /// The exception that is thrown when one or more errors occur during composition in /// a . /// [Serializable] public class CompositionException : Exception { const string ErrorsKey = "Errors"; private ReadOnlyCollection _errors; #if CLR40 && !SILVERLIGHT [Serializable] private struct CompositionExceptionData : ISafeSerializationData { public CompositionError[] _errors; void ISafeSerializationData.CompleteDeserialization(object obj) { CompositionException exception = obj as CompositionException; exception._errors = new ReadOnlyCollection(this._errors); } } #endif /// /// Initializes a new instance of the class. /// public CompositionException() : this((string)null, (Exception)null, (IEnumerable)null) { } /// /// Initializes a new instance of the class /// with the specified error message. /// /// /// A containing a message that describes the /// ; or to set /// the property to its default value. /// public CompositionException(string message) : this(message, (Exception)null, (IEnumerable)null) { } /// /// Initializes a new instance of the class /// with the specified error message and exception that is the cause of the /// exception. /// /// /// A containing a message that describes the /// ; or to set /// the property to its default value. /// /// /// The that is the underlying cause of the /// ; or to set /// the property to . /// public CompositionException(string message, Exception innerException) : this(message, innerException, (IEnumerable)null) { } internal CompositionException(CompositionError error) : this(new CompositionError[] { error }) { } /// /// Initializes a new instance of the class /// with the specified errors. /// /// /// An of objects /// representing the errors that are the cause of the /// ; or to set the /// property to an empty . /// /// /// contains an element that is . /// public CompositionException(IEnumerable errors) : this((string)null, (Exception)null, errors) { } internal CompositionException(string message, Exception innerException, IEnumerable errors) : base(message, innerException) { Requires.NullOrNotNullElements(errors, "errors"); #if CLR40 && !SILVERLIGHT SerializeObjectState += delegate(object exception, SafeSerializationEventArgs eventArgs) { var data = new CompositionExceptionData(); if(this._errors != null) { data._errors = this._errors.Select(error => new CompositionError( ((ICompositionError)error).Id, error.Description, error.Element.ToSerializableElement(), error.Exception)).ToArray(); } else { data._errors = new CompositionError[0]; } eventArgs.AddSerializedState(data); }; #endif _errors = new ReadOnlyCollection(errors == null ? new CompositionError[0] : errors.ToArray()); } /// /// Gets the errors that are the cause of the exception. /// /// /// An of objects /// representing the errors that are the cause of the /// . /// public ReadOnlyCollection Errors { get { return _errors; } } /// /// Gets a message that describes the exception. /// /// /// A containing a message that describes the /// . /// public override string Message { [System.Security.SecuritySafeCritical] get { if (this.Errors.Count == 0) { // If there are no errors, then we simply return base.Message, // which will either use the default Exception message, or if // one was specified; the user supplied message. return base.Message; } return BuildDefaultMessage(); } } private string BuildDefaultMessage() { IEnumerable> paths = CalculatePaths(this); StringBuilder writer = new StringBuilder(); WriteHeader(writer, this.Errors.Count, paths.Count()); WritePaths(writer, paths); return writer.ToString(); } private static void WriteHeader(StringBuilder writer, int errorsCount, int pathCount) { if (errorsCount > 1 && pathCount > 1) { // The composition produced multiple composition errors, with {0} root causes. The root causes are provided below. writer.AppendFormat( CultureInfo.CurrentCulture, Strings.CompositionException_MultipleErrorsWithMultiplePaths, pathCount); } else if (errorsCount == 1 && pathCount > 1) { // The composition produced a single composition error, with {0} root causes. The root causes are provided below. writer.AppendFormat( CultureInfo.CurrentCulture, Strings.CompositionException_SingleErrorWithMultiplePaths, pathCount); } else { Assumes.IsTrue(errorsCount == 1); Assumes.IsTrue(pathCount == 1); // The composition produced a single composition error. The root cause is provided below. writer.AppendFormat( CultureInfo.CurrentCulture, Strings.CompositionException_SingleErrorWithSinglePath, pathCount); } writer.Append(' '); writer.AppendLine(Strings.CompositionException_ReviewErrorProperty); } private static void WritePaths(StringBuilder writer, IEnumerable> paths) { int ordinal = 0; foreach (IEnumerable path in paths) { ordinal++; WritePath(writer, path, ordinal); } } private static void WritePath(StringBuilder writer, IEnumerable path, int ordinal) { writer.AppendLine(); writer.Append(ordinal.ToString(CultureInfo.CurrentCulture)); writer.Append(Strings.CompositionException_PathsCountSeparator); writer.Append(' '); WriteError(writer, path.First()); foreach (CompositionError error in path.Skip(1)) { writer.AppendLine(); writer.Append(Strings.CompositionException_ErrorPrefix); writer.Append(' '); WriteError(writer, error); } } private static void WriteError(StringBuilder writer, CompositionError error) { writer.AppendLine(error.Description); if (error.Element != null) { WriteElementGraph(writer, error.Element); } } private static void WriteElementGraph(StringBuilder writer, ICompositionElement element) { // Writes the composition element and its origins in the format: // Element: Export --> Part --> PartDefinition --> Catalog writer.AppendFormat(CultureInfo.CurrentCulture, Strings.CompositionException_ElementPrefix, element.DisplayName); while ((element = element.Origin) != null) { writer.AppendFormat(CultureInfo.CurrentCulture, Strings.CompositionException_OriginFormat, Strings.CompositionException_OriginSeparator, element.DisplayName); } writer.AppendLine(); } private static IEnumerable> CalculatePaths(CompositionException exception) { List> paths = new List>(); VisitContext context = new VisitContext(); context.Path = new Stack(); context.LeafVisitor = path => { // Take a snapshot of the path paths.Add(path.Copy()); }; VisitCompositionException(exception, context); return paths; } private static void VisitCompositionException(CompositionException exception, VisitContext context) { foreach (CompositionError error in exception.Errors) { VisitError(error, context); } if (exception.InnerException != null) { VisitException(exception.InnerException, context); } } private static void VisitError(CompositionError error, VisitContext context) { context.Path.Push(error); if (error.Exception == null) { // This error is a root cause, so write // out the stack from this point context.LeafVisitor(context.Path); } else { VisitException(error.Exception, context); } context.Path.Pop(); } private static void VisitException(Exception exception, VisitContext context) { CompositionException composition = exception as CompositionException; if (composition != null) { VisitCompositionException(composition, context); } else { VisitError(new CompositionError(exception.Message, exception.InnerException), context); } } private struct VisitContext { public Stack Path; public Action> LeafVisitor; } } }