Property Merger

/// <summary>
	/// Property mapper, a reflection pattern modified to act as a merge tool
	/// </summary>
	/// <remarks>
	/// Merges two items based on the property map
	/// </remarks>
	public class PropertyMergeTool
	{

		/// <summary>
		/// Get the columns necessary to merge on
		/// </summary>
		/// <typeparam name="T">The type to perform the merge with</typeparam>
		/// <returns>The return value</returns>
		public string[] GetColumns<T>()
		{
			Type t = typeof(T);
			return t.GetProperties().GetPropertyCollection(p=>p.Name).ToArray();
		}

		/// <summary>
		/// Copy all the data rows as per the property map
		/// </summary>
		/// <typeparam name="T">The type to copy into</typeparam>
		/// <param name="row">The datarow (for merging this is just the source object)</param>
		/// <param name="emptyObject">The object to copy data into</param>
		/// <param name="mapping">The mapping to control the data copy</param>
		/// <returns>The merged object</returns>
		public static T Merge<T>(T sourceObject, T destinationObject, PropertyMapCollection mapping)
		{
			Type objectType = typeof(T);
			//ProgressConsole.WriteLine("DataRow -> "+objectType.Name+" conversion");

			var mappings = mapping.PropertyMappings.ToDictionaryWithoutDuplicates(o => o.DestinationProperty);

			PropertyInfo[] properties = objectType.GetProperties();

			foreach (PropertyInfo pi in properties)
			{
				if (mappings.ContainsKey(pi.Name))
				{
					string sourceColumn = mappings[pi.Name].SourceColumnName;

					if (mappings[pi.Name].DestinationDataTakesPrecendence)
					{
						//do nothing!
					}
					else
					if (mappings[pi.Name].SourceDataTakesPrecendence)
					{
						if (sourceColumn != null)
						{
							object sourceData = GetValue(pi, sourceObject);
							if (sourceData!=null)
							{
								//both the mapping and the column exists
								try
								{
									SetValue(pi, destinationObject, sourceData);
									//ProgressConsole.WriteLine("Mapping " + pi.Name + "->" + sourceValue.ToString());
								}
								catch (Exception)
								{
									string fallbackValue = mappings[pi.Name].DefaultValueIfInvalid;
									SetValue(pi, destinationObject, fallbackValue);
									//ProgressConsole.WriteLine("Default Mapping " + pi.Name + "->" + fallbackValue);
								}
							}
						}
						else
						{
							string fallbackValue = mappings[pi.Name].DefaultValueIfInvalid;
							SetValue(pi, destinationObject, fallbackValue);
						}
					}
				}
			}

			return destinationObject;
		}

		/// <summary>
		/// Get a value
		/// </summary>
		/// <param name="pi"></param>
		/// <param name="sourceObject"></param>
		/// <returns></returns>
		private static object GetValue(PropertyInfo pi, object sourceObject)
		{
			//check for invalid values and return here?
			return pi.GetValue(sourceObject, null);
		}

		/// <summary>
		/// Set value, applies validation
		/// </summary>
		/// <param name="pi"></param>
		/// <param name="emptyObject"></param>
		/// <param name="value"></param>
		private static void SetValue(PropertyInfo pi, object emptyObject, object value)
		{
			if (pi.PropertyType.IsSubclassOf(typeof(System.Nullable)) && (value == null || value.ToString() == ""))
			{
				pi.SetValue(emptyObject, null, null);
				return;
			}

			if (pi.PropertyType == typeof(DateTime) || pi.PropertyType == typeof(Nullable<DateTime>))
			{
				if (pi.PropertyType == typeof(Nullable<DateTime>) && (value == null || value.ToString() == ""))
				{
					pi.SetValue(emptyObject, null, null);
					return;
				}
				else
				{
					if (value is DateTime)
					{
						pi.SetValue(emptyObject, (DateTime)value, null);
					}
					else
					{
						if (value is string && ((string)value) == "")
						{
							pi.SetValue(emptyObject, DateTime.Now, null);
							return;
						}
						else
						{

							pi.SetValue(emptyObject, Convert.ToDateTime(value), null);
							return;
						}
					}
				}
			}

			if (pi.PropertyType == typeof(Boolean) || pi.PropertyType == typeof(Nullable<Boolean>))
			{
				if (pi.PropertyType == typeof(Nullable<Boolean>) && (value == null || value.ToString() == ""))
				{
					pi.SetValue(emptyObject, null, null);
					return;
				}
				else
				{
					pi.SetValue(emptyObject, Convert.ToBoolean(value), null);
					return;
				}
			}

			if (pi.PropertyType == typeof(System.Int32) || pi.PropertyType == typeof(Nullable<System.Int32>))
			{
				if (pi.PropertyType == typeof(Nullable<Int32>) && (value == null || value.ToString() == ""))
				{
					pi.SetValue(emptyObject, null, null);
					return;
				}
				else
				{
					pi.SetValue(emptyObject, Convert.ToInt32(value), null);
					return;
				}
			}
			if (pi.PropertyType == typeof(System.Guid) || pi.PropertyType == typeof(Nullable<System.Guid>))
			{
				if (pi.PropertyType == typeof(Nullable<Guid>) && (value == null || (string)value == ""))
				{
					pi.SetValue(emptyObject, null, null);
					return;
				}
				else
				{
					pi.SetValue(emptyObject, new Guid((string)value), null);
					return;
				}
			}
			pi.SetValue(emptyObject, value, null);
		}

		[XmlRoot()]
		[Serializable]
		public class PropertyMapCollection
		{
			[XmlArray()]
			public PropertyMap[] PropertyMappings;
		}

		[Serializable]
		public class PropertyMap
		{
			[XmlAttribute]
			public string SourceColumnName { get; set; }
			[XmlAttribute]
			public string DestinationProperty { get; set; }
			[XmlAttribute]
			public string DefaultValueIfInvalid { get; set; }
			[XmlAttribute]
			public bool SourceDataTakesPrecendence { get; set; }
			[XmlAttribute]
			public bool DestinationDataTakesPrecendence { get; set; }
		}

		public static PropertyMapCollection LoadPropertyMapFromXml(string filename)
		{
			if (!File.Exists(filename))
			{
				PropertyMapCollection col = new PropertyMapCollection();
				col.PropertyMappings = new PropertyMap[] { };
				return col;
			}
			using (XmlTextReader tr = new XmlTextReader(filename))
			{
				XmlSerializer s = new XmlSerializer(typeof(PropertyMapCollection));
				PropertyMapCollection propertyMapCollection = s.Deserialize(tr) as PropertyMapCollection;
				return propertyMapCollection;
			}
		}

		public static void SavePropertyMapToXml(PropertyMapCollection property, string filename)
		{
			//to avoid failure during the serialisation resulting in corruption we'll do it into a temp file then copy it, then the OS is responsible for any corruption instead...
			string tempFile = Path.GetTempFileName();
			using (XmlTextWriter tw = new XmlTextWriter(tempFile, Encoding.Default))
			{
				XmlSerializer s = new XmlSerializer(typeof(PropertyMapCollection));
				s.Serialize(tw, property);
				File.Copy(tempFile, filename, true);
			}
		}
	}