Add CONST_CAST macro, for casting away "const" more safely.
[pspp-builds.git] / src / data / value-labels.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 1997-9, 2000, 2009 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "value-labels.h"
20
21 #include <stdlib.h>
22
23 #include <data/data-out.h>
24 #include <data/value.h>
25 #include <data/variable.h>
26 #include <libpspp/array.h>
27 #include <libpspp/cast.h>
28 #include <libpspp/compiler.h>
29 #include <libpspp/hash-functions.h>
30 #include <libpspp/hmap.h>
31 #include <libpspp/message.h>
32 #include <libpspp/str.h>
33
34 #include "xalloc.h"
35
36 static struct atom *atom_create (const char *string);
37 static void atom_destroy (struct atom *);
38 static const char *atom_to_string (const struct atom *);
39
40 /* Returns the label in VL.  The caller must not modify or free
41    the returned value. */
42 const char *
43 val_lab_get_label (const struct val_lab *vl)
44 {
45   return atom_to_string (vl->label);
46 }
47
48 /* Creates and returns a new, empty set of value labels with the
49    given WIDTH. */
50 struct val_labs *
51 val_labs_create (int width)
52 {
53   struct val_labs *vls = xmalloc (sizeof *vls);
54   vls->width = width;
55   hmap_init (&vls->labels);
56   return vls;
57 }
58
59 /* Creates and returns a new set of value labels identical to
60    VLS.  Returns a null pointer if VLS is null. */
61 struct val_labs *
62 val_labs_clone (const struct val_labs *vls)
63 {
64   struct val_labs *copy;
65   struct val_lab *label;
66
67   if (vls == NULL)
68     return NULL;
69
70   copy = val_labs_create (vls->width);
71   HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
72     val_labs_add (copy, &label->value, atom_to_string (label->label));
73   return copy;
74 }
75
76 /* Determines whether VLS's width can be changed to NEW_WIDTH,
77    using the rules checked by value_is_resizable. */
78 bool
79 val_labs_can_set_width (const struct val_labs *vls, int new_width)
80 {
81   struct val_lab *label;
82
83   HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
84     if (!value_is_resizable (&label->value, vls->width, new_width))
85       return false;
86
87   return true;
88 }
89
90 /* Changes the width of VLS to NEW_WIDTH.  The original and new
91    width must be both numeric or both string. */
92 void
93 val_labs_set_width (struct val_labs *vls, int new_width)
94 {
95   assert (val_labs_can_set_width (vls, new_width));
96   if (value_needs_resize (vls->width, new_width))
97     {
98       struct val_lab *label;
99       HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
100         value_resize (&label->value, vls->width, new_width);
101     }
102   vls->width = new_width;
103 }
104
105 /* Destroys VLS. */
106 void
107 val_labs_destroy (struct val_labs *vls)
108 {
109   if (vls != NULL)
110     {
111       val_labs_clear (vls);
112       hmap_destroy (&vls->labels);
113       free (vls);
114     }
115 }
116
117 /* Removes all the value labels from VLS. */
118 void
119 val_labs_clear (struct val_labs *vls)
120 {
121   struct val_lab *label, *next;
122
123   HMAP_FOR_EACH_SAFE (label, next, struct val_lab, node, &vls->labels)
124     {
125       hmap_delete (&vls->labels, &label->node);
126       value_destroy (&label->value, vls->width);
127       atom_destroy (label->label);
128       free (label);
129     }
130 }
131
132 /* Returns the number of value labels in VLS.
133    Returns 0 if VLS is null. */
134 size_t
135 val_labs_count (const struct val_labs *vls)
136 {
137   return vls == NULL ? 0 : hmap_count (&vls->labels);
138 }
139 \f
140 static void
141 do_add_val_lab (struct val_labs *vls, const union value *value,
142                 const char *label)
143 {
144   struct val_lab *lab = xmalloc (sizeof *lab);
145   value_init (&lab->value, vls->width);
146   value_copy (&lab->value, value, vls->width);
147   lab->label = atom_create (label);
148   hmap_insert (&vls->labels, &lab->node, value_hash (value, vls->width, 0));
149 }
150
151 /* If VLS does not already contain a value label for VALUE, adds
152    LABEL for it and returns true.  Otherwise, returns false. */
153 bool
154 val_labs_add (struct val_labs *vls, const union value *value,
155               const char *label)
156 {
157   const struct val_lab *lab = val_labs_lookup (vls, value);
158   if (lab == NULL)
159     {
160       do_add_val_lab (vls, value, label);
161       return true;
162     }
163   else
164     return false;
165 }
166
167 /* Sets LABEL as the value label for VALUE in VLS, replacing any
168    existing label for VALUE. */
169 void
170 val_labs_replace (struct val_labs *vls, const union value *value,
171                   const char *label)
172 {
173   struct val_lab *vl = CONST_CAST (struct val_lab *,
174                                    val_labs_lookup (vls, value));
175   if (vl != NULL)
176     {
177       atom_destroy (vl->label);
178       vl->label = atom_create (label);
179     }
180   else
181     do_add_val_lab (vls, value, label);
182 }
183
184 /* Removes LABEL from VLS. */
185 void
186 val_labs_remove (struct val_labs *vls, const struct val_lab *label_)
187 {
188   struct val_lab *label = CONST_CAST (struct val_lab *, label_);
189   hmap_delete (&vls->labels, &label->node);
190   value_destroy (&label->value, vls->width);
191   atom_destroy (label->label);
192   free (label);
193 }
194
195 /* Searches VLS for a value label for VALUE.  If successful,
196    returns the string used as the label; otherwise, returns a
197    null pointer.  Returns a null pointer if VLS is null. */
198 const char *
199 val_labs_find (const struct val_labs *vls, const union value *value)
200 {
201   const struct val_lab *label = val_labs_lookup (vls, value);
202   return label ? atom_to_string (label->label) : NULL;
203 }
204
205 /* Searches VLS for a value label for VALUE.  If successful,
206    returns the value label; otherwise, returns a null pointer.
207    Returns a null pointer if VLS is null. */
208 const struct val_lab *
209 val_labs_lookup (const struct val_labs *vls, const union value *value)
210 {
211   if (vls != NULL)
212     {
213       struct val_lab *label;
214       HMAP_FOR_EACH_WITH_HASH (label, struct val_lab, node,
215                                value_hash (value, vls->width, 0), &vls->labels)
216         if (value_equal (&label->value, value, vls->width))
217           return label;
218     }
219   return NULL;
220 }
221 \f
222 /* Returns the first value label in VLS, in arbitrary order, or a
223    null pointer if VLS is empty or if VLS is a null pointer.  If
224    the return value is non-null, then val_labs_next() may be used
225    to continue iterating. */
226 const struct val_lab *
227 val_labs_first (const struct val_labs *vls)
228 {
229   return vls ? HMAP_FIRST (struct val_lab, node, &vls->labels) : NULL;
230 }
231
232 /* Returns the next value label in an iteration begun by
233    val_labs_first().  If the return value is non-null, then
234    val_labs_next() may be used to continue iterating. */
235 const struct val_lab *
236 val_labs_next (const struct val_labs *vls, const struct val_lab *label)
237 {
238   return HMAP_NEXT (label, struct val_lab, node, &vls->labels);
239 }
240
241 static int
242 compare_labels_by_value_3way (const void *a_, const void *b_, const void *vls_)
243 {
244   const struct val_lab *const *a = a_;
245   const struct val_lab *const *b = b_;
246   const struct val_labs *vls = vls_;
247   return value_compare_3way (&(*a)->value, &(*b)->value, vls->width);
248 }
249
250 /* Allocates and returns an array of pointers to value labels
251    that is sorted in increasing order by value.  The array has
252    val_labs_count(VLS) elements.  The caller is responsible for
253    freeing the array. */
254 const struct val_lab **
255 val_labs_sorted (const struct val_labs *vls)
256 {
257   if (vls != NULL)
258     {
259       const struct val_lab *label;
260       const struct val_lab **labels;
261       size_t i;
262
263       labels = xmalloc (val_labs_count (vls) * sizeof *labels);
264       i = 0;
265       HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
266         labels[i++] = label;
267       assert (i == val_labs_count (vls));
268       sort (labels, val_labs_count (vls), sizeof *labels,
269             compare_labels_by_value_3way, vls);
270       return labels;
271     }
272   else
273     return NULL;
274 }
275 \f
276 /* Atoms: reference-counted constant strings. */
277
278 /* An atom. */
279 struct atom
280   {
281     struct hmap_node node;      /* Hash map node. */
282     char *string;               /* String value. */
283     unsigned ref_count;         /* Number of references. */
284   };
285
286 /* Hash table of atoms. */
287 static struct hmap atoms = HMAP_INITIALIZER (atoms);
288
289 static void free_atom (struct atom *atom);
290 static void free_all_atoms (void);
291
292 /* Creates and returns an atom for STRING. */
293 static struct atom *
294 atom_create (const char *string)
295 {
296   static bool initialized;
297   struct atom *atom;
298   size_t hash;
299
300   assert (string != NULL);
301
302   if (!initialized)
303     {
304       initialized = true;
305       atexit (free_all_atoms);
306     }
307
308   hash = hash_string (string, 0);
309   HMAP_FOR_EACH_WITH_HASH (atom, struct atom, node, hash, &atoms)
310     if (!strcmp (atom->string, string))
311       {
312         atom->ref_count++;
313         return atom;
314       }
315
316   atom = xmalloc (sizeof *atom);
317   atom->string = xstrdup (string);
318   atom->ref_count = 1;
319   hmap_insert (&atoms, &atom->node, hash);
320   return atom;
321 }
322
323 /* Destroys ATOM. */
324 static void
325 atom_destroy (struct atom *atom)
326 {
327   if (atom != NULL)
328     {
329       assert (atom->ref_count > 0);
330       atom->ref_count--;
331       if (atom->ref_count == 0)
332         {
333           hmap_delete (&atoms, &atom->node);
334           free_atom (atom);
335         }
336     }
337 }
338
339 /* Returns the string associated with ATOM. */
340 static const char *
341 atom_to_string (const struct atom *atom)
342 {
343   return atom->string;
344 }
345
346 static void
347 free_atom (struct atom *atom)
348 {
349   free (atom->string);
350   free (atom);
351 }
352
353 static void
354 free_all_atoms (void)
355 {
356   struct atom *atom, *next;
357
358   HMAP_FOR_EACH_SAFE (atom, next, struct atom, node, &atoms)
359     free_atom (atom);
360 }