0fe829cebe1781c4ccc2d791756d534b88153e0c
[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 = val_labs_lookup (vls, value);
174   if (vl != NULL)
175     {
176       atom_destroy (vl->label);
177       vl->label = atom_create (label);
178     }
179   else
180     do_add_val_lab (vls, value, label);
181 }
182
183 /* Removes LABEL from VLS. */
184 void
185 val_labs_remove (struct val_labs *vls, struct val_lab *label)
186 {
187   hmap_delete (&vls->labels, &label->node);
188   value_destroy (&label->value, vls->width);
189   atom_destroy (label->label);
190   free (label);
191 }
192
193 /* Searches VLS for a value label for VALUE.  If successful,
194    returns the string used as the label; otherwise, returns a
195    null pointer.  Returns a null pointer if VLS is null. */
196 const char *
197 val_labs_find (const struct val_labs *vls, const union value *value)
198 {
199   const struct val_lab *label = val_labs_lookup (vls, value);
200   return label ? atom_to_string (label->label) : NULL;
201 }
202
203 /* Searches VLS for a value label for VALUE.  If successful,
204    returns the value label; otherwise, returns a null pointer.
205    Returns a null pointer if VLS is null. */
206 struct val_lab *
207 val_labs_lookup (const struct val_labs *vls, const union value *value)
208 {
209   if (vls != NULL)
210     {
211       struct val_lab *label;
212       HMAP_FOR_EACH_WITH_HASH (label, struct val_lab, node,
213                                value_hash (value, vls->width, 0), &vls->labels)
214         if (value_equal (&label->value, value, vls->width))
215           return label;
216     }
217   return NULL;
218 }
219 \f
220 /* Returns the first value label in VLS, in arbitrary order, or a
221    null pointer if VLS is empty or if VLS is a null pointer.  If
222    the return value is non-null, then val_labs_next() may be used
223    to continue iterating. */
224 const struct val_lab *
225 val_labs_first (const struct val_labs *vls)
226 {
227   return vls ? HMAP_FIRST (struct val_lab, node, &vls->labels) : NULL;
228 }
229
230 /* Returns the next value label in an iteration begun by
231    val_labs_first().  If the return value is non-null, then
232    val_labs_next() may be used to continue iterating. */
233 const struct val_lab *
234 val_labs_next (const struct val_labs *vls, const struct val_lab *label)
235 {
236   return HMAP_NEXT (label, struct val_lab, node, &vls->labels);
237 }
238
239 static int
240 compare_labels_by_value_3way (const void *a_, const void *b_, const void *vls_)
241 {
242   const struct val_lab *const *a = a_;
243   const struct val_lab *const *b = b_;
244   const struct val_labs *vls = vls_;
245   return value_compare_3way (&(*a)->value, &(*b)->value, vls->width);
246 }
247
248 /* Allocates and returns an array of pointers to value labels
249    that is sorted in increasing order by value.  The array has
250    val_labs_count(VLS) elements.  The caller is responsible for
251    freeing the array. */
252 const struct val_lab **
253 val_labs_sorted (const struct val_labs *vls)
254 {
255   if (vls != NULL)
256     {
257       const struct val_lab *label;
258       const struct val_lab **labels;
259       size_t i;
260
261       labels = xmalloc (val_labs_count (vls) * sizeof *labels);
262       i = 0;
263       HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
264         labels[i++] = label;
265       assert (i == val_labs_count (vls));
266       sort (labels, val_labs_count (vls), sizeof *labels,
267             compare_labels_by_value_3way, vls);
268       return labels;
269     }
270   else
271     return NULL;
272 }
273 \f
274 /* Atoms: reference-counted constant strings. */
275
276 /* An atom. */
277 struct atom
278   {
279     struct hmap_node node;      /* Hash map node. */
280     char *string;               /* String value. */
281     unsigned ref_count;         /* Number of references. */
282   };
283
284 /* Hash table of atoms. */
285 static struct hmap atoms = HMAP_INITIALIZER (atoms);
286
287 static void free_atom (struct atom *atom);
288 static void free_all_atoms (void);
289
290 /* Creates and returns an atom for STRING. */
291 static struct atom *
292 atom_create (const char *string)
293 {
294   static bool initialized;
295   struct atom *atom;
296   size_t hash;
297
298   assert (string != NULL);
299
300   if (!initialized)
301     {
302       initialized = true;
303       atexit (free_all_atoms);
304     }
305
306   hash = hash_string (string, 0);
307   HMAP_FOR_EACH_WITH_HASH (atom, struct atom, node, hash, &atoms)
308     if (!strcmp (atom->string, string))
309       {
310         atom->ref_count++;
311         return atom;
312       }
313
314   atom = xmalloc (sizeof *atom);
315   atom->string = xstrdup (string);
316   atom->ref_count = 1;
317   hmap_insert (&atoms, &atom->node, hash);
318   return atom;
319 }
320
321 /* Destroys ATOM. */
322 static void
323 atom_destroy (struct atom *atom)
324 {
325   if (atom != NULL)
326     {
327       assert (atom->ref_count > 0);
328       atom->ref_count--;
329       if (atom->ref_count == 0)
330         {
331           hmap_delete (&atoms, &atom->node);
332           free_atom (atom);
333         }
334     }
335 }
336
337 /* Returns the string associated with ATOM. */
338 static const char *
339 atom_to_string (const struct atom *atom)
340 {
341   return atom->string;
342 }
343
344 static void
345 free_atom (struct atom *atom)
346 {
347   free (atom->string);
348   free (atom);
349 }
350
351 static void
352 free_all_atoms (void)
353 {
354   struct atom *atom, *next;
355
356   HMAP_FOR_EACH_SAFE (atom, next, struct atom, node, &atoms)
357     free_atom (atom);
358 }