context: // MappedProperty.cs - MappedProperty implementation for Gtk#Databindings
  //
  // Author: m. <ml@arsis.net>
  //
  // Copyright (c) 2006 m.
  //
  // This program is free software; you can redistribute it and/or
  // modify it under the terms of version 2 of the Lesser GNU General 
  // Public License as published by the Free Software Foundation.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  // Lesser General Public License for more details.
  //
  // You should have received a copy of the GNU Lesser General Public
  // License along with this program; if not, write to the
  // Free Software Foundation, Inc., 59 Temple Place - Suite 330,
  // Boston, MA 02111-1307, USA.
  
  #define NEWCACHE
  //#define OLDCACHE
  
  using System;
  using System.Reflection;
  #if NEWCACHE
  using System.Collections;
  #endif
  using System.Data.Bindings.Cached;
  using System.Data.Bindings.DebugInformation;
  
  namespace System.Data.Bindings
  {
  	/// <summary>
  	/// Connection between property and mapping
  	/// </summary>
  	public class MappedProperty
  	{
  #if NEWCACHE
  		private bool resetResolve = false;
  		private CachedProperty dataCache = null;
  		private CachedProperty controlCache = null;
  #endif
  		private bool isSubItem = false;
  		private MappedProperty masterItem = null;
  #if OLDCACHE
  		private bool invalid = true;
  		private bool cached = false;
  #endif
  
  		internal const string NullValue = "!#not_defined";
  		
  		/// <summary>
  		/// Returns if this MappedProperty is Sub-Item of another MappedProperty
  		/// </summary>
  		/// <remarks>
  		/// Only valid for column properties, to allow more complex columns than one CellRenderer per
  		/// column.
  		/// </remarks>
  		[ToDo ("Add named CellRenderer spec to allow CellRenderer to be activated on demand")]
  		public bool IsSubItem {
  			get { return ((masterItem != null) && (isSubItem == true)); }
  		}
  		
  		/// <summary>
  		/// MasterItem returns MasterProperty of this sub-Item.
  		/// </summary>
  		/// <remarks>
  		/// Only valid for column properties, to allow more complex columns than one CellRenderer per
  		/// column.
  		/// </remarks>
  		public MappedProperty MasterItem {
  			get {
  				if (IsSubItem == false)
  					return (null);
  				return (masterItem);
  			}
  		}
  		
  		private WeakReference adaptor = null;
  		/// <summary>
  		/// WeakReference to adaptor, and read-only IAdaptor access
  		/// </summary>
  		public IAdaptor Adaptor {
  			get {
  				if (adaptor == null)
  					return (null);
  				return ((IAdaptor) adaptor.Target);
  			}
  		}
  		
  		/// <summary>
  		/// Returns System.Type of the mapped property
  		/// </summary>
  /*		private System.Type valuePropertyType;
  		public System.Type ValuePropertyType {
  			get { return (valuePropertyType); }
  		}*/
  		
  		private string name = "";
  		/// <summary>
  		/// Mapped Property name, uses caching and if needed resolves again
  		/// </summary>
  		public string Name {
  			get { return (name); }
  			set { 
  #if NEWCACHE
  				if (name == value)
  					return;
  				name = value;
  				resetResolve = true;
  #endif
  #if OLDCACHE
  				if ((name == value) && (cached == true))
  					return;
  				invalid = true;
  				name = value;
  				cached = false;
  				Resolve();
  #endif
  			}
  		}
  		
  #if NEWCACHE
  		private void ClearDataMappingCache()
  		{
  			dataCache.Clear();
  		}
  #endif
  	
  		private string hint = NullValue;
  		/// <value>
  		/// Specifies hint for mapped property
  		/// </value>
  		public string Hint {
  			get { 
  				if (hint == NullValue)
  					hint = GetHint();
  				return (hint); 
  			}
  		}
  		
  		private string title = NullValue;
  		/// <value>
  		/// Specifies hint for mapped property
  		/// </value>
  		public string Title {
  			get { 
  				if (title == NullValue)
  					title = GetTitle();
  				return (title); 
  			}
  		}
  		
  		private string mappingTarget = "";
  		/// <summary>
  		/// Mapping target defines which property is to be controlled with this
  		/// mapping
  		/// </summary>
  		public string MappingTarget {
  			get { return (mappingTarget); }
  			set { mappingTarget = value; }
  		}
  		
  		private string columnName = "";
  		/// <summary>
  		/// Column name if this is column
  		/// </summary>
  		public string ColumnName {
  			get {
  				if (IsColumnMapping == false)
  					return ("");
  				else
  					if (columnName != "")
  						return (columnName);
  					else
  						return (Name);
  			}
  			set { columnName = value; }
  		}
  		
  		private bool isColumnMapping = false;
  		/// <summary>
  		/// Returns if this mapping is defined as column
  		/// </summary>
  		/// <remarks>
  		/// Column mappings can't have result set and can't be defined in Boundary Adaptor
  		/// </remarks>
  		public bool IsColumnMapping {
  			get { return (isColumnMapping); }
  			set {
  				if (value == true)
  					if (Adaptor.IsBoundaryAdaptor == true)
  						throw new ExceptionColumnInBoundaryMapping();
  				isColumnMapping = value;
  				if (IsColumnMapping == true)
  					AllowedToWrite = false;
  			}
  		}
  		
  		private ChildMappedProperties submappings = null;
  		/// <summary>
  		/// Column submapings
  		/// </summary>
  		public ChildMappedProperties Submappings {
  			get { return (submappings); }
  		}
  		
  		/// <summary>
  		/// Returns true if this mapped property has submappings
  		/// </summary>
  		public bool HasSubmappings {
  			get {
  				if (submappings == null)
  					return (false);
  				return (submappings.Count > 0);
  			}
  		}
  		
  		/// <summary>
  		/// Returns if this is targeted value
  		/// </summary>
  		public bool IsTargeted {
  			get { return ((IsDefault == false) && (this.MappingTarget != "")); }
  		}
  		
  		/// <summary>
  		/// Returns if this is targeted value
  		/// </summary>
  		public bool IsSecondaryTargeted {
  			get { return ((IsDefault == false) && (IsColumnMapping == false) && (IsGlobal == false)); }
  		}
  		
  		private string boundingClassName = "";
  		/// <summary>
  		/// Sets global class boundary mapping
  		/// </summary>
  		public string BoundingClassName {
  			get { return (boundingClassName); }
  			set {
  				if (value != "")
  					if (Adaptor.IsBoundaryAdaptor == false)
  						throw new ExceptionBoundaryMappingSetToNonBoundary();
  				boundingClassName = value;
  			}
  		}
  		
  		/// <summary>
  		/// Returns data directions as string
  		/// </summary>
  		private string ReadWriteString {
  			get { 
  				if (IsDefault == true)
  					return ("<>");
  				if (IsGlobal == true)
  					return (">>");
  				if (IsColumnMapping == true)
  					if (allowedToWrite == true)
  						return ("<>");
  					else
  						return ("<<");
  				if ((AllowedToRead == true) && (AllowedToWrite == true))
  					return ("<>");
  				if (AllowedToRead == true)
  					return (">>");
  				if (AllowedToWrite == true)
  					return ("<<");
  				return ("<<");
  			}
  		}
  		
  		/// <summary>
  		/// Read Write flags for this mapped property
  		/// </summary>
  		public EReadWrite RWFlags {
  			get {
  				if (allowedToRead == true)
  					if (allowedToWrite == true)
  						return (EReadWrite.ReadWrite);
  					else
  						return (EReadWrite.ReadOnly);
  				else
  					if (allowedToWrite == true)
  						return (EReadWrite.WriteOnly);
  					else
  						return (EReadWrite.None);
  			}
  			set {
  				allowedToRead = ((value == EReadWrite.ReadOnly) || (value == EReadWrite.ReadWrite));
  				allowedToWrite = ((value == EReadWrite.WriteOnly) || (value == EReadWrite.ReadWrite));
  			}
  		}
  		
  		private EReadWrite originalRWFlags = EReadWrite.ReadOnly;
  		/// <summary>
  		/// Returns original RWFlags, because in some cases they are needed, for example
  		/// treeview can contain multiple types and has to resolve this differently
  		/// </summary>
  		public EReadWrite OriginalRWFlags {
  			get { return (originalRWFlags); }
  		}
  		
  		private bool allowedToRead = true;
  		/// <summary>
  		/// Returns if this mapping can read from DataSource
  		/// </summary>
  		public bool AllowedToRead {
  			get { return (allowedToRead && Valid); }
  			set { allowedToRead = value; }
  		}
  		
  		private bool allowedToWrite = true;
  		/// <summary>
  		/// Returns if this mapping can write to DataSource
  		/// </summary>
  		public bool AllowedToWrite {
  			get {
  				if (IsGlobal == true)
  					return (false);
  				return ((allowedToWrite == true) && (Valid == true));
  			}
  			set { 
  				allowedToWrite = value;
  				// Throw exception only when AllowedToRead was intentionally set but not allowed
  				if ((value == true) && (IsGlobal == true))
  					throw new ExceptionPropertyWriteNotPossible();
  			}
  		}
  		
  		/// <summary>
  		/// Checks if this is Global Boundary mapping
  		/// </summary>
  		public bool IsGlobal {
  			get {
  				if (Adaptor.IsBoundaryAdaptor == false)
  					return (false);
  				return (boundingClassName != "");
  			}
  		}
  		
  		/// <summary>
  		/// Returns information about this mapped property is boundary or not
  		/// </summary>
  		public bool IsBoundaryMapping {
  			get { return ((Adaptor.IsBoundaryAdaptor == true) && (mappingTarget != "")); }
  		}
  		
  		/// <summary>
  		/// Checks if this is default mapped property
  		/// </summary>
  		/// <remarks>
  		/// Only non-Boundary adaptors can have default properties. Assigning default property
  		/// to BoundaryAdaptor will result in Exception
  		/// </remarks>
  		public bool IsDefault {
  			get { return ((Adaptor.IsBoundaryAdaptor == false) && (MappingTarget == "") && (IsColumnMapping == false)); }
  		}
  		
  		private bool isNormalProperty = false;
  
  #if OLDCACHE
  		private PropertyInfo cachedInfo;
  		/// <summary>
  		/// Resolves current mapping property the fastest way possible
  		/// </summary>
  		private PropertyInfo Info {
  			get { 
  				if (Resolve() == true)
  					return (cachedInfo);
  				return (null);
  			}
  		}
  #endif
  		
  		/// <summary>
  		/// Resolves mapped type
  		/// </summary>
  		/// <returns>
  		/// Type  <see cref="System.Type"/>
  		/// </returns>
  		public System.Type GetMappedType()
  		{
  			if (Resolve() == true)
  #if NEWCACHE
  				return (dataCache.PropertyType);
  #endif
  #if OLDCACHE
  				return (cachedInfo.GetType());
  #endif
  			return (null);
  		}
  		
  		/// <summary>
  		// Returns validity of this Mapping
  		/// </summary>
  		public bool Valid {
  			get { 
  #if NEWCACHE
  //				if ((this.Name == "") || (Adaptor.InheritedTarget == null))
  				if ((this.Name == "") || (Adaptor.FinalTarget == null))
  					return (false);
  
  				if (Resolve() == true)
  					return (dataCache.IsAccessValid == EPropertyAccess.Valid);
  				return (false);
  #endif
  #if OLDCACHE
  				if (cached == false)
  					if (Adaptor.InheritedTarget == true) {
  						if (Resolve() == false) 
  							return (false);
  					}
  					else
  						return (false);
  				
  				return (! (invalid));
  #endif
  			}
  		}
  		
  		/// <summary>
  		/// Get and Set the Value of the Mapping
  		/// </summary>
  		public object Value {
  			get {
  				if (AllowedToRead == true)
  					return (GetValue());
  				return (null);
  			}
  			set {
  				// Only non-boundary Adaptors can set value back to the object
  				if (Adaptor.IsBoundaryAdaptor == false) {
  					if (AllowedToWrite == true)
  						SetValue (value);
  					else
  						throw new ExceptionPropertyWriteNotPossible();
  				}
  			}
  		}
  		
  		/// <summary>
  		/// Sets read write from string
  		/// </summary>
  		public void SetReadWrite (string aStr)
  		{
  			switch (aStr) {
  				case ">>" :
  					AllowedToRead = true;
  					AllowedToWrite = false;
  					break;
  				case "<<" :
  					AllowedToRead = false;
  					AllowedToWrite = true;
  					break;
  				case "<>" :
  					AllowedToRead = true;
  					AllowedToWrite = true;
  					break;
  				default :
  					throw new ExceptionAssigningWrongReadWriteMapping (aStr);
  			}
  		}
  		
  		/// <summary>
  		/// Resets PropertyInfo
  		/// </summary>
  		public void Reset()
  		{
  #if NEWCACHE
  			resetResolve = true;
  //			dataCache.SetObject (null);
  #endif
  #if OLDCACHE
  			cached = false;
  			cachedInfo = null;
  #endif
  		}
  		
  		/// <summary>
  		/// Resoves PropertyInfo from connected object
  		/// </summary>
  		public bool Resolve()
  		{
  #if NEWCACHE
  			if (dataCache == null)
  				dataCache = new CachedProperty (Adaptor.FinalTarget, Name);
  			if (resetResolve == true) {
  				dataCache.SetObject (Adaptor.FinalTarget);
  				dataCache.SetProperty (Name.Trim());
  			}
  /*			if (dataCache.IsAccessValid == EPropertyAccess.Invalid)
  				if (Adaptor.FinalTarget != null)	{
  					dataCache.SetObject (Adaptor.FinalTarget);
  					dataCache.SetProperty (Name.Trim());
  				}*/
  			return (dataCache.IsAccessValid == EPropertyAccess.Valid);
  #endif			
  #if OLDCACHE
  			if (cached == true)
  				return (true);
  				
  			if (name == "")
  				return (false);
  			
  			cached = false;
  			cachedInfo = null;
  			if (Adaptor.FinalTarget != null)
  				cachedInfo = ConnectionProvider.ResolveMappingProperty (Adaptor.FinalTarget, Name, false);
  				
  			cached = (cachedInfo != null);
  			invalid = (cachedInfo == null);
  			return (cachedInfo != null);
  #endif
  		}
  		
  		/// <summary>
  		/// Connects on adaptors OnTargetChange
  		/// </summary>
  		public void Connect()
  		{
  			if (Adaptor != null)
  				Adaptor.TargetChanged += TargetChanged;
  		}
  		
  		/// <summary>
  		/// Gets value from referenced Target
  		/// </summary>
  		public object GetValue ()
  		{
  #if NEWCACHE
  			object val = null;
  			if (Resolve() == true)
  //				dataCache.GetValue (Adaptor.FinalTarget, Name, out val);
  				dataCache.GetValue (out val);
  			return (val);
  #endif
  #if OLDCACHE
  //			if (cached == false)
  //				Name = name;
  			if ((cached == false) && (Adaptor.InheritedTarget == false))
  				return (null);
  			if (cached == false)
  				if (Adaptor.InheritedTarget == true) {
  					if (Resolve() == false)
  						return (null);
  				}
  				else
  					return (null);
  			
  			if (Info.CanRead == true)
  
gt: 				return (Info.GetValue (Adaptor.FinalTarget, null));