Use Jenkins Hash (#40)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index deb6d55..eb48144 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+# 1.0.0
+
+- Update hashCode implementation to use `Jenkins Hash` ([#39](https://github.com/felangel/equatable/issues/39)).
+- Documentation Updates
+
 # 0.6.1
 
 - Minor documentation updates
diff --git a/lib/src/equatable_utils.dart b/lib/src/equatable_utils.dart
index e5c8176..778ab4f 100644
--- a/lib/src/equatable_utils.dart
+++ b/lib/src/equatable_utils.dart
@@ -1,27 +1,7 @@
 import 'package:collection/collection.dart';
 
-int mapPropsToHashCode(dynamic props) {
-  int hashCode = 0;
-
-  if (props is Map) {
-    props.forEach((key, value) {
-      final propHashCode = mapPropsToHashCode(key) ^ mapPropsToHashCode(value);
-      hashCode = hashCode ^ propHashCode;
-    });
-  } else if (props is List || props is Iterable || props is Set) {
-    props.forEach((prop) {
-      final propHashCode =
-          (prop is List || prop is Iterable || prop is Set || prop is Map)
-              ? mapPropsToHashCode(prop)
-              : prop.hashCode;
-      hashCode = hashCode ^ propHashCode;
-    });
-  } else {
-    hashCode = hashCode ^ props.hashCode;
-  }
-
-  return hashCode;
-}
+int mapPropsToHashCode(Iterable props) =>
+    _finish(props.fold(0, (hash, object) => _combine(hash, object)));
 
 const DeepCollectionEquality _equality = DeepCollectionEquality();
 
@@ -45,3 +25,26 @@
   }
   return true;
 }
+
+/// Jenkins Hash Functions
+/// https://en.wikipedia.org/wiki/Jenkins_hash_function
+int _combine(int hash, dynamic object) {
+  if (object is Map) {
+    object.forEach((key, value) {
+      hash = _combine(hash, [key, value]);
+    });
+    return hash;
+  }
+  if (object is Iterable) {
+    return mapPropsToHashCode(object);
+  }
+  hash = 0x1fffffff & (hash + object.hashCode);
+  hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
+  return hash ^ (hash >> 6);
+}
+
+int _finish(int hash) {
+  hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
+  hash = hash ^ (hash >> 11);
+  return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index dcd9f3a..f17a414 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: equatable
 description: An abstract class that helps to implement equality without needing to explicitly override == and hashCode.
-version: 0.6.1
+version: 1.0.0
 author: Felix Angelov <felangelov@gmail.com>
 homepage: https://github.com/felangel/equatable
 
diff --git a/test/equatable_mixin_test.dart b/test/equatable_mixin_test.dart
index b1da0ff..7defdbf 100644
--- a/test/equatable_mixin_test.dart
+++ b/test/equatable_mixin_test.dart
@@ -1,5 +1,6 @@
 import 'dart:convert';
 
+import 'package:equatable/src/equatable_utils.dart';
 import 'package:test/test.dart';
 import 'package:equatable/equatable.dart';
 
@@ -102,7 +103,10 @@
 
     test('should return correct hashCode', () {
       final instance = EmptyEquatable();
-      expect(instance.hashCode, instance.runtimeType.hashCode);
+      expect(
+        instance.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+      );
     });
 
     test('should return true when instances are different', () {
@@ -134,7 +138,7 @@
       final instance = SimpleEquatable('simple');
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^ instance.data.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -179,7 +183,7 @@
       final instance = SimpleEquatable(0);
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^ instance.data.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -218,7 +222,7 @@
       final instance = SimpleEquatable(true);
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^ instance.data.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -265,7 +269,7 @@
       ));
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^ instance.data.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -309,6 +313,7 @@
       final instance = MultipartEquatable("s1", "s2");
       expect(instance.toString(), 'MultipartEquatable<String>');
     });
+
     test('should return true when instance is the same', () {
       final instance = MultipartEquatable("s1", "s2");
       expect(instance == instance, true);
@@ -318,12 +323,17 @@
       final instance = MultipartEquatable("s1", "s2");
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^
-            instance.d1.hashCode ^
-            instance.d2.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
+    test('should return different hashCodes when property order has changed',
+        () {
+      final instance1 = MultipartEquatable("s1", "s2");
+      final instance2 = MultipartEquatable("s2", "s1");
+      expect(instance1.hashCode == instance2.hashCode, isFalse);
+    });
+
     test('should return true when instances are different', () {
       final instanceA = MultipartEquatable("s1", "s2");
       final instanceB = MultipartEquatable("s1", "s2");
@@ -377,11 +387,7 @@
       );
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^
-            instance.name.hashCode ^
-            instance.age.hashCode ^
-            instance.hairColor.hashCode ^
-            instance.children[0].hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -466,9 +472,7 @@
       ) as Map<String, dynamic>);
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^
-            instance.username.hashCode ^
-            instance.password.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
diff --git a/test/equatable_test.dart b/test/equatable_test.dart
index d7f1ce6..a35b302 100644
--- a/test/equatable_test.dart
+++ b/test/equatable_test.dart
@@ -1,5 +1,6 @@
 import 'dart:convert';
 
+import 'package:equatable/src/equatable_utils.dart';
 import 'package:test/test.dart';
 
 import 'package:equatable/equatable.dart';
@@ -105,7 +106,10 @@
 
     test('should return correct hashCode', () {
       final instance = EmptyEquatable();
-      expect(instance.hashCode, instance.runtimeType.hashCode);
+      expect(
+        instance.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
+      );
     });
 
     test('should return true when instances are different', () {
@@ -137,7 +141,7 @@
       final instance = SimpleEquatable('simple');
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^ instance.data.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -187,7 +191,7 @@
       final instance = SimpleEquatable(0);
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^ instance.data.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -226,7 +230,7 @@
       final instance = SimpleEquatable(true);
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^ instance.data.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -273,7 +277,7 @@
       ));
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^ instance.data.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -317,6 +321,7 @@
       final instance = MultipartEquatable("s1", "s2");
       expect(instance.toString(), 'MultipartEquatable<String>');
     });
+
     test('should return true when instance is the same', () {
       final instance = MultipartEquatable("s1", "s2");
       expect(instance == instance, true);
@@ -326,12 +331,17 @@
       final instance = MultipartEquatable("s1", "s2");
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^
-            instance.d1.hashCode ^
-            instance.d2.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
+    test('should return different hashCodes when property order has changed',
+        () {
+      final instance1 = MultipartEquatable("s1", "s2");
+      final instance2 = MultipartEquatable("s2", "s1");
+      expect(instance1.hashCode == instance2.hashCode, isFalse);
+    });
+
     test('should return true when instances are different', () {
       final instanceA = MultipartEquatable("s1", "s2");
       final instanceB = MultipartEquatable("s1", "s2");
@@ -385,11 +395,7 @@
       );
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^
-            instance.name.hashCode ^
-            instance.age.hashCode ^
-            instance.hairColor.hashCode ^
-            instance.children[0].hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });
 
@@ -474,9 +480,7 @@
       ) as Map<String, dynamic>);
       expect(
         instance.hashCode,
-        instance.runtimeType.hashCode ^
-            instance.username.hashCode ^
-            instance.password.hashCode,
+        instance.runtimeType.hashCode ^ mapPropsToHashCode(instance.props),
       );
     });