// -----------------------------------------------------------------------
// 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;
}
}
}