standalone: fix w/h confusion
[pcsx_rearmed.git] / deps / libretro-common / lists / nested_list.c
CommitLineData
3719602c
PC
1/* Copyright (C) 2010-2020 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (nested_list.c).
5 * ---------------------------------------------------------------------------------------
6 *
7 * Permission is hereby granted, free of charge,
8 * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation the rights to
10 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22
23#include <string/stdstring.h>
24#include <lists/string_list.h>
25#include <array/rbuf.h>
26#include <array/rhmap.h>
27
28#include <lists/nested_list.h>
29
30struct nested_list_item
31{
32 nested_list_item_t *parent_item;
33 nested_list_t *parent_list;
34 nested_list_t *children;
35 char *id;
36 const void *value;
37};
38
39struct nested_list
40{
41 nested_list_item_t **items;
42 nested_list_item_t **item_map;
43};
44
45/**************************************/
46/* Initialisation / De-Initialisation */
47/**************************************/
48
49/* Forward declaration - required since
50 * nested_list_free_list() is recursive */
51static void nested_list_free_list(nested_list_t *list);
52
53/* Frees contents of a nested list item */
54static void nested_list_free_item(nested_list_item_t *item)
55{
56 if (!item)
57 return;
58
59 item->parent_item = NULL;
60 item->parent_list = NULL;
61
62 if (item->children)
63 {
64 nested_list_free_list(item->children);
65 item->children = NULL;
66 }
67
68 if (item->id)
69 {
70 free(item->id);
71 item->id = NULL;
72 }
73
74 item->value = NULL;
75 free(item);
76}
77
78/* Frees contents of a nested list */
79static void nested_list_free_list(nested_list_t *list)
80{
81 size_t i;
82
83 if (!list)
84 return;
85
86 for (i = 0; i < RBUF_LEN(list->items); i++)
87 nested_list_free_item(list->items[i]);
88
89 RBUF_FREE(list->items);
90 RHMAP_FREE(list->item_map);
91 free(list);
92}
93
94/**
95 * nested_list_init:
96 *
97 * Creates a new empty nested list. Returned pointer
98 * must be freed using nested_list_free.
99 *
100 * Returns: Valid nested_list_t pointer if successful,
101 * otherwise NULL.
102 */
103nested_list_t *nested_list_init(void)
104{
105 /* Create nested list */
106 nested_list_t *list = (nested_list_t*)malloc(sizeof(*list));
107
108 if (!list)
109 return NULL;
110
111 /* Initialise members */
112 list->items = NULL;
113 list->item_map = NULL;
114
115 return list;
116}
117
118/**
119 * nested_list_free:
120 * @list : pointer to nested_list_t object
121 *
122 * Frees specified nested list.
123 */
124void nested_list_free(nested_list_t *list)
125{
126 nested_list_free_list(list);
127}
128
129/***********/
130/* Setters */
131/***********/
132
133/* Creates and adds a new item to the specified
134 * nested list. Returns NULL if item matching 'id'
135 * already exists */
136static nested_list_item_t *nested_list_add_item_to_list(nested_list_t *list,
137 nested_list_item_t *parent_item, const char *id, const void *value)
138{
139 size_t num_items = 0;
140 nested_list_item_t *new_item = NULL;
141 nested_list_t *child_list = NULL;
142
143 if (!list || string_is_empty(id))
144 goto end;
145
146 num_items = RBUF_LEN(list->items);
147
148 /* Ensure that item does not already exist */
149 if (RHMAP_HAS_STR(list->item_map, id))
150 goto end;
151
152 /* Attempt to allocate a buffer slot for the
153 * new item */
154 if (!RBUF_TRYFIT(list->items, num_items + 1))
155 goto end;
156
157 /* Create new empty child list */
158 child_list = nested_list_init();
159 if (!child_list)
160 goto end;
161
162 /* Create new list item */
163 new_item = (nested_list_item_t*)malloc(sizeof(*new_item));
164 if (!new_item)
165 {
166 nested_list_free(child_list);
167 goto end;
168 }
169
170 /* Assign members */
171 new_item->parent_item = parent_item;
172 new_item->parent_list = list;
173 new_item->children = child_list;
174 new_item->id = strdup(id);
175 new_item->value = value;
176
177 /* Increment item buffer size */
178 RBUF_RESIZE(list->items, num_items + 1);
179
180 /* Add new item to buffer */
181 list->items[num_items] = new_item;
182
183 /* Update map */
184 RHMAP_SET_STR(list->item_map, id, new_item);
185end:
186 return new_item;
187}
188
189/**
190 * nested_list_add_item:
191 *
192 * @list : pointer to nested_list_t object
193 * @address : a delimited list of item identifiers,
194 * corresponding to item 'levels'
195 * @delim : delimiter to use when splitting @address
196 * into individual ids
197 * @value : optional value (user data) associated with
198 * new list item. This is added to the last
199 * item specified by @address
200 *
201 * Appends a new item to the specified nested list.
202 * If @delim is NULL, item is added to the top level
203 * list (@list itself) with id equal to @address.
204 * Otherwise, @address is split by @delim and each
205 * id is added as new 'layer'. For example:
206 *
207 * > @address = "one:two:three", @delim = ":" will
208 * produce:
209 * top_level_list:one
210 * `- "one" list:two
211 * `- "two" list:three
212 * where @value is assigned to the "two" list:three
213 * item.
214 *
215 * Returns: true if successful, otherwise false. Will
216 * always return false if item specified by @address
217 * already exists in the nested list.
218 */
219bool nested_list_add_item(nested_list_t *list,
220 const char *address, const char *delim, const void *value)
221{
222 struct string_list id_list = {0};
223 const char *top_id = NULL;
224 bool success = false;
225
226 if (!list || string_is_empty(address))
227 goto end;
228
229 /* If delim is NULL or address contains a single
230 * token, then we are adding an item to the top
231 * level list */
232 if (string_is_empty(delim))
233 top_id = address;
234 else
235 {
236 string_list_initialize(&id_list);
237 if (!string_split_noalloc(&id_list, address, delim) ||
238 (id_list.size < 1))
239 goto end;
240
241 if (id_list.size == 1)
242 top_id = id_list.elems[0].data;
243 }
244
245 if (!string_is_empty(top_id))
246 {
247 if (nested_list_add_item_to_list(list, NULL, top_id, value))
248 success = true;
249 }
250 else
251 {
252 nested_list_t *current_list = list;
253 nested_list_item_t *parent_item = NULL;
254 nested_list_item_t *next_item = NULL;
255 size_t i;
256
257 /* Loop over list item ids */
258 for (i = 0; i < id_list.size; i++)
259 {
260 const char *id = id_list.elems[i].data;
261
262 if (string_is_empty(id))
263 goto end;
264
265 /* If this is the last entry in the id list,
266 * then we are adding the item itself */
267 if (i == (id_list.size - 1))
268 {
269 if (nested_list_add_item_to_list(current_list,
270 parent_item, id, value))
271 success = true;
272
273 break;
274 }
275 /* Otherwise, id corresponds to a 'category' */
276 else
277 {
278 /* Check whether category item already exists */
279 next_item = RHMAP_GET_STR(current_list->item_map, id);
280
281 /* Create it, if required */
282 if (!next_item)
283 next_item = nested_list_add_item_to_list(current_list,
284 parent_item, id, NULL);
285
286 if (!next_item)
287 break;
288
289 /* Update pointers */
290 parent_item = next_item;
291 current_list = next_item->children;
292 }
293 }
294 }
295
296end:
297 string_list_deinitialize(&id_list);
298 return success;
299}
300
301/***********/
302/* Getters */
303/***********/
304
305/**
306 * nested_list_get_size:
307 *
308 * @list : pointer to nested_list_t object
309 *
310 * Fetches the current size (number of items) in
311 * the specified list.
312 *
313 * Returns: list size.
314 */
315size_t nested_list_get_size(nested_list_t *list)
316{
317 if (!list)
318 return 0;
319
320 return RBUF_LEN(list->items);
321}
322
323/**
324 * nested_list_get_item:
325 *
326 * @list : pointer to nested_list_t object
327 * @address : a delimited list of item identifiers,
328 * corresponding to item 'levels'
329 * @delim : delimiter to use when splitting @address
330 * into individual ids
331 *
332 * Searches for (and returns) the list item corresponding
333 * to @address. If @delim is NULL, the top level list
334 * (@list itself) is searched for an item with an id
335 * equal to @address. Otherwise, @address is split by
336 * @delim and each id is searched for in a subsequent
337 * list level.
338 *
339 * Returns: valid nested_list_item_t pointer if item
340 * is found, otherwise NULL.
341 */
342nested_list_item_t *nested_list_get_item(nested_list_t *list,
343 const char *address, const char *delim)
344{
345 nested_list_item_t *search_item = NULL;
346 struct string_list id_list = {0};
347 const char *top_id = NULL;
348
349 if (!list || string_is_empty(address))
350 goto end;
351
352 /* If delim is NULL or address contains a single
353 * token, then we are fetching an item from the
354 * top level list */
355 if (string_is_empty(delim))
356 top_id = address;
357 else
358 {
359 string_list_initialize(&id_list);
360 if (!string_split_noalloc(&id_list, address, delim) ||
361 (id_list.size < 1))
362 goto end;
363
364 if (id_list.size == 1)
365 top_id = id_list.elems[0].data;
366 }
367
368 if (!string_is_empty(top_id))
369 search_item = RHMAP_GET_STR(list->item_map, top_id);
370 else
371 {
372 /* Otherwise, search 'category' levels */
373 nested_list_t *current_list = list;
374 nested_list_item_t *next_item = NULL;
375 size_t i;
376
377 /* Loop over list item ids */
378 for (i = 0; i < id_list.size; i++)
379 {
380 const char *id = id_list.elems[i].data;
381
382 if (string_is_empty(id))
383 goto end;
384
385 /* If this is the last entry in the id list,
386 * then we are searching for the item itself */
387 if (i == (id_list.size - 1))
388 {
389 search_item = RHMAP_GET_STR(current_list->item_map, id);
390 break;
391 }
392 /* Otherwise, id corresponds to a 'category' */
393 else
394 {
395 next_item = RHMAP_GET_STR(current_list->item_map, id);
396
397 if (!next_item)
398 break;
399
400 /* Update pointer */
401 current_list = next_item->children;
402 }
403 }
404 }
405
406end:
407 string_list_deinitialize(&id_list);
408 return search_item;
409}
410
411/**
412 * nested_list_get_item_idx:
413 *
414 * @list : pointer to nested_list_t object
415 * @idx : item index
416 *
417 * Fetches the item corresponding to index @idx in
418 * the top level list (@list itself) of the specified
419 * nested list.
420 *
421 * Returns: valid nested_list_item_t pointer if item
422 * exists, otherwise NULL.
423 */
424nested_list_item_t *nested_list_get_item_idx(nested_list_t *list,
425 size_t idx)
426{
427 if (!list || (idx >= RBUF_LEN(list->items)))
428 return NULL;
429
430 return list->items[idx];
431}
432
433/**
434 * nested_list_item_get_parent:
435 *
436 * @list_item : pointer to nested_list_item_t object
437 *
438 * Fetches the parent item of the specified nested
439 * list item. If returned value is NULL, specified
440 * nested list item belongs to a top level list.
441 *
442 * Returns: valid nested_list_item_t pointer if item
443 * has a parent, otherwise NULL.
444 */
445nested_list_item_t *nested_list_item_get_parent(nested_list_item_t *list_item)
446{
447 if (!list_item)
448 return NULL;
449
450 return list_item->parent_item;
451}
452
453/**
454 * nested_list_item_get_parent_list:
455 *
456 * @list_item : pointer to nested_list_item_t object
457 *
458 * Fetches a pointer to the nested list of which the
459 * specified list item is a direct member.
460 *
461 * Returns: valid nested_list_t pointer if successful,
462 * otherwise NULL.
463 */
464nested_list_t *nested_list_item_get_parent_list(nested_list_item_t *list_item)
465{
466 if (!list_item)
467 return NULL;
468
469 return list_item->parent_list;
470}
471
472/**
473 * nested_list_item_get_children:
474 *
475 * @list_item : pointer to nested_list_item_t object
476 *
477 * Fetches a pointer to the nested list of child items
478 * belonging to the specified list item.
479 *
480 * Returns: valid nested_list_t pointer if item has
481 * children, otherwise NULL.
482 */
483nested_list_t *nested_list_item_get_children(nested_list_item_t *list_item)
484{
485 if (!list_item ||
486 !list_item->children ||
487 (RBUF_LEN(list_item->children->items) < 1))
488 return NULL;
489
490 return list_item->children;
491}
492
493/**
494 * nested_list_item_get_id:
495 *
496 * @list_item : pointer to nested_list_item_t object
497 *
498 * Fetches the id string of the specified list item,
499 * as set by nested_list_add_item().
500 *
501 * Returns: item id if successful, otherwise NULL.
502 */
503const char *nested_list_item_get_id(nested_list_item_t *list_item)
504{
505 if (!list_item)
506 return NULL;
507
508 return list_item->id;
509}
510
511/**
512 * nested_list_item_get_address:
513 *
514 * @list_item : pointer to nested_list_item_t object
515 * @delim : delimiter to use when concatenating
516 * individual item ids into a an @address
517 * string
518 * @address : a delimited list of item identifiers,
519 * corresponding to item 'levels'
520 * @len : length of supplied @address char array
521
522 * Fetches a compound @address string corresponding to
523 * the specified item's 'position' in the top level
524 * nested list of which it is a member. The resultant
525 * @address may be used to find the item when calling
526 * nested_list_get_item() on the top level nested list.
527 *
528 * Returns: true if successful, otherwise false.
529 */
530bool nested_list_item_get_address(nested_list_item_t *list_item,
531 const char *delim, char *address, size_t len)
532{
533 nested_list_item_t *current_item = list_item;
534 struct string_list id_list = {0};
535 bool success = false;
536 union string_list_elem_attr attr;
537 size_t i;
538
539 if (!list_item ||
540 string_is_empty(delim) ||
541 !address ||
542 (len < 1))
543 goto end;
544
545 address[0] = '\0';
546 attr.i = 0;
547
548 /* If this is an item of the top level
549 * list, just copy the item id directly */
550 if (!list_item->parent_item)
551 {
552 strlcpy(address, list_item->id, len);
553 success = true;
554 goto end;
555 }
556
557 /* ...otherwise we have to combine the ids
558 * of the item and all of its 'ancestors' */
559 string_list_initialize(&id_list);
560
561 /* Fetch all ids */
562 do
563 {
564 const char *id = current_item->id;
565
566 if (string_is_empty(id) ||
567 !string_list_append(&id_list, id, attr))
568 goto end;
569
570 current_item = current_item->parent_item;
571 }
572 while (current_item);
573
574 if (id_list.size < 1)
575 goto end;
576
577 /* Build address string */
578 for (i = id_list.size; i > 0; i--)
579 {
580 const char *id = id_list.elems[i - 1].data;
581
582 if (string_is_empty(id))
583 goto end;
584
585 strlcat(address, id, len);
586 if (i > 1)
587 strlcat(address, delim, len);
588 }
589
590 success = true;
591end:
592 string_list_deinitialize(&id_list);
593 return success;
594}
595
596/**
597 * nested_list_item_get_value:
598 *
599 * @list_item : pointer to nested_list_item_t object
600 *
601 * Fetches the value (user data) associated with the
602 * specified list item.
603 *
604 * Returns: pointer to user data if set, otherwise
605 * NULL.
606 */
607const void *nested_list_item_get_value(nested_list_item_t *list_item)
608{
609 if (!list_item)
610 return NULL;
611
612 return list_item->value;
613}