Change "union value" to dynamically allocate long strings.
[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/compiler.h>
28 #include <libpspp/hash-functions.h>
29 #include <libpspp/hmap.h>
30 #include <libpspp/message.h>
31 #include <libpspp/str.h>
32
33 #include "xalloc.h"
34
35 static struct atom *atom_create (const char *string);
36 static void atom_destroy (struct atom *);
37 static const char *atom_to_string (const struct atom *);
38
39 /* Returns the label in VL.  The caller must not modify or free
40    the returned value. */
41 const char *
42 val_lab_get_label (const struct val_lab *vl)
43 {
44   return atom_to_string (vl->label);
45 }
46
47 /* Creates and returns a new, empty set of value labels with the
48    given WIDTH. */
49 struct val_labs *
50 val_labs_create (int width)
51 {
52   struct val_labs *vls = xmalloc (sizeof *vls);
53   vls->width = width;
54   hmap_init (&vls->labels);
55   return vls;
56 }
57
58 /* Creates and returns a new set of value labels identical to
59    VLS.  Returns a null pointer if VLS is null. */
60 struct val_labs *
61 val_labs_clone (const struct val_labs *vls)
62 {
63   struct val_labs *copy;
64   struct val_lab *label;
65
66   if (vls == NULL)
67     return NULL;
68
69   copy = val_labs_create (vls->width);
70   HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
71     val_labs_add (copy, &label->value, atom_to_string (label->label));
72   return copy;
73 }
74
75 /* Determines whether VLS's width can be changed to NEW_WIDTH,
76    using the rules checked by value_is_resizable. */
77 bool
78 val_labs_can_set_width (const struct val_labs *vls, int new_width)
79 {
80   struct val_lab *label;
81
82   HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
83     if (!value_is_resizable (&label->value, vls->width, new_width))
84       return false;
85
86   return true;
87 }
88
89 /* Changes the width of VLS to NEW_WIDTH.  The original and new
90    width must be both numeric or both string. */
91 void
92 val_labs_set_width (struct val_labs *vls, int new_width)
93 {
94   assert (val_labs_can_set_width (vls, new_width));
95   if (value_needs_resize (vls->width, new_width))
96     {
97       struct val_lab *label;
98       HMAP_FOR_EACH (label, struct val_lab, node, &vls->labels)
99         value_resize (&label->value, vls->width, new_width);
100     }
101   vls->width = new_width;
102 }
103
104 /* Destroys VLS. */
105 void
106 val_labs_destroy (struct val_labs *vls)
107 {
108   if (vls != NULL)
109     {
110       val_labs_clear (vls);
111       hmap_destroy (&vls->labels);
112       free (vls);
113     }
114 }
115
116 /* Removes all the value labels from VLS. */
117 void
118 val_labs_clear (struct val_labs *vls)
119 {
120   struct val_lab *label, *next;
121
122   HMAP_FOR_EACH_SAFE (label, next, struct val_lab, node, &vls->labels)
123     {
124       hmap_delete (&vls->labels, &label->node);
125       value_destroy (&label->value, vls->width);
126       atom_destroy (label->label);
127       free (label);
128     }
129 }
130
131 /* Returns the number of value labels in VLS.
132    Returns 0 if VLS is null. */
133 size_t
134 val_labs_count (const struct val_labs *vls)
135 {
136   return vls == NULL ? 0 : hmap_count (&vls->labels);
137 }
138 \f
139 static void
140 do_add_val_lab (struct val_labs *vls, const union value *value,
141                 const char *label)
142 {
143   struct val_lab *lab = xmalloc (sizeof *lab);
144   value_init (&lab->value, vls->width);
145   value_copy (&lab->value, value, vls->width);
146   lab->label = atom_create (label);
147   hmap_insert (&vls->labels, &lab->node, value_hash (value, vls->width, 0));
148 }
149
150 /* If VLS does not already contain a value label for VALUE, adds
151    LABEL for it and returns true.  Otherwise, returns false. */
152 bool
153 val_labs_add (struct val_labs *vls, const union value *value,
154               const char *label)
155 {
156   const struct val_lab *lab = val_labs_lookup (vls, value);
157   if (lab == NULL)
158     {
159       do_add_val_lab (vls, value, label);
160       return true;
161     }
162   else
163     return false;
164 }
165
166 /* Sets LABEL as the value label for VALUE in VLS, replacing any
167    existing label for VALUE. */
168 void
169 val_labs_replace (struct val_labs *vls, const union value *value,
170                   const char *label)
171 {
172   struct val_lab *vl = (struct val_lab *) val_labs_lookup (vls, value);
173   if (vl != NULL)
174     {
175       atom_destroy (vl->label);
176       vl->label = atom_create (label);
177     }
178   else
179     do_add_val_lab (vls, value, label);
180 }
181
182 /* Removes LABEL from VLS. */
183 void
184 val_labs_remove (struct val_labs *vls, const struct val_lab *label_)
185 {
186   struct val_lab *label = (struct val_lab *) label_;
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 const 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 }