PageRenderTime 21ms CodeModel.GetById 2ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 0ms

/Utilities/Collections/BidirHashtable.cs

#
C# | 504 lines | 277 code | 38 blank | 189 comment | 1 complexity | 2604aa376840e06eef1c0d7ade956862 MD5 | raw file
  1using System;
  2using System.Collections;
  3using NUnit.Framework;
  4
  5namespace Delta.Utilities.Collections
  6{
  7	/// <summary>
  8	/// BidirHashtable is a simple, bidirectional data structure designed
  9	/// around Hashtables and accessed like a more robust Hashtable.
 10	/// Internally it just contains two hashtables:
 11	/// One maps from key to value, the other maps from value to key.
 12	/// Therefore, note that both types of objects must have reasonable
 13	/// GetHashCode() and Equals() implementations. Lookup in either direction
 14	/// is quick; changes take twice as long since two Hashtables are accessed.
 15	/// It is not currently serializable, but aside from that, it implements the
 16	/// same interfaces as a Hashtable. Forward lookup is just through the [] as
 17	/// in Hashtable. Reverse lookup is through ReverseLookup().
 18	/// </summary>
 19	[Serializable]
 20	public class BidirHashtable :
 21		IDictionary, ICollection, IEnumerable, ICloneable
 22	{
 23		#region FromHashtable (Static)
 24		/// <summary>
 25		/// Creates a Bidirectional hashtable from a normal Hashtable.
 26		/// </summary>
 27		/// <param name="setHashtable">Hashtable to initialize from</param>
 28		/// <returns>BidirHashtable with the same data</returns>
 29		public static BidirHashtable FromHashtable(Hashtable setHashtable)
 30		{
 31			return new BidirHashtable(setHashtable);
 32		}
 33		#endregion
 34
 35		#region ToHashtable (Static)
 36		/// <summary>
 37		/// Hashtable
 38		/// </summary>
 39		/// <param name="bd">Bidirectional hashtable</param>
 40		/// <returns>A normal Hashtable</returns>
 41		public static Hashtable ToHashtable(BidirHashtable bd)
 42		{
 43			return (Hashtable)bd.hashtableForward.Clone();
 44		}
 45		#endregion
 46
 47		#region Attach (Static)
 48		/// <summary>
 49		/// Creates a new BidirHashtable which is attached to
 50		/// the input Hashtable.  The reverse mapping is set up
 51		/// automatically.
 52		/// Note that there is no need for a Detach(); just stop using
 53		/// this object and let it get garbage-collected aside from
 54		/// the attached table, which could still be used (though the
 55		/// user would have to be careful not to use the attached
 56		/// table while the BidirHashtable might still be used).
 57		/// While it is possible that someone might want to set up
 58		/// the reverse mapping by doing an Attach(), for that you
 59		/// must just use Attach() and then ReverseDirection().
 60		/// </summary>
 61		/// <param name="ht">Hastable</param>
 62		/// <returns>BidirHashtable with the same data</returns>
 63		public static BidirHashtable Attach(Hashtable ht)
 64		{
 65			return new BidirHashtable(ht, 0);
 66		}
 67		#endregion
 68
 69		#region Count (Public)
 70		/// <summary>
 71		/// Count
 72		/// </summary>
 73		public int Count
 74		{
 75			get
 76			{
 77				return hashtableForward.Count;
 78			}
 79		}
 80		#endregion
 81
 82		#region IsSynchronized (Public)
 83		/// <summary>
 84		/// Is synchronized
 85		/// </summary>
 86		public bool IsSynchronized
 87		{
 88			get
 89			{
 90				return hashtableForward.IsSynchronized;
 91			}
 92		}
 93		#endregion
 94
 95		#region SyncRoot (Public)
 96		/// <summary>
 97		/// Sync root
 98		/// </summary>
 99		public object SyncRoot
100		{
101			get
102			{
103				return hashtableForward.SyncRoot;
104			}
105		}
106		#endregion
107
108		#region IsFixedSize (Public)
109		/// <summary>
110		/// Is fixed size
111		/// </summary>
112		public bool IsFixedSize
113		{
114			get
115			{
116				return hashtableForward.IsFixedSize;
117			}
118		}
119		#endregion
120
121		#region IsReadOnly (Public)
122		/// <summary>
123		/// Is hashtable read only? Will return IsReadOnly from the underlying
124		/// hashtable (usually false).
125		/// </summary>
126		public bool IsReadOnly
127		{
128			get
129			{
130				return hashtableForward.IsReadOnly;
131			}
132		}
133		#endregion
134
135		#region Keys (Public)
136		/// <summary>
137		/// Note:  Editing the keys would make this class inconsistent.
138		/// </summary>
139		public ICollection Keys
140		{
141			get
142			{
143				return hashtableForward.Keys;
144			}
145		}
146		#endregion
147
148		#region Values (Public)
149		/// <summary>
150		/// Note: Editing the values would make this class inconsistent,
151		/// as they are used for the reverse mapping (and editing would change
152		/// their hashcodes, plus make the forward and reverse hashmaps
153		/// inconsistent.
154		/// </summary>
155		public ICollection Values
156		{
157			get
158			{
159				return hashtableForward.Values;
160			}
161		}
162		#endregion
163
164		#region Item (Public)
165		/// <summary>
166		/// Forward lookup and set.
167		/// </summary>
168		/// <param name="key">Key</param>
169		/// <returns>Item at this key</returns>
170		public object this[object key]
171		{
172			get
173			{
174				return hashtableForward[key];
175			}
176			set
177			{
178				// If the forward map contains this key, changing it
179				// means removing it from the reverse map by its value,
180				// which is the key for the reverse map.
181				if (hashtableForward.ContainsKey(key))
182				{
183					hashtableBackward.Remove(hashtableForward[key]);
184				}
185				hashtableForward[key] = value;
186				hashtableBackward[value] = key;
187			}
188		}
189		#endregion
190
191		#region ForwardHashtable (Public)
192		/// <summary>
193		/// Gives direct access to forward hashtable.
194		/// Although provided as a "just-in-case" kind of convenience,
195		/// it should be used carefully, if ever, as it gives direct
196		/// access to internal data, and if altered, the state of this
197		/// object will become inconsistent.
198		/// Preferably, use (Hashtable) conversion instead, which
199		/// returns a clone.
200		/// </summary>
201		public Hashtable ForwardHashtable
202		{
203			get
204			{
205				return hashtableForward;
206			}
207		}
208		#endregion
209
210		#region BackwardHashtable (Public)
211		/// <summary>
212		/// Gives direct access to backward hashtable.
213		/// Although provided as a "just-in-case" kind of convenience,
214		/// it should be used carefully, if ever, as it gives direct
215		/// access to internal data, and if altered, the state of this
216		/// object will become inconsistent.
217		/// Preferably, use BackwardHashtableClone instead, which
218		/// returns a clone.
219		/// </summary>
220		public Hashtable BackwardHashtable
221		{
222			get
223			{
224				return hashtableBackward;
225			}
226		}
227		#endregion
228
229		#region BackwardHashtableClone (Public)
230		/// <summary>
231		/// Returns a clone of the backward table which can be
232		/// passed off and edited.
233		/// </summary>
234		public Hashtable BackwardHashtableClone
235		{
236			get
237			{
238				return (Hashtable)hashtableBackward.Clone();
239			}
240		}
241		#endregion
242
243		#region Private
244
245		#region hashtableForward (Private)
246		/// <summary>
247		/// Hashtable forward
248		/// </summary>
249		private Hashtable hashtableForward;
250		#endregion
251
252		#region hashtableBackward (Private)
253		/// <summary>
254		/// Hashtable backward
255		/// </summary>
256		private Hashtable hashtableBackward;
257		#endregion
258
259		#endregion
260
261		#region Constructors
262		/// <summary>
263		/// Standard constructor.
264		/// Eventually it might be nice to add more, but in most cases
265		/// the default should do fine.
266		/// </summary>
267		public BidirHashtable()
268		{
269			hashtableForward = new Hashtable();
270			hashtableBackward = new Hashtable();
271		}
272
273		/// <summary>
274		/// This constructor initializes from an existing
275		/// IDictionary (such as another Hashtable).
276		/// It sets up both the forward and backward tables.
277		/// A use for this would be to take an existing Hashtable
278		/// that you don't want rewritten as a BidirHashtable, and
279		/// set it up for efficient reverse lookups.
280		/// Obviously, it'str more efficient to use a BidirHashtable
281		/// from the start, but the cost of converting a lot of code
282		/// may be too high to justify.
283		/// </summary>
284		/// <param name="dict">Dictionary to create hashtable from</param>
285		public BidirHashtable(IDictionary dict)
286		{
287			hashtableForward = new Hashtable();
288			hashtableBackward = new Hashtable();
289
290			foreach (object key in dict.Keys)
291			{
292				this[key] = dict[key];
293			}
294		}
295
296		/// <summary>
297		/// Private constructor used when explicitly attaching to
298		/// an existing Hashtable and then setting up the reverse
299		/// mapping from it.
300		/// </summary>
301		/// <param name="ht">Hashtable to attach to.</param>
302		/// <param name="bytDummyIndicatesAttach">
303		/// Dummy parameter (just to give it a different signature)</param>
304		private BidirHashtable(Hashtable ht, byte bytDummyIndicatesAttach)
305		{
306			hashtableForward = ht;
307			hashtableBackward = new Hashtable();
308			// Use bytDummyIndicatesAttach to subpress CA1801 warning
309			bytDummyIndicatesAttach++;
310
311			foreach (object key in ht.Keys)
312			{
313				hashtableBackward[ht[key]] = key;
314			}
315		}
316		#endregion
317
318		#region ICloneable Members
319		/// <summary>
320		/// Clone the current BidirHashtable.
321		/// </summary>
322		/// <returns>Cloned BidirHashtable as an object</returns>
323		public object Clone()
324		{
325			BidirHashtable bh = new BidirHashtable();
326			bh.hashtableForward = (Hashtable)hashtableForward.Clone();
327			bh.hashtableBackward = (Hashtable)hashtableBackward.Clone();
328			return bh;
329		}
330		#endregion
331
332		#region ICollection Members
333		/// <summary>
334		/// Copy to
335		/// </summary>
336		/// <param name="array">Array</param>
337		/// <param name="index">Index</param>
338		public void CopyTo(Array array, int index)
339		{
340			hashtableForward.CopyTo(array, index);
341		}
342		#endregion
343
344		#region IDictionary Members
345		/// <summary>
346		/// Add
347		/// </summary>
348		/// <param name="key">Key</param>
349		/// <param name="value">Values</param>
350		public void Add(object key, object value)
351		{
352			hashtableForward.Add(key, value);
353			hashtableBackward.Add(value, key);
354		}
355
356		/// <summary>
357		/// Clear
358		/// </summary>
359		public void Clear()
360		{
361			hashtableForward.Clear();
362			hashtableBackward.Clear();
363		}
364
365		/// <summary>
366		/// Contains
367		/// </summary>
368		/// <param name="key">Key to search for</param>
369		/// <returns>True if an entry with the key was found, false otherwise.
370		/// </returns>
371		public bool Contains(object key)
372		{
373			return hashtableForward.Contains(key);
374		}
375
376		/// <summary>
377		/// Remove
378		/// </summary>
379		/// <param name="key">Key</param>
380		public void Remove(object key)
381		{
382			object val = hashtableForward[key];
383			hashtableForward.Remove(key);
384			hashtableBackward.Remove(val);
385		}
386
387		//public
388		IDictionaryEnumerator IDictionary.GetEnumerator()
389		{
390			return hashtableForward.GetEnumerator();
391		}
392		#endregion
393
394		#region IEnumerable Members
395		IEnumerator IEnumerable.GetEnumerator()
396		{
397			return hashtableForward.GetEnumerator();
398		}
399		#endregion
400
401		#region op_Explicit (Operator)
402		/// <summary>
403		/// Bidir hashtable
404		/// </summary>
405		/// <param name="ht">Hashtable</param>
406		/// <returns>New BidirHashtable with the same data</returns>
407		public static explicit operator BidirHashtable(Hashtable ht)
408		{
409			return new BidirHashtable(ht);
410		}
411
412		/// <summary>
413		/// Hashtable
414		/// </summary>
415		/// <param name="bd">BidirHashtable</param>
416		/// <returns>Cloned hashtable with the same data</returns>
417		public static explicit operator Hashtable(BidirHashtable bd)
418		{
419			return (Hashtable)bd.hashtableForward.Clone();
420		}
421		#endregion
422
423		#region CopyValuesTo (Public)
424		/// <summary>
425		/// Copy values to
426		/// </summary>
427		/// <param name="array">Array</param>
428		/// <param name="index">Index</param>
429		public void CopyValuesTo(Array array, int index)
430		{
431			hashtableBackward.CopyTo(array, index);
432		}
433		#endregion
434
435		#region ReverseLookup (Public)
436		/// <summary>
437		/// This is the key addition in this class--a reverse lookup
438		/// which operates at the speed of a regular Hashtable.
439		/// </summary>
440		/// <param name="val">Value for lookup</param>
441		/// <returns>Object at lookup location</returns>
442		public object ReverseLookup(object val)
443		{
444			return hashtableBackward[val];
445		}
446		#endregion
447
448		#region ContainsValue (Public)
449		/// <summary>
450		/// Contains value
451		/// </summary>
452		/// <param name="val">Value to check for</param>
453		/// <returns>True if the value was found, false otherwise.</returns>
454		public bool ContainsValue(object val)
455		{
456			return hashtableBackward.Contains(val);
457		}
458		#endregion
459
460		#region ReverseDirection (Public)
461		/// <summary>
462		/// Reverses the direction of the BidirHashtable.
463		/// This just swaps the two internal Hashtables.
464		/// </summary>
465		public void ReverseDirection()
466		{
467			Hashtable htTemp = hashtableForward;
468			hashtableForward = hashtableBackward;
469			hashtableBackward = htTemp;
470		}
471		#endregion
472
473		/// <summary>
474		/// Bidir hashtable tests
475		/// </summary>
476		internal class BidirHashtableTests
477		{
478			#region TestBidirHashtable
479			/// <summary>
480			/// Test bidir hashtable
481			/// </summary>
482			[Test]
483			public void TestBidirHashtable()
484			{
485				BidirHashtable hash = new BidirHashtable();
486				hash.Add("test item", 10);
487				hash.Add("yoyo", 20);
488				hash.Add("wazzzap", 30);
489				// We can't add "yoyo" twice, use another name!
490				hash.Add("yoyo2", 40);
491				Assert.Equal(4, hash.Keys.Count);
492				Assert.Equal(10, (int)hash["test item"]);
493				Assert.Equal(40, (int)hash["yoyo2"]);
494				Assert.Equal("yoyo", (string)hash.ReverseLookup(20));
495				Assert.Equal("yoyo2", (string)hash.ReverseLookup(40));
496				Assert.True(hash.Contains("wazzzap"));
497				Assert.True(hash.ContainsValue(20));
498				Assert.False(hash.Contains("yoyo3"));
499				Assert.False(hash.ContainsValue(50));
500			}
501			#endregion
502		}
503	}
504}