/nhibernate/src/NHibernate/Collection/PersistentBag.cs

https://bitbucket.org/fabiomaulo/nhibernate/ · C# · 542 lines · 411 code · 74 blank · 57 comment · 38 complexity · 9d2ceac37cca12449847cc3489d7924a MD5 · raw file

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Data;
  5. using System.Diagnostics;
  6. using NHibernate.DebugHelpers;
  7. using NHibernate.Engine;
  8. using NHibernate.Loader;
  9. using NHibernate.Persister.Collection;
  10. using NHibernate.Type;
  11. using NHibernate.Util;
  12. namespace NHibernate.Collection
  13. {
  14. /// <summary>
  15. /// An unordered, unkeyed collection that can contain the same element
  16. /// multiple times. The .NET collections API has no Bag class.
  17. /// Most developers seem to use <see cref="IList" />s to represent bag semantics,
  18. /// so NHibernate follows this practice.
  19. /// </summary>
  20. [Serializable]
  21. [DebuggerTypeProxy(typeof (CollectionProxy))]
  22. public class PersistentBag : AbstractPersistentCollection, IList
  23. {
  24. protected IList bag;
  25. public PersistentBag() {} // needed for serialization
  26. public PersistentBag(ISessionImplementor session) : base(session) {}
  27. public PersistentBag(ISessionImplementor session, ICollection coll) : base(session)
  28. {
  29. bag = coll as IList;
  30. if (bag == null)
  31. {
  32. bag = new ArrayList(coll);
  33. }
  34. SetInitialized();
  35. IsDirectlyAccessible = true;
  36. }
  37. public override bool RowUpdatePossible
  38. {
  39. get { return false; }
  40. }
  41. public override bool IsWrapper(object collection)
  42. {
  43. return bag == collection;
  44. }
  45. public override bool Empty
  46. {
  47. get { return bag.Count == 0; }
  48. }
  49. public override IEnumerable Entries(ICollectionPersister persister)
  50. {
  51. return bag;
  52. }
  53. public override object ReadFrom(IDataReader reader, ICollectionPersister role, ICollectionAliases descriptor,
  54. object owner)
  55. {
  56. // note that if we load this collection from a cartesian product
  57. // the multiplicity would be broken ... so use an idbag instead
  58. object element = role.ReadElement(reader, owner, descriptor.SuffixedElementAliases, Session);
  59. // NH Different behavior : we don't check for null
  60. // The NH-750 test show how checking for null we are ignoring the not-found tag and
  61. // the DB may have some records ignored by NH. This issue may need some more deep consideration.
  62. //if (element != null)
  63. bag.Add(element);
  64. return element;
  65. }
  66. public override void BeforeInitialize(ICollectionPersister persister, int anticipatedSize)
  67. {
  68. bag = (IList) persister.CollectionType.Instantiate(anticipatedSize);
  69. }
  70. public override bool EqualsSnapshot(ICollectionPersister persister)
  71. {
  72. IType elementType = persister.ElementType;
  73. EntityMode entityMode = Session.EntityMode;
  74. IList sn = (IList) GetSnapshot();
  75. if (sn.Count != bag.Count)
  76. {
  77. return false;
  78. }
  79. foreach (object elt in bag)
  80. {
  81. if (CountOccurrences(elt, bag, elementType, entityMode) != CountOccurrences(elt, sn, elementType, entityMode))
  82. {
  83. return false;
  84. }
  85. }
  86. return true;
  87. }
  88. public override bool IsSnapshotEmpty(object snapshot)
  89. {
  90. return ((ICollection) snapshot).Count == 0;
  91. }
  92. /// <summary>
  93. /// Counts the number of times that the <paramref name="element"/> occurs
  94. /// in the <paramref name="list"/>.
  95. /// </summary>
  96. /// <param name="element">The element to find in the list.</param>
  97. /// <param name="list">The <see cref="IList"/> to search.</param>
  98. /// <param name="elementType">The <see cref="IType"/> that can determine equality.</param>
  99. /// <param name="entityMode">The entity mode.</param>
  100. /// <returns>
  101. /// The number of occurrences of the element in the list.
  102. /// </returns>
  103. private static int CountOccurrences(object element, IList list, IType elementType, EntityMode entityMode)
  104. {
  105. int result = 0;
  106. foreach (object obj in list)
  107. {
  108. if (elementType.IsSame(element, obj, entityMode))
  109. {
  110. result++;
  111. }
  112. }
  113. return result;
  114. }
  115. public override ICollection GetSnapshot(ICollectionPersister persister)
  116. {
  117. EntityMode entityMode = Session.EntityMode;
  118. List<object> clonedList = new List<object>(bag.Count);
  119. foreach (object current in bag)
  120. {
  121. clonedList.Add(persister.ElementType.DeepCopy(current, entityMode, persister.Factory));
  122. }
  123. return clonedList;
  124. }
  125. public override ICollection GetOrphans(object snapshot, string entityName)
  126. {
  127. IList sn = (IList) snapshot;
  128. return GetOrphans(sn, bag, entityName, Session);
  129. }
  130. public override object Disassemble(ICollectionPersister persister)
  131. {
  132. int length = bag.Count;
  133. object[] result = new object[length];
  134. for (int i = 0; i < length; i++)
  135. {
  136. result[i] = persister.ElementType.Disassemble(bag[i], Session, null);
  137. }
  138. return result;
  139. }
  140. /// <summary>
  141. /// Initializes this PersistentBag from the cached values.
  142. /// </summary>
  143. /// <param name="persister">The CollectionPersister to use to reassemble the PersistentBag.</param>
  144. /// <param name="disassembled">The disassembled PersistentBag.</param>
  145. /// <param name="owner">The owner object.</param>
  146. public override void InitializeFromCache(ICollectionPersister persister, object disassembled, object owner)
  147. {
  148. object[] array = (object[]) disassembled;
  149. int size = array.Length;
  150. BeforeInitialize(persister, size);
  151. for (int i = 0; i < size; i++)
  152. {
  153. object element = persister.ElementType.Assemble(array[i], Session, owner);
  154. if (element != null)
  155. {
  156. bag.Add(element);
  157. }
  158. }
  159. }
  160. /// <summary>
  161. /// Gets a <see cref="Boolean"/> indicating if this PersistentBag needs to be recreated
  162. /// in the database.
  163. /// </summary>
  164. /// <param name="persister"></param>
  165. /// <returns>
  166. /// <see langword="false" /> if this is a <c>one-to-many</c> Bag, <see langword="true" /> if this is not
  167. /// a <c>one-to-many</c> Bag. Since a Bag is an unordered, unindexed collection
  168. /// that permits duplicates it is not possible to determine what has changed in a
  169. /// <c>many-to-many</c> so it is just recreated.
  170. /// </returns>
  171. public override bool NeedsRecreate(ICollectionPersister persister)
  172. {
  173. return !persister.IsOneToMany;
  174. }
  175. // For a one-to-many, a <bag> is not really a bag;
  176. // it is *really* a set, since it can't contain the
  177. // same element twice. It could be considered a bug
  178. // in the mapping dtd that <bag> allows <one-to-many>.
  179. // Anyway, here we implement <set> semantics for a
  180. // <one-to-many> <bag>!
  181. public override IEnumerable GetDeletes(ICollectionPersister persister, bool indexIsFormula)
  182. {
  183. IType elementType = persister.ElementType;
  184. EntityMode entityMode = Session.EntityMode;
  185. List<object> deletes = new List<object>();
  186. IList sn = (IList) GetSnapshot();
  187. int i = 0;
  188. foreach (object old in sn)
  189. {
  190. bool found = false;
  191. if (bag.Count > i && elementType.IsSame(old, bag[i++], entityMode))
  192. {
  193. //a shortcut if its location didn't change!
  194. found = true;
  195. }
  196. else
  197. {
  198. foreach (object newObject in bag)
  199. {
  200. if (elementType.IsSame(old, newObject, entityMode))
  201. {
  202. found = true;
  203. break;
  204. }
  205. }
  206. }
  207. if (!found)
  208. {
  209. deletes.Add(old);
  210. }
  211. }
  212. return deletes;
  213. }
  214. public override bool NeedsInserting(object entry, int i, IType elemType)
  215. {
  216. IList sn = (IList) GetSnapshot();
  217. EntityMode entityMode = Session.EntityMode;
  218. if (sn.Count > i && elemType.IsSame(sn[i], entry, entityMode))
  219. {
  220. // a shortcut if its location didn't change
  221. return false;
  222. }
  223. else
  224. {
  225. //search for it
  226. foreach (object old in sn)
  227. {
  228. if (elemType.IsEqual(old, entry, entityMode))
  229. {
  230. return false;
  231. }
  232. }
  233. return true;
  234. }
  235. }
  236. public override bool NeedsUpdating(object entry, int i, IType elemType)
  237. {
  238. return false;
  239. }
  240. public override object GetIndex(object entry, int i, ICollectionPersister persister)
  241. {
  242. throw new NotSupportedException("Bags don't have indexes");
  243. }
  244. public override object GetElement(object entry)
  245. {
  246. return entry;
  247. }
  248. public override object GetSnapshotElement(object entry, int i)
  249. {
  250. IList sn = (IList) GetSnapshot();
  251. return sn[i];
  252. }
  253. public override bool EntryExists(object entry, int i)
  254. {
  255. return entry != null;
  256. }
  257. public override string ToString()
  258. {
  259. Read();
  260. return StringHelper.CollectionToString(bag);
  261. }
  262. #region IList Members
  263. public bool IsReadOnly
  264. {
  265. get { return false; }
  266. }
  267. public object this[int index]
  268. {
  269. get
  270. {
  271. Read();
  272. return bag[index];
  273. }
  274. set
  275. {
  276. Write();
  277. bag[index] = value;
  278. }
  279. }
  280. public void RemoveAt(int index)
  281. {
  282. Write();
  283. bag.RemoveAt(index);
  284. }
  285. public void Insert(int index, object value)
  286. {
  287. Write();
  288. bag.Insert(index, value);
  289. }
  290. public void Remove(object value)
  291. {
  292. Initialize(true);
  293. // NH: Different implementation: we use the count to know if the value was removed (better performance)
  294. int contained = bag.Count;
  295. bag.Remove(value);
  296. if (contained != bag.Count)
  297. {
  298. Dirty();
  299. }
  300. }
  301. public bool Contains(object value)
  302. {
  303. bool? exists = ReadElementExistence(value);
  304. return !exists.HasValue ? bag.Contains(value) : exists.Value;
  305. }
  306. public void Clear()
  307. {
  308. if (ClearQueueEnabled)
  309. {
  310. QueueOperation(new ClearDelayedOperation(this));
  311. }
  312. else
  313. {
  314. Initialize(true);
  315. if (!(bag.Count == 0))
  316. {
  317. bag.Clear();
  318. Dirty();
  319. }
  320. }
  321. }
  322. public int IndexOf(object value)
  323. {
  324. Read();
  325. return bag.IndexOf(value);
  326. }
  327. public int Add(object value)
  328. {
  329. if (!IsOperationQueueEnabled)
  330. {
  331. Write();
  332. return bag.Add(value);
  333. }
  334. else
  335. {
  336. QueueOperation(new SimpleAddDelayedOperation(this, value));
  337. //TODO: take a look at this - I don't like it because it changes the
  338. // meaning of Add - instead of returning the index it was added at
  339. // returns a "fake" index - not consistent with IList interface...
  340. return -1;
  341. }
  342. }
  343. public bool IsFixedSize
  344. {
  345. get { return false; }
  346. }
  347. #endregion
  348. #region ICollection Members
  349. public bool IsSynchronized
  350. {
  351. get { return false; }
  352. }
  353. public int Count
  354. {
  355. get { return ReadSize() ? CachedSize : bag.Count; }
  356. }
  357. public void CopyTo(Array array, int index)
  358. {
  359. for (int i = index; i < Count; i++)
  360. {
  361. array.SetValue(this[i], i);
  362. }
  363. }
  364. public object SyncRoot
  365. {
  366. get { return this; }
  367. }
  368. #endregion
  369. #region IEnumerable Members
  370. public IEnumerator GetEnumerator()
  371. {
  372. Read();
  373. return bag.GetEnumerator();
  374. }
  375. #endregion
  376. public override bool AfterInitialize(ICollectionPersister persister)
  377. {
  378. // NH Different behavior : NH-739
  379. // would be nice to prevent this overhead but the operation is managed where the ICollectionPersister is not available
  380. bool result;
  381. if (persister.IsOneToMany && HasQueuedOperations)
  382. {
  383. int additionStartFrom = bag.Count;
  384. IList additionQueue = new List<object>(additionStartFrom);
  385. foreach (object o in QueuedAdditionIterator)
  386. {
  387. if (o != null)
  388. {
  389. for (int i = 0; i < bag.Count; i++)
  390. {
  391. // we are using ReferenceEquals to be sure that is exactly the same queued instance
  392. if (ReferenceEquals(o, bag[i]))
  393. {
  394. additionQueue.Add(o);
  395. break;
  396. }
  397. }
  398. }
  399. }
  400. result = base.AfterInitialize(persister);
  401. if(!result)
  402. {
  403. // removing duplicated additions
  404. foreach (object o in additionQueue)
  405. {
  406. for (int i = additionStartFrom; i < bag.Count; i++)
  407. {
  408. if (ReferenceEquals(o, bag[i]))
  409. {
  410. bag.RemoveAt(i);
  411. break;
  412. }
  413. }
  414. }
  415. }
  416. }
  417. else
  418. {
  419. result = base.AfterInitialize(persister);
  420. }
  421. return result;
  422. }
  423. #region DelayedOperations
  424. protected sealed class ClearDelayedOperation : IDelayedOperation
  425. {
  426. private readonly PersistentBag enclosingInstance;
  427. public ClearDelayedOperation(PersistentBag enclosingInstance)
  428. {
  429. this.enclosingInstance = enclosingInstance;
  430. }
  431. public object AddedInstance
  432. {
  433. get { return null; }
  434. }
  435. public object Orphan
  436. {
  437. get { throw new NotSupportedException("queued clear cannot be used with orphan delete"); }
  438. }
  439. public void Operate()
  440. {
  441. enclosingInstance.bag.Clear();
  442. }
  443. }
  444. protected sealed class SimpleAddDelayedOperation : IDelayedOperation
  445. {
  446. private readonly PersistentBag enclosingInstance;
  447. private readonly object value;
  448. public SimpleAddDelayedOperation(PersistentBag enclosingInstance, object value)
  449. {
  450. this.enclosingInstance = enclosingInstance;
  451. this.value = value;
  452. }
  453. public object AddedInstance
  454. {
  455. get { return value; }
  456. }
  457. public object Orphan
  458. {
  459. get { return null; }
  460. }
  461. public void Operate()
  462. {
  463. enclosingInstance.bag.Add(value);
  464. }
  465. }
  466. #endregion
  467. }
  468. }