StyleCop clean.
[dotnetoauth.git] / src / DotNetOpenAuth / Messaging / Reflection / MessageDescription.cs
blob94812d81c2f84a0713484fc88f0a117bf0c4ec98
1 //-----------------------------------------------------------------------
2 // <copyright file="MessageDescription.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.Messaging.Reflection {
8 using System;
9 using System.Collections.Generic;
10 using System.Diagnostics;
11 using System.Globalization;
12 using System.Linq;
13 using System.Reflection;
15 /// <summary>
16 /// A mapping between serialized key names and <see cref="MessagePart"/> instances describing
17 /// those key/values pairs.
18 /// </summary>
19 internal class MessageDescription {
20 /// <summary>
21 /// A dictionary of reflected message types and the generated reflection information.
22 /// </summary>
23 private static Dictionary<MessageTypeAndVersion, MessageDescription> reflectedMessageTypes = new Dictionary<MessageTypeAndVersion, MessageDescription>();
25 /// <summary>
26 /// The type of message this instance was generated from.
27 /// </summary>
28 private MessageTypeAndVersion messageTypeAndVersion;
30 /// <summary>
31 /// A mapping between the serialized key names and their
32 /// describing <see cref="MessagePart"/> instances.
33 /// </summary>
34 private Dictionary<string, MessagePart> mapping;
36 /// <summary>
37 /// Initializes a new instance of the <see cref="MessageDescription"/> class.
38 /// </summary>
39 /// <param name="messageTypeAndVersion">The type and protocol version of the message to reflect over.</param>
40 private MessageDescription(MessageTypeAndVersion messageTypeAndVersion) {
41 ErrorUtilities.VerifyArgumentNotNull(messageTypeAndVersion, "messageTypeAndVersion");
43 if (!typeof(IProtocolMessage).IsAssignableFrom(messageTypeAndVersion.Type)) {
44 throw new ArgumentException(string.Format(
45 CultureInfo.CurrentCulture,
46 MessagingStrings.UnexpectedType,
47 typeof(IProtocolMessage),
48 messageTypeAndVersion.Type));
51 this.messageTypeAndVersion = messageTypeAndVersion;
52 this.ReflectMessageType();
55 /// <summary>
56 /// Gets the mapping between the serialized key names and their describing
57 /// <see cref="MessagePart"/> instances.
58 /// </summary>
59 internal IDictionary<string, MessagePart> Mapping {
60 get { return this.mapping; }
63 /// <summary>
64 /// Gets a <see cref="MessageDescription"/> instance prepared for the
65 /// given message type.
66 /// </summary>
67 /// <param name="messageType">A type that implements <see cref="IProtocolMessage"/>.</param>
68 /// <param name="messageVersion">The protocol version of the message.</param>
69 /// <returns>A <see cref="MessageDescription"/> instance.</returns>
70 internal static MessageDescription Get(Type messageType, Version messageVersion) {
71 ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType");
72 ErrorUtilities.VerifyArgumentNotNull(messageVersion, "messageVersion");
74 MessageTypeAndVersion key = new MessageTypeAndVersion(messageType, messageVersion);
76 MessageDescription result;
77 if (!reflectedMessageTypes.TryGetValue(key, out result)) {
78 lock (reflectedMessageTypes) {
79 if (!reflectedMessageTypes.TryGetValue(key, out result)) {
80 reflectedMessageTypes[key] = result = new MessageDescription(key);
85 return result;
88 /// <summary>
89 /// Reflects over some <see cref="IProtocolMessage"/>-implementing type
90 /// and prepares to serialize/deserialize instances of that type.
91 /// </summary>
92 internal void ReflectMessageType() {
93 this.mapping = new Dictionary<string, MessagePart>();
95 Type currentType = this.messageTypeAndVersion.Type;
96 do {
97 foreach (MemberInfo member in currentType.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) {
98 if (member is PropertyInfo || member is FieldInfo) {
99 MessagePartAttribute partAttribute =
100 (from a in member.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>()
101 orderby a.MinVersionValue descending
102 where a.MinVersionValue <= this.messageTypeAndVersion.Version
103 select a).FirstOrDefault();
104 if (partAttribute != null) {
105 MessagePart part = new MessagePart(member, partAttribute);
106 this.mapping.Add(part.Name, part);
110 currentType = currentType.BaseType;
111 } while (currentType != null);
114 /// <summary>
115 /// Ensures the message parts pass basic validation.
116 /// </summary>
117 /// <param name="parts">The key/value pairs of the serialzied message.</param>
118 internal void EnsureMessagePartsPassBasicValidation(IDictionary<string, string> parts) {
119 this.EnsureRequiredMessagePartsArePresent(parts.Keys);
120 this.EnsureRequiredProtocolMessagePartsAreNotEmpty(parts);
123 /// <summary>
124 /// Verifies that a given set of keys include all the required parameters
125 /// for this message type or throws an exception.
126 /// </summary>
127 /// <param name="keys">The names of all parameters included in a message.</param>
128 /// <exception cref="ProtocolException">Thrown when required parts of a message are not in <paramref name="keys"/></exception>
129 private void EnsureRequiredMessagePartsArePresent(IEnumerable<string> keys) {
130 var missingKeys = (from part in Mapping.Values
131 where part.IsRequired && !keys.Contains(part.Name)
132 select part.Name).ToArray();
133 if (missingKeys.Length > 0) {
134 throw new ProtocolException(
135 string.Format(
136 CultureInfo.CurrentCulture,
137 MessagingStrings.RequiredParametersMissing,
138 this.messageTypeAndVersion.Type.FullName,
139 string.Join(", ", missingKeys)));
143 /// <summary>
144 /// Ensures the protocol message parts that must not be empty are in fact not empty.
145 /// </summary>
146 /// <param name="partValues">A dictionary of key/value pairs that make up the serialized message.</param>
147 private void EnsureRequiredProtocolMessagePartsAreNotEmpty(IDictionary<string, string> partValues) {
148 string value;
149 var emptyValuedKeys = (from part in Mapping.Values
150 where !part.AllowEmpty && partValues.TryGetValue(part.Name, out value) && value.Length == 0
151 select part.Name).ToArray();
152 if (emptyValuedKeys.Length > 0) {
153 throw new ProtocolException(
154 string.Format(
155 CultureInfo.CurrentCulture,
156 MessagingStrings.RequiredNonEmptyParameterWasEmpty,
157 this.messageTypeAndVersion.Type.FullName,
158 string.Join(", ", emptyValuedKeys)));
162 /// <summary>
163 /// A struct used as the key to bundle message type and version.
164 /// </summary>
165 private struct MessageTypeAndVersion {
166 /// <summary>
167 /// Backing store for the <see cref="Type"/> property.
168 /// </summary>
169 private readonly Type type;
171 /// <summary>
172 /// Backing store for the <see cref="Version"/> property.
173 /// </summary>
174 private readonly Version version;
176 /// <summary>
177 /// Initializes a new instance of the <see cref="MessageTypeAndVersion"/> struct.
178 /// </summary>
179 /// <param name="messageType">Type of the message.</param>
180 /// <param name="messageVersion">The message version.</param>
181 internal MessageTypeAndVersion(Type messageType, Version messageVersion) {
182 ErrorUtilities.VerifyArgumentNotNull(messageType, "messageType");
183 ErrorUtilities.VerifyArgumentNotNull(messageVersion, "messageVersion");
185 this.type = messageType;
186 this.version = messageVersion;
189 /// <summary>
190 /// Gets the message type.
191 /// </summary>
192 internal Type Type { get { return this.type; } }
194 /// <summary>
195 /// Gets the message version.
196 /// </summary>
197 internal Version Version { get { return this.version; } }
199 /// <summary>
200 /// Implements the operator ==.
201 /// </summary>
202 /// <param name="first">The first object to compare.</param>
203 /// <param name="second">The second object to compare.</param>
204 /// <returns>The result of the operator.</returns>
205 public static bool operator ==(MessageTypeAndVersion first, MessageTypeAndVersion second) {
206 // structs cannot be null, so this is safe
207 return first.Equals(second);
210 /// <summary>
211 /// Implements the operator !=.
212 /// </summary>
213 /// <param name="first">The first object to compare.</param>
214 /// <param name="second">The second object to compare.</param>
215 /// <returns>The result of the operator.</returns>
216 public static bool operator !=(MessageTypeAndVersion first, MessageTypeAndVersion second) {
217 // structs cannot be null, so this is safe
218 return !first.Equals(second);
221 /// <summary>
222 /// Indicates whether this instance and a specified object are equal.
223 /// </summary>
224 /// <param name="obj">Another object to compare to.</param>
225 /// <returns>
226 /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
227 /// </returns>
228 public override bool Equals(object obj) {
229 if (obj is MessageTypeAndVersion) {
230 MessageTypeAndVersion other = (MessageTypeAndVersion)obj;
231 return this.type == other.type && this.version == other.version;
232 } else {
233 return false;
237 /// <summary>
238 /// Returns the hash code for this instance.
239 /// </summary>
240 /// <returns>
241 /// A 32-bit signed integer that is the hash code for this instance.
242 /// </returns>
243 public override int GetHashCode() {
244 return this.type.GetHashCode();