[ http://issues.apache.org/jira/browse/IBATISNET-119?page=all ] Gilles Bayon resolved IBATISNET-119: ------------------------------------
Fix Version: DataMapper 1.3 Resolution: Fixed In SVN > CacheKey.Equals(object)/HashCode issue still a problem (was IBATISNET-118) > -------------------------------------------------------------------------- > > Key: IBATISNET-119 > URL: http://issues.apache.org/jira/browse/IBATISNET-119 > Project: iBatis for .NET > Type: Bug > Components: DataMapper > Versions: DataMapper 1.2.1 > Environment: Windows > Reporter: Thomas Tannahill > Assignee: Gilles Bayon > Fix For: DataMapper 1.3 > > The fix for IBATISNET-118 does not address the issue (just hides it in one > particular instance). > The issue is that you can not use HashCodes to determine object equality. > Unique objects do not always have unique HashCodes (no matter how hard you > try). There just aren't enough hashcodes to go around :) > I understand why my previous 'patch' wasn't accepted (didn't expect it to > be). Since we are not using an object's own "GetHashCode()" to get it's > hashcode, we can not use it's own "Equals" to determine equality. So we > really have to do is create a new method on ObjectProbe that compares two > objects in much the same way that ObjectProbe generates hashcodes. I was > just too lazy to write it yesterday :) > Here is an updated unit test and a potential fix... > --- BEGIN UNIT TEST --- > using IBatisNet.DataMapper; > using IBatisNet.DataMapper.TypeHandlers; > using NUnit.Framework; > namespace IBatisNet.DataMapper.Test.NUnit.SqlMapTests > { > [TestFixture] > public class CacheKeyTest > { > [Test] > public void > ShouldNotConsider1LAndNegative9223372034707292159LToBeEqual() > { > // old version of ObjectProbe gave TestClass based on > these longs the same HashCode > DoTestClassEquals(1L, -9223372034707292159L); > } > [Test] > public void > ShouldNotConsider1LAndNegative9223372036524971138LToBeEqual() > { > // current version of ObjectProbe gives TestClass based > on these longs the same HashCode > DoTestClassEquals(1L, -9223372036524971138L); > } > private static void DoTestClassEquals(long firstLong, long > secondLong) > { > TypeHandlerFactory factory = new TypeHandlerFactory(); > > // Two cache keys are equal except for the parameter. > CacheKey key = new CacheKey(factory, "STATEMENT", > "SQL", new TestClass(firstLong), new string[] {"AProperty"}, 0, 0, > CacheKeyType.Object); > CacheKey aDifferentKey = new CacheKey(factory, > "STATEMENT", "SQL", new TestClass(secondLong), new string[] {"AProperty"}, 0, > 0, CacheKeyType.Object); > > Assert.IsFalse(aDifferentKey.Equals(key)); // should > not be equal. > } > private class TestClass > { > private long _property = long.MinValue; > public TestClass(long aProperty) > { > _property = aProperty; > } > public long AProperty > { > get { return _property; } > set { _property = value; } > } > } > } > } > --- END UNIT TEST --- > FIX (not sure if this will break other parts of the system... but it > shouldn't): > --- BEGIN ObjectProbe --- > #region Apache Notice > /***************************************************************************** > * $Header: $ > * $Revision: $ > * $Date: $ > * > * iBATIS.NET Data Mapper > * Copyright (C) 2004 - Gilles Bayon > * > * > * Licensed under the Apache License, Version 2.0 (the "License"); > * you may not use this file except in compliance with the License. > * You may obtain a copy of the License at > * > * http://www.apache.org/licenses/LICENSE-2.0 > * > * Unless required by applicable law or agreed to in writing, software > * distributed under the License is distributed on an "AS IS" BASIS, > * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. > * See the License for the specific language governing permissions and > * limitations under the License. > * > > ********************************************************************************/ > #endregion > using System; > using System.Collections; > using System.Reflection; > using IBatisNet.Common.Exceptions; > namespace IBatisNet.Common.Utilities.Objects > { > /// <summary> > /// Description résumée de ObjectProbe. > /// </summary> > public class ObjectProbe > { > private static ArrayList _simpleTypeMap = new ArrayList(); > static ObjectProbe() > { > _simpleTypeMap.Add(typeof(string)); > _simpleTypeMap.Add(typeof(Byte)); > _simpleTypeMap.Add(typeof(Int16)); > _simpleTypeMap.Add(typeof(char)); > _simpleTypeMap.Add(typeof(Int32)); > _simpleTypeMap.Add(typeof(Int64)); > _simpleTypeMap.Add(typeof(Single)); > _simpleTypeMap.Add(typeof(Double)); > _simpleTypeMap.Add(typeof(Boolean)); > _simpleTypeMap.Add(typeof(DateTime)); > _simpleTypeMap.Add(typeof(Decimal)); > // > _simpleTypeMap.Add(typeof(Hashtable)); > // > _simpleTypeMap.Add(typeof(SortedList)); > // > _simpleTypeMap.Add(typeof(ArrayList)); > // > _simpleTypeMap.Add(typeof(Array)); > // > simpleTypeMap.Add(LinkedList.class); > // > simpleTypeMap.Add(HashSet.class); > // > simpleTypeMap.Add(TreeSet.class); > // simpleTypeMap.Add(Vector.class); > // > simpleTypeMap.Add(Hashtable.class); > _simpleTypeMap.Add(typeof(SByte)); > _simpleTypeMap.Add(typeof(UInt16)); > _simpleTypeMap.Add(typeof(UInt32)); > _simpleTypeMap.Add(typeof(UInt64)); > _simpleTypeMap.Add(typeof(IEnumerator)); > } > /// <summary> > /// Returns an array of the readable properties names exposed > by an object > /// </summary> > /// <param name="obj">The object</param> > /// <returns>The properties name</returns> > public static string[] GetReadablePropertyNames(object obj) > { > return > ReflectionInfo.GetInstance(obj.GetType()).GetReadablePropertyNames(); > } > > /// <summary> > /// Returns an array of the writeable properties name exposed > by a object > /// </summary> > /// <param name="obj">The object</param> > /// <returns>The properties name</returns> > public static string[] GetWriteablePropertyNames(object obj) > { > return > ReflectionInfo.GetInstance(obj.GetType()).GetWriteablePropertyNames(); > } > /// <summary> > /// Returns the type that the set expects to receive as a > parameter when > /// setting a property value. > /// </summary> > /// <param name="obj">The object to check</param> > /// <param name="propertyName">The name of the property</param> > /// <returns>The type of the property</returns> > private static Type GetPropertyTypeForSetter(object obj, string > propertyName) > { > Type type = obj.GetType(); > if (obj is IDictionary) > { > IDictionary map = (IDictionary) obj; > object value = map[propertyName]; > if (value == null) > { > type = typeof(object); > } > else > { > type = value.GetType(); > } > } > else > { > if (propertyName.IndexOf('.') > -1) > { > StringTokenizer parser = new > StringTokenizer(propertyName, "."); > IEnumerator enumerator = > parser.GetEnumerator(); > while (enumerator.MoveNext()) > { > propertyName = > (string)enumerator.Current; > type = > ReflectionInfo.GetInstance(type).GetSetterType(propertyName); > } > } > else > { > type = > ReflectionInfo.GetInstance(type).GetSetterType(propertyName); > } > } > return type; > } > /// <summary> > /// Returns the type that the set expects to receive as a > parameter when > /// setting a property value. > /// </summary> > /// <param name="type">The type to check</param> > /// <param name="propertyName">The name of the property</param> > /// <returns>The type of the property</returns> > private static Type GetPropertyTypeForSetter(Type type, string > propertyName) > { > if (propertyName.IndexOf('.') > -1) > { > StringTokenizer parser = new > StringTokenizer(propertyName, "."); > IEnumerator enumerator = parser.GetEnumerator(); > while (enumerator.MoveNext()) > { > propertyName = > (string)enumerator.Current; > type = > ReflectionInfo.GetInstance(type).GetSetterType(propertyName); > } > } > else > { > type = > ReflectionInfo.GetInstance(type).GetSetterType(propertyName); > } > return type; > } > /// <summary> > /// Returns the type that the get expects to receive as a > parameter when > /// setting a property value. > /// </summary> > /// <param name="obj">The object to check</param> > /// <param name="propertyName">The name of the property</param> > /// <returns>The type of the property</returns> > public static Type GetPropertyTypeForGetter(object obj, string > propertyName) > { > Type type = obj.GetType(); > if (obj is IDictionary) > { > IDictionary map = (IDictionary) obj; > object value = map[propertyName]; > if (value == null) > { > type = typeof(object); > } > else > { > type = value.GetType(); > } > } > else > { > if (propertyName.IndexOf('.') > -1) > { > StringTokenizer parser = new > StringTokenizer(propertyName, "."); > IEnumerator enumerator = > parser.GetEnumerator(); > while (enumerator.MoveNext()) > { > propertyName = > (string)enumerator.Current; > type = > ReflectionInfo.GetInstance(type).GetGetterType(propertyName); > } > } > else > { > type = > ReflectionInfo.GetInstance(type).GetGetterType(propertyName); > } > } > return type; > } > /// <summary> > /// Returns the type that the get expects to receive as a > parameter when > /// setting a property value. > /// </summary> > /// <param name="type">The type to check</param> > /// <param name="propertyName">The name of the property</param> > /// <returns>The type of the property</returns> > public static Type GetPropertyTypeForGetter(Type type, string > propertyName) > { > if (propertyName.IndexOf('.') > -1) > { > StringTokenizer parser = new > StringTokenizer(propertyName, "."); > IEnumerator enumerator = parser.GetEnumerator(); > while (enumerator.MoveNext()) > { > propertyName = > (string)enumerator.Current; > type = > ReflectionInfo.GetInstance(type).GetGetterType(propertyName); > } > } > else > { > type = > ReflectionInfo.GetInstance(type).GetGetterType(propertyName); > } > return type; > } > private static object GetArrayProperty(object obj, string > indexedName) > { > object value = null; > try > { > int startIndex = indexedName.IndexOf("["); > int length = indexedName.IndexOf("]"); > string name = indexedName.Substring(0, > startIndex); > string index = indexedName.Substring( > startIndex+1, length-(startIndex+1)); > int i = System.Convert.ToInt32(index); > > if (name.Length > 0) > { > value = GetProperty(obj, name); > } > else > { > value = obj; > } > if (value is IList) > { > value = ((IList) value)[i]; > } > else > { > throw new ProbeException("The '" + name > + "' property of the " + obj.GetType().Name + " class is not a List or > Array."); > } > } > catch (ProbeException pe) > { > throw pe; > } > catch(Exception e) > { > throw new ProbeException("Error getting ordinal > value from .net object. Cause" + e.Message, e); > } > return value; > } > /// <summary> > /// > /// </summary> > /// <param name="obj"></param> > /// <param name="propertyName"></param> > /// <returns></returns> > protected static object GetProperty(object obj, string > propertyName) > { > ReflectionInfo reflectionCache = > ReflectionInfo.GetInstance(obj.GetType()); > try > { > object value = null; > if (propertyName.IndexOf("[") > -1) > { > value = GetArrayProperty(obj, > propertyName); > } > else > { > if (obj is IDictionary) > { > value = ((IDictionary) > obj)[propertyName]; > } > else > { > PropertyInfo propertyInfo = > reflectionCache.GetGetter(propertyName); > if (propertyInfo == null) > { > throw new > ProbeException("No Get method for property " + propertyName + " on instance > of " + obj.GetType().Name); > } > try > { > value = > propertyInfo.GetValue(obj, null); > } > catch (ArgumentException ae) > { > throw new > ProbeException(ae); > } > catch (TargetException t) > { > throw new > ProbeException(t); > } > catch > (TargetParameterCountException tp) > { > throw new > ProbeException(tp); > } > catch (MethodAccessException > ma) > { > throw new > ProbeException(ma); > } > > } > } > return value; > } > catch (ProbeException pe) > { > throw pe; > } > catch(Exception e) > { > throw new ProbeException("Could not Set > property '" + propertyName + "' for " + obj.GetType().Name + ". Cause: " + > e.Message, e); > } > } > private static void SetArrayProperty(object obj, string > indexedName, object value) > { > try > { > int startIndex = indexedName.IndexOf("["); > int length = indexedName.IndexOf("]"); > string name = indexedName.Substring(0, > startIndex); > string index = indexedName.Substring( > startIndex+1, length-(startIndex+1)); > int i = System.Convert.ToInt32(index); > > object list = null; > if (name.Length > 0) > { > list = GetProperty(obj, name); > } > else > { > list = obj; > } > if (list is IList) > { > ((IList) list)[i] = value; > } > else > { > throw new ProbeException("The '" + name > + "' property of the " + obj.GetType().Name + " class is not a List or > Array."); > } > } > catch (ProbeException pe) > { > throw pe; > } > catch (Exception e) > { > throw new ProbeException("Error getting ordinal > value from .net object. Cause" + e.Message, e); > } > } > /// <summary> > /// > /// </summary> > /// <param name="obj"></param> > /// <param name="propertyName"></param> > /// <param name="propertyValue"></param> > protected static void SetProperty(object obj, string > propertyName, object propertyValue) > { > ReflectionInfo reflectionCache = > ReflectionInfo.GetInstance(obj.GetType()); > try > { > if (propertyName.IndexOf("[") > -1) > { > SetArrayProperty(obj, propertyName, > propertyValue); > } > else > { > if (obj is IDictionary) > { > ((IDictionary) > obj)[propertyName] = propertyValue; > } > else > { > PropertyInfo propertyInfo = > reflectionCache.GetSetter(propertyName); > > if (propertyInfo == null) > { > throw new > ProbeException("No Set method for property " + propertyName + " on instance > of " + obj.GetType().Name); > } > try > { > > propertyInfo.SetValue(obj, propertyValue, null); > } > catch (ArgumentException ae) > { > throw new > ProbeException(ae); > } > catch (TargetException t) > { > throw new > ProbeException(t); > } > catch > (TargetParameterCountException tp) > { > throw new > ProbeException(tp); > } > catch (MethodAccessException > ma) > { > throw new > ProbeException(ma); > } > > } > } > } > catch (ProbeException pe) > { > throw pe; > } > catch (Exception e) > { > throw new ProbeException("Could not Get > property '" + propertyName + "' for " + obj.GetType().Name + ". Cause: " + > e.Message, e); > } > } > /// <summary> > /// Return the specified property on an object. > /// </summary> > /// <param name="obj">The Object on which to invoke the > specified property.</param> > /// <param name="propertyName">The name of the property.</param> > /// <returns>An Object representing the return value of the > invoked property.</returns> > public static object GetPropertyValue(object obj, string > propertyName) > { > if (propertyName.IndexOf('.') > -1) > { > StringTokenizer parser = new > StringTokenizer(propertyName, "."); > IEnumerator enumerator = parser.GetEnumerator(); > object value = obj; > string token = null; > while (enumerator.MoveNext()) > { > token = (string)enumerator.Current; > value = GetProperty(value, token); > if (value == null) > { > break; > } > } > return value; > } > else > { > return GetProperty(obj, propertyName); > } > } > /// <summary> > /// Set the specified property on an object > /// </summary> > /// <param name="obj">The Object on which to invoke the > specified property.</param> > /// <param name="propertyName">The name of the property to > set.</param> > /// <param name="propertyValue">The new value to set.</param> > public static void SetPropertyValue(object obj, string > propertyName, object propertyValue) > { > if (propertyName.IndexOf('.') > -1) > { > StringTokenizer parser = new > StringTokenizer(propertyName, "."); > IEnumerator enumerator = parser.GetEnumerator(); > enumerator.MoveNext(); > string currentPropertyName = > (string)enumerator.Current; > object child = obj; > > while (enumerator.MoveNext()) > { > Type type = > GetPropertyTypeForSetter(child, currentPropertyName); > object parent = child; > child = GetProperty(parent, > currentPropertyName); > if (child == null) > { > try > { > child = > Activator.CreateInstance(type); > > SetPropertyValue(parent, currentPropertyName, child); > } > catch (Exception e) > { > throw new > ProbeException("Cannot set value of property '" + propertyName + "' because > '" + currentPropertyName + "' is null and cannot be instantiated on instance > of " + type.Name + ". Cause:" + e.Message, e); > } > } > currentPropertyName = > (string)enumerator.Current; > } > SetProperty(child, currentPropertyName, > propertyValue); > } > else > { > SetProperty(obj, propertyName, propertyValue); > } > } > /// <summary> > /// Checks to see if a Object has a writable property/field be > a given name > /// </summary> > /// <param name="obj"> The object to check</param> > /// <param name="propertyName">The property to check for</param> > /// <returns>True if the property exists and is > writable</returns> > public static bool HasWritableProperty(object obj, string > propertyName) > { > bool hasProperty = false; > if (obj is IDictionary) > { > hasProperty = ((IDictionary) > obj).Contains(propertyName); > } > else > { > if (propertyName.IndexOf('.') > -1) > { > StringTokenizer parser = new > StringTokenizer(propertyName, "."); > IEnumerator enumerator = > parser.GetEnumerator(); > Type type = obj.GetType(); > while (enumerator.MoveNext()) > { > propertyName = > (string)enumerator.Current; > type = > ReflectionInfo.GetInstance(type).GetGetterType(propertyName); > hasProperty = > ReflectionInfo.GetInstance(type).HasWritableProperty(propertyName); > } > } > else > { > hasProperty = > ReflectionInfo.GetInstance(obj.GetType()).HasWritableProperty(propertyName); > } > } > return hasProperty; > } > /// <summary> > /// Checks to see if the Object have a property/field be a > given name. > /// </summary> > /// <param name="obj">The Object on which to invoke the > specified property.</param> > /// <param name="propertyName">The name of the property to > check for.</param> > /// <returns> > /// True or false if the property exists and is readable. > /// </returns> > public static bool HasReadableProperty(object obj, string > propertyName) > { > bool hasProperty = false; > if (obj is IDictionary) > { > hasProperty = ((IDictionary) > obj).Contains(propertyName); > } > else > { > if (propertyName.IndexOf('.') > -1) > { > StringTokenizer parser = new > StringTokenizer(propertyName, "."); > IEnumerator enumerator = > parser.GetEnumerator(); > Type type = obj.GetType(); > while (enumerator.MoveNext()) > { > propertyName = > (string)enumerator.Current; > type = > ReflectionInfo.GetInstance(type).GetGetterType(propertyName); > hasProperty = > ReflectionInfo.GetInstance(type).HasReadableProperty(propertyName); > } > } > else > { > hasProperty = > ReflectionInfo.GetInstance(obj.GetType()).HasReadableProperty(propertyName); > } > } > > return hasProperty; > } > /// <summary> > /// > /// </summary> > /// <param name="type"></param> > /// <returns></returns> > public static bool IsSimpleType(Type type) > { > if (_simpleTypeMap.Contains(type)) > { > return true; > } > else if (type.IsSubclassOf(typeof(ICollection))) > { > return true; > } > else if (type.IsSubclassOf(typeof(IDictionary))) > { > return true; > } > else if (type.IsSubclassOf(typeof(IList))) > { > return true; > } > else if (type.IsSubclassOf(typeof(IEnumerable))) > { > return true; > } > else > { > return false; > } > } > /// <summary> > /// Calculates a hash code for all readable properties of a > object. > /// </summary> > /// <param name="obj">The object to calculate the hash code > for.</param> > /// <returns>The hash code.</returns> > public static int ObjectHashCode(object obj) > { > return ObjectHashCode(obj, > GetReadablePropertyNames(obj)); > } > public static int ObjectHashCode(object obj, string[] > properties) > { > ArrayList values = UnwrapObjectDownToSimpleTypes(obj, > properties); > int hashCode = obj.GetType().FullName.GetHashCode(); > foreach (object simpleObject in values) > { > if (simpleObject != null) > { > hashCode += simpleObject.GetHashCode(); > hashCode += > simpleObject.ToString().GetHashCode(); > hashCode *= 29; > } > } > return hashCode; > } > public static bool AreObjectsEqual(object obj1, object obj2) > { > return AreObjectsEqual(obj1, obj2, > GetReadablePropertyNames(obj1)); > } > public static bool AreObjectsEqual(object obj1, object obj2, > string[] properties) > { > if (obj1 == null && obj2 != null) > return false; > if (obj1 != null && obj2 == null) > return false; > if (obj1 == null && obj2 == null) > return true; > if (obj1.GetType() != obj2.GetType()) > return false; > ArrayList obj1Values = > UnwrapObjectDownToSimpleTypes(obj1, properties); > ArrayList obj2Values = > UnwrapObjectDownToSimpleTypes(obj2, properties); > if (obj1Values.Count != obj2Values.Count) > return false; > for (int i = 0; i < obj1Values.Count; i++) > { > if (obj1Values[i] != obj2Values[i]) > return false; > } > return true; > } > public static ArrayList UnwrapObjectDownToSimpleTypes(object > obj, ArrayList objectValues) > { > return UnwrapObjectDownToSimpleTypes(obj, > GetReadablePropertyNames(obj), objectValues); > } > public static ArrayList UnwrapObjectDownToSimpleTypes(object > obj, string[] properties) > { > return UnwrapObjectDownToSimpleTypes(obj, properties, > new ArrayList()); > } > public static ArrayList UnwrapObjectDownToSimpleTypes(object > obj, string[] properties, ArrayList objectValues) > { > ArrayList alreadyDigested = new ArrayList(); > int hashcode = obj.GetType().FullName.GetHashCode(); > for (int i = 0; i < properties.Length; i++) > { > object value = GetProperty(obj, properties[i]); > if (value != null) > { > if (IsSimpleType(value.GetType())) > { > objectValues.Add(value); > } > else > { > // It's a Object > // Check to avoid endless loop > (circular dependency) > if (value != obj) > { > if > (!alreadyDigested.Contains(value)) > { > > alreadyDigested.Add(value); > > UnwrapObjectDownToSimpleTypes(value, objectValues); > } > } > } > } > else > { > objectValues.Add(null); > } > } > return objectValues; > } > } > } > --- END ObjectProbe --- > --- BEGIN CacheKey.Equals(object) --- > public override bool Equals(object obj) > { > //----------------------------------- > if (this == obj) return true; > if (!(obj is CacheKey)) return false; > CacheKey cacheKey = (CacheKey)obj; > if (_maxResults != cacheKey._maxResults) return false; > if (_skipRecords != cacheKey._skipRecords) return false; > if (_type != cacheKey._type) return false; > if (_parameter is Hashtable) > { > if (_hashCode != cacheKey._hashCode) return > false; > if (!_parameter.Equals(cacheKey._parameter)) > return false; > } > else if (_parameter != null && > _typeHandlerFactory.IsSimpleType(_parameter.GetType())) > { > if (_parameter != null ? > !_parameter.Equals(cacheKey._parameter) : cacheKey._parameter != null) return > false; > } > else > { > if (!ObjectProbe.AreObjectsEqual(_parameter, > cacheKey._parameter, _properties)) > { > return false; > } > } > if (_sql != null ? !_sql.Equals(cacheKey._sql) : > cacheKey._sql != null) return false; > return true; > } > --- END CacheKey.Equals(object) --- -- This message is automatically generated by JIRA. - If you think it was sent incorrectly contact one of the administrators: http://issues.apache.org/jira/secure/Administrators.jspa - For more information on JIRA, see: http://www.atlassian.com/software/jira