1 ------------------------------------------------------------------------------
3 -- GNAT LIBRARY COMPONENTS --
5 -- A D A . C O N T A I N E R S . --
6 -- H A S H _ T A B L E S . G E N E R I C _ O P E R A T I O N S --
10 -- Copyright (C) 2004-2006, Free Software Foundation, Inc. --
12 -- GNAT is free software; you can redistribute it and/or modify it under --
13 -- terms of the GNU General Public License as published by the Free Soft- --
14 -- ware Foundation; either version 2, or (at your option) any later ver- --
15 -- sion. GNAT is distributed in the hope that it will be useful, but WITH- --
16 -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY --
17 -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License --
18 -- for more details. You should have received a copy of the GNU General --
19 -- Public License distributed with GNAT; see file COPYING. If not, write --
20 -- to the Free Software Foundation, 51 Franklin Street, Fifth Floor, --
21 -- Boston, MA 02110-1301, USA. --
23 -- As a special exception, if other files instantiate generics from this --
24 -- unit, or you link this unit with other files to produce an executable, --
25 -- this unit does not by itself cause the resulting executable to be --
26 -- covered by the GNU General Public License. This exception does not --
27 -- however invalidate any other reasons why the executable file might be --
28 -- covered by the GNU Public License. --
30 -- This unit was originally developed by Matthew J Heaney. --
31 ------------------------------------------------------------------------------
33 with Ada.Containers.Prime_Numbers;
34 with Ada.Unchecked_Deallocation;
36 with System; use type System.Address;
38 package body Ada.Containers.Hash_Tables.Generic_Operations is
41 new Ada.Unchecked_Deallocation (Buckets_Type, Buckets_Access);
47 procedure Adjust (HT : in out Hash_Table_Type) is
48 Src_Buckets : constant Buckets_Access := HT.Buckets;
49 N : constant Count_Type := HT.Length;
50 Src_Node : Node_Access;
51 Dst_Prev : Node_Access;
61 -- Technically it isn't necessary to allocate the exact same length
62 -- buckets array, because our only requirement is that following
63 -- assignment the source and target containers compare equal (that is,
64 -- operator "=" returns True). We can satisfy this requirement with any
65 -- hash table length, but we decide here to match the length of the
66 -- source table. This has the benefit that when iterating, elements of
67 -- the target are delivered in the exact same order as for the source.
69 HT.Buckets := new Buckets_Type (Src_Buckets'Range);
71 for Src_Index in Src_Buckets'Range loop
72 Src_Node := Src_Buckets (Src_Index);
74 if Src_Node /= null then
76 Dst_Node : constant Node_Access := Copy_Node (Src_Node);
80 pragma Assert (Index (HT, Dst_Node) = Src_Index);
83 HT.Buckets (Src_Index) := Dst_Node;
84 HT.Length := HT.Length + 1;
89 Src_Node := Next (Src_Node);
90 while Src_Node /= null loop
92 Dst_Node : constant Node_Access := Copy_Node (Src_Node);
96 pragma Assert (Index (HT, Dst_Node) = Src_Index);
99 Set_Next (Node => Dst_Prev, Next => Dst_Node);
100 HT.Length := HT.Length + 1;
102 Dst_Prev := Dst_Node;
105 Src_Node := Next (Src_Node);
110 pragma Assert (HT.Length = N);
117 function Capacity (HT : Hash_Table_Type) return Count_Type is
119 if HT.Buckets = null then
123 return HT.Buckets'Length;
130 procedure Clear (HT : in out Hash_Table_Type) is
131 Index : Hash_Type := 0;
136 raise Program_Error with
137 "attempt to tamper with elements (container is busy)";
140 while HT.Length > 0 loop
141 while HT.Buckets (Index) = null loop
146 Bucket : Node_Access renames HT.Buckets (Index);
150 Bucket := Next (Bucket);
151 HT.Length := HT.Length - 1;
153 exit when Bucket = null;
159 ---------------------------
160 -- Delete_Node_Sans_Free --
161 ---------------------------
163 procedure Delete_Node_Sans_Free
164 (HT : in out Hash_Table_Type;
167 pragma Assert (X /= null);
174 if HT.Length = 0 then
175 raise Program_Error with
176 "attempt to delete node from empty hashed container";
179 Indx := Index (HT, X);
180 Prev := HT.Buckets (Indx);
183 raise Program_Error with
184 "attempt to delete node from empty hash bucket";
188 HT.Buckets (Indx) := Next (Prev);
189 HT.Length := HT.Length - 1;
193 if HT.Length = 1 then
194 raise Program_Error with
195 "attempt to delete node not in its proper hash bucket";
202 raise Program_Error with
203 "attempt to delete node not in its proper hash bucket";
207 Set_Next (Node => Prev, Next => Next (Curr));
208 HT.Length := HT.Length - 1;
214 end Delete_Node_Sans_Free;
220 procedure Finalize (HT : in out Hash_Table_Type) is
230 function First (HT : Hash_Table_Type) return Node_Access is
234 if HT.Length = 0 then
238 Indx := HT.Buckets'First;
240 if HT.Buckets (Indx) /= null then
241 return HT.Buckets (Indx);
248 ---------------------
249 -- Free_Hash_Table --
250 ---------------------
252 procedure Free_Hash_Table (Buckets : in out Buckets_Access) is
256 if Buckets = null then
260 for J in Buckets'Range loop
261 while Buckets (J) /= null loop
263 Buckets (J) := Next (Node);
275 function Generic_Equal
276 (L, R : Hash_Table_Type) return Boolean is
279 L_Node : Node_Access;
284 if L'Address = R'Address then
288 if L.Length /= R.Length then
296 -- Find the first node of hash table L
300 L_Node := L.Buckets (L_Index);
301 exit when L_Node /= null;
302 L_Index := L_Index + 1;
305 -- For each node of hash table L, search for an equivalent node in hash
310 if not Find (HT => R, Key => L_Node) then
316 L_Node := Next (L_Node);
318 if L_Node = null then
319 -- We have exhausted the nodes in this bucket
325 -- Find the next bucket
328 L_Index := L_Index + 1;
329 L_Node := L.Buckets (L_Index);
330 exit when L_Node /= null;
336 -----------------------
337 -- Generic_Iteration --
338 -----------------------
340 procedure Generic_Iteration (HT : Hash_Table_Type) is
344 if HT.Length = 0 then
348 for Indx in HT.Buckets'Range loop
349 Node := HT.Buckets (Indx);
350 while Node /= null loop
355 end Generic_Iteration;
361 procedure Generic_Read
362 (Stream : not null access Root_Stream_Type'Class;
363 HT : out Hash_Table_Type)
371 Count_Type'Base'Read (Stream, N);
374 raise Program_Error with "stream appears to be corrupt";
381 -- The RM does not specify whether or how the capacity changes when a
382 -- hash table is streamed in. Therefore we decide here to allocate a new
383 -- buckets array only when it's necessary to preserve representation
387 or else HT.Buckets'Length < N
390 NN := Prime_Numbers.To_Prime (N);
391 HT.Buckets := new Buckets_Type (0 .. NN - 1);
396 Node : constant Node_Access := New_Node (Stream);
397 Indx : constant Hash_Type := Index (HT, Node);
398 B : Node_Access renames HT.Buckets (Indx);
400 Set_Next (Node => Node, Next => B);
404 HT.Length := HT.Length + 1;
412 procedure Generic_Write
413 (Stream : not null access Root_Stream_Type'Class;
414 HT : Hash_Table_Type)
416 procedure Write (Node : Node_Access);
417 pragma Inline (Write);
419 procedure Write is new Generic_Iteration (Write);
425 procedure Write (Node : Node_Access) is
427 Write (Stream, Node);
431 -- See Generic_Read for an explanation of why we do not stream out the
432 -- buckets array length too.
434 Count_Type'Base'Write (Stream, HT.Length);
443 (Buckets : Buckets_Type;
444 Node : Node_Access) return Hash_Type is
446 return Hash_Node (Node) mod Buckets'Length;
450 (Hash_Table : Hash_Table_Type;
451 Node : Node_Access) return Hash_Type is
453 return Index (Hash_Table.Buckets.all, Node);
460 procedure Move (Target, Source : in out Hash_Table_Type) is
462 if Target'Address = Source'Address then
466 if Source.Busy > 0 then
467 raise Program_Error with
468 "attempt to tamper with elements (container is busy)";
474 Buckets : constant Buckets_Access := Target.Buckets;
476 Target.Buckets := Source.Buckets;
477 Source.Buckets := Buckets;
480 Target.Length := Source.Length;
489 (HT : Hash_Table_Type;
490 Node : Node_Access) return Node_Access
492 Result : Node_Access := Next (Node);
495 if Result /= null then
499 for Indx in Index (HT, Node) + 1 .. HT.Buckets'Last loop
500 Result := HT.Buckets (Indx);
502 if Result /= null then
510 ----------------------
511 -- Reserve_Capacity --
512 ----------------------
514 procedure Reserve_Capacity
515 (HT : in out Hash_Table_Type;
521 if HT.Buckets = null then
523 NN := Prime_Numbers.To_Prime (N);
524 HT.Buckets := new Buckets_Type (0 .. NN - 1);
530 if HT.Length = 0 then
532 -- This is the easy case. There are no nodes, so no rehashing is
533 -- necessary. All we need to do is allocate a new buckets array
534 -- having a length implied by the specified capacity. (We say
535 -- "implied by" because bucket arrays are always allocated with a
536 -- length that corresponds to a prime number.)
543 if N = HT.Buckets'Length then
547 NN := Prime_Numbers.To_Prime (N);
549 if NN = HT.Buckets'Length then
554 X : Buckets_Access := HT.Buckets;
556 HT.Buckets := new Buckets_Type (0 .. NN - 1);
563 if N = HT.Buckets'Length then
567 if N < HT.Buckets'Length then
569 -- This is a request to contract the buckets array. The amount of
570 -- contraction is bounded in order to preserve the invariant that the
571 -- buckets array length is never smaller than the number of elements
572 -- (the load factor is 1).
574 if HT.Length >= HT.Buckets'Length then
578 NN := Prime_Numbers.To_Prime (HT.Length);
580 if NN >= HT.Buckets'Length then
585 NN := Prime_Numbers.To_Prime (Count_Type'Max (N, HT.Length));
587 if NN = HT.Buckets'Length then -- can't expand any more
593 raise Program_Error with
594 "attempt to tamper with elements (container is busy)";
598 Dst_Buckets : Buckets_Access := new Buckets_Type (0 .. NN - 1);
599 Src_Buckets : Buckets_Access := HT.Buckets;
601 L : Count_Type renames HT.Length;
602 LL : constant Count_Type := L;
604 Src_Index : Hash_Type := Src_Buckets'First;
609 Src_Bucket : Node_Access renames Src_Buckets (Src_Index);
612 while Src_Bucket /= null loop
614 Src_Node : constant Node_Access := Src_Bucket;
616 Dst_Index : constant Hash_Type :=
617 Index (Dst_Buckets.all, Src_Node);
619 Dst_Bucket : Node_Access renames Dst_Buckets (Dst_Index);
622 Src_Bucket := Next (Src_Node);
624 Set_Next (Src_Node, Dst_Bucket);
626 Dst_Bucket := Src_Node;
629 pragma Assert (L > 0);
634 -- If there's an error computing a hash value during a
635 -- rehash, then AI-302 says the nodes "become lost." The
636 -- issue is whether to actually deallocate these lost nodes,
637 -- since they might be designated by extant cursors. Here
638 -- we decide to deallocate the nodes, since it's better to
639 -- solve real problems (storage consumption) rather than
640 -- imaginary ones (the user might, or might not, dereference
641 -- a cursor designating a node that has been deallocated),
642 -- and because we have a way to vet a dangling cursor
643 -- reference anyway, and hence can actually detect the
646 for Dst_Index in Dst_Buckets'Range loop
648 B : Node_Access renames Dst_Buckets (Dst_Index);
660 raise Program_Error with
661 "hash function raised exception during rehash";
664 Src_Index := Src_Index + 1;
667 HT.Buckets := Dst_Buckets;
672 end Reserve_Capacity;
674 end Ada.Containers.Hash_Tables.Generic_Operations;