1 /*
2 * libosinfo:
3 *
4 * Copyright (C) 2009-2020 Red Hat, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library. If not, see
18 * <http://www.gnu.org/licenses/>.
19 */
20
21 #include <osinfo/osinfo.h>
22 #include <glib/gi18n-lib.h>
23
24 /**
25 * SECTION:osinfo_list
26 * @short_description: Abstract base class for entity lists
27 * @see_also: #OsinfoEntity
28 *
29 * #OsinfoList provides a way to maintain a list of #OsinfoEntity objects.
30 *
31 */
32
33 struct _OsinfoListPrivate
34 {
35 GPtrArray *array;
36 GHashTable *entities;
37
38 GType elementType;
39 };
40
41 enum {
42 PROP_O,
43 PROP_ELEMENT_TYPE,
44 LAST_PROP
45 };
46
47 static GParamSpec *properties[LAST_PROP];
48 G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE(OsinfoList, osinfo_list, G_TYPE_OBJECT)
49
50 static void osinfo_list_set_element_type(OsinfoList *list, GType type);
51
52 static void
osinfo_list_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)53 osinfo_list_set_property(GObject *object,
54 guint property_id,
55 const GValue *value,
56 GParamSpec *pspec)
57 {
58 OsinfoList *list = OSINFO_LIST(object);
59
60 switch (property_id) {
61 case PROP_ELEMENT_TYPE:
62 osinfo_list_set_element_type(list, g_value_get_gtype(value));
63 break;
64
65 default:
66 /* We don't have any other property... */
67 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
68 break;
69 }
70 }
71
72 static void
osinfo_list_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)73 osinfo_list_get_property(GObject *object,
74 guint property_id,
75 GValue *value,
76 GParamSpec *pspec)
77 {
78 OsinfoList *list = OSINFO_LIST(object);
79
80 switch (property_id) {
81 case PROP_ELEMENT_TYPE:
82 g_value_set_gtype(value, osinfo_list_get_element_type(list));
83 break;
84
85 default:
86 /* We don't have any other property... */
87 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
88 break;
89 }
90 }
91
92
93 static void
osinfo_list_finalize(GObject * object)94 osinfo_list_finalize(GObject *object)
95 {
96 OsinfoList *list = OSINFO_LIST(object);
97
98 g_ptr_array_free(list->priv->array, TRUE);
99 g_hash_table_unref(list->priv->entities);
100
101 /* Chain up to the parent class */
102 G_OBJECT_CLASS(osinfo_list_parent_class)->finalize(object);
103 }
104
105 /* Init functions */
106 static void
osinfo_list_class_init(OsinfoListClass * klass)107 osinfo_list_class_init(OsinfoListClass *klass)
108 {
109 GObjectClass *g_klass = G_OBJECT_CLASS(klass);
110
111 g_klass->set_property = osinfo_list_set_property;
112 g_klass->get_property = osinfo_list_get_property;
113 g_klass->finalize = osinfo_list_finalize;
114
115 /**
116 * OsinfoList:element-type:
117 *
118 * The specialization of the list. The list will be
119 * restricted to storing #OsinfoEntity objects of
120 * the specified type.
121 */
122 properties[PROP_ELEMENT_TYPE] = g_param_spec_gtype("element-type",
123 "Element type",
124 _("List element type"),
125 OSINFO_TYPE_ENTITY,
126 G_PARAM_CONSTRUCT_ONLY |
127 G_PARAM_READWRITE |
128 G_PARAM_STATIC_STRINGS);
129
130 g_object_class_install_properties(g_klass, LAST_PROP, properties);
131 }
132
133 static void
osinfo_list_init(OsinfoList * list)134 osinfo_list_init(OsinfoList *list)
135 {
136 list->priv = osinfo_list_get_instance_private(list);
137
138 list->priv->array = g_ptr_array_new_with_free_func(NULL);
139 list->priv->entities = g_hash_table_new_full(g_str_hash,
140 g_str_equal,
141 NULL,
142 g_object_unref);
143 }
144
145
146 /**
147 * osinfo_list_get_element_type:
148 * @list: the entity list
149 *
150 * Retrieves the type of the subclass of #OsinfoEntity
151 * that may be stored in the list
152 *
153 * Returns: the type of entity stored
154 */
osinfo_list_get_element_type(OsinfoList * list)155 GType osinfo_list_get_element_type(OsinfoList *list)
156 {
157 g_return_val_if_fail(OSINFO_IS_LIST(list), G_TYPE_INVALID);
158
159 return list->priv->elementType;
160 }
161
162 /**
163 * osinfo_list_set_element_type:
164 * @list: the entity list
165 * @type: the type for stored entities
166 *
167 * Sets the type of the subclass of #OsinfoEntity
168 * that may be stored in the list
169 *
170 * Returns: the type of entity stored
171 */
osinfo_list_set_element_type(OsinfoList * list,GType type)172 static void osinfo_list_set_element_type(OsinfoList *list, GType type)
173 {
174 g_return_if_fail(OSINFO_IS_LIST(list));
175
176 list->priv->elementType = type;
177 }
178
179
180 /**
181 * osinfo_list_get_length:
182 * @list: the entity list
183 *
184 * Retrieves the number of elements currently stored
185 * in the list
186 *
187 * Returns: the list length
188 */
osinfo_list_get_length(OsinfoList * list)189 gint osinfo_list_get_length(OsinfoList *list)
190 {
191 g_return_val_if_fail(OSINFO_IS_LIST(list), 0);
192
193 return list->priv->array->len;
194 }
195
196 /**
197 * osinfo_list_get_nth:
198 * @list: the entity list
199 * @idx: the list position to fetch
200 *
201 * Retrieves the element in the list at position @idx. If
202 * @idx is less than zero, or greater than the number of
203 * elements in the list, the results are undefined.
204 *
205 * Returns: (transfer none): the list element or %NULL
206 */
osinfo_list_get_nth(OsinfoList * list,gint idx)207 OsinfoEntity *osinfo_list_get_nth(OsinfoList *list, gint idx)
208 {
209 g_return_val_if_fail(OSINFO_IS_LIST(list), NULL);
210 g_return_val_if_fail(list->priv->array->len > idx, NULL);
211
212 return g_ptr_array_index(list->priv->array, idx);
213 }
214
215
216 /**
217 * osinfo_list_get_elements:
218 * @list: the entity list
219 *
220 * Retrieve a linked list of all elements in the list.
221 *
222 * Returns: (transfer container) (element-type OsinfoEntity): the list elements
223 */
osinfo_list_get_elements(OsinfoList * list)224 GList *osinfo_list_get_elements(OsinfoList *list)
225 {
226 g_return_val_if_fail(OSINFO_IS_LIST(list), NULL);
227
228 return g_hash_table_get_values(list->priv->entities);
229 }
230
231 /**
232 * osinfo_list_find_by_id:
233 * @list: the entity list
234 * @id: the unique identifier
235 *
236 * Search the list looking for the entity with a matching
237 * unique identifier.
238 *
239 * Returns: (transfer none): the matching entity, or NULL
240 */
osinfo_list_find_by_id(OsinfoList * list,const gchar * id)241 OsinfoEntity *osinfo_list_find_by_id(OsinfoList *list, const gchar *id)
242 {
243 g_return_val_if_fail(OSINFO_IS_LIST(list), NULL);
244
245 return g_hash_table_lookup(list->priv->entities, id);
246 }
247
248
249 /**
250 * osinfo_list_add:
251 * @list: the entity list
252 * @entity: (transfer none): the entity to add to the list
253 *
254 * Adds a new entity to the list.
255 */
osinfo_list_add(OsinfoList * list,OsinfoEntity * entity)256 void osinfo_list_add(OsinfoList *list, OsinfoEntity *entity)
257 {
258 OsinfoEntity *preexisting;
259
260 g_return_if_fail(OSINFO_IS_LIST(list));
261 g_return_if_fail(G_TYPE_CHECK_INSTANCE_TYPE(entity, list->priv->elementType));
262
263 g_object_ref(entity);
264 preexisting = osinfo_list_find_by_id(list, osinfo_entity_get_id(entity));
265 if (preexisting != NULL) {
266 g_ptr_array_remove(list->priv->array, preexisting);
267 }
268 g_ptr_array_add(list->priv->array, entity);
269 g_hash_table_replace(list->priv->entities,
270 (gchar *)osinfo_entity_get_id(entity), entity);
271 }
272
273
274 /**
275 * osinfo_list_add_filtered:
276 * @list: the entity list
277 * @source: (transfer none): the source for elements
278 * @filter: (transfer none): filter to process the source with
279 *
280 * Adds all entities from @source which are matched by @filter. Using one
281 * of the constructors in a subclass is preferable
282 * to this method.
283 */
osinfo_list_add_filtered(OsinfoList * list,OsinfoList * source,OsinfoFilter * filter)284 void osinfo_list_add_filtered(OsinfoList *list, OsinfoList *source, OsinfoFilter *filter)
285 {
286 int i, len;
287
288 g_return_if_fail(OSINFO_IS_LIST(list));
289 g_return_if_fail(osinfo_list_get_element_type(list) == osinfo_list_get_element_type(source));
290
291 len = osinfo_list_get_length(source);
292 for (i = 0; i < len; i++) {
293 OsinfoEntity *entity = osinfo_list_get_nth(source, i);
294 if (osinfo_filter_matches(filter, entity))
295 osinfo_list_add(list, entity);
296 }
297 }
298
299
300 /**
301 * osinfo_list_add_intersection:
302 * @list: the entity list
303 * @sourceOne: (transfer none): the first list to add
304 * @sourceTwo: (transfer none): the second list to add
305 *
306 * Computes the intersection between @sourceOne and @sourceTwo and
307 * adds the resulting list of entities to the @list. Using one
308 * of the constructors in a subclass is preferable
309 * to this method.
310 */
osinfo_list_add_intersection(OsinfoList * list,OsinfoList * sourceOne,OsinfoList * sourceTwo)311 void osinfo_list_add_intersection(OsinfoList *list, OsinfoList *sourceOne, OsinfoList *sourceTwo)
312 {
313 int i, len;
314 GHashTable *otherSet;
315 GHashTable *newSet;
316
317 g_return_if_fail(OSINFO_IS_LIST(list));
318 g_return_if_fail(osinfo_list_get_element_type(list) == osinfo_list_get_element_type(sourceOne));
319 g_return_if_fail(osinfo_list_get_element_type(list) == osinfo_list_get_element_type(sourceTwo));
320
321 // Make set representation of otherList and newList
322 otherSet = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
323 newSet = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
324
325 // Add all from otherList to otherSet
326 len = osinfo_list_get_length(sourceTwo);
327 for (i = 0; i < len; i++) {
328 OsinfoEntity *entity = osinfo_list_get_nth(sourceTwo, i);
329 g_hash_table_insert(otherSet, g_strdup(osinfo_entity_get_id(entity)), entity);
330 }
331
332 // If other contains entity, and new list does not, add to new list
333 len = osinfo_list_get_length(sourceOne);
334 for (i = 0; i < len; i++) {
335 OsinfoEntity *entity = osinfo_list_get_nth(sourceOne, i);
336
337 if (g_hash_table_lookup(otherSet, osinfo_entity_get_id(entity)) &&
338 !g_hash_table_lookup(newSet, osinfo_entity_get_id(entity))) {
339 g_hash_table_insert(newSet, g_strdup(osinfo_entity_get_id(entity)), entity);
340 osinfo_list_add(list, entity);
341 }
342 }
343
344 g_hash_table_destroy(otherSet);
345 g_hash_table_destroy(newSet);
346 }
347
348
349 /**
350 * osinfo_list_add_union:
351 * @list: the entity list
352 * @sourceOne: (transfer none): the first list to add
353 * @sourceTwo: (transfer none): the second list to add
354 *
355 * Computes the union between @sourceOne and @sourceTwo and
356 * adds the resulting list of entities to the @list. Using one
357 * of the constructors in a subclass is preferable
358 * to this method.
359 */
osinfo_list_add_union(OsinfoList * list,OsinfoList * sourceOne,OsinfoList * sourceTwo)360 void osinfo_list_add_union(OsinfoList *list, OsinfoList *sourceOne, OsinfoList *sourceTwo)
361 {
362 // Make set version of new list
363 GHashTable *newSet;
364 int i, len;
365
366 g_return_if_fail(OSINFO_IS_LIST(list));
367 g_return_if_fail(osinfo_list_get_element_type(list) == osinfo_list_get_element_type(sourceOne));
368 g_return_if_fail(osinfo_list_get_element_type(list) == osinfo_list_get_element_type(sourceTwo));
369
370 newSet = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
371
372 // Add all from first list to new list
373 len = osinfo_list_get_length(sourceOne);
374 for (i = 0; i < len; i++) {
375 OsinfoEntity *entity = osinfo_list_get_nth(sourceOne, i);
376 osinfo_list_add(list, entity);
377 g_hash_table_insert(newSet, g_strdup(osinfo_entity_get_id(entity)), entity);
378 }
379
380 // Add remaining elements from this list to new list
381 len = osinfo_list_get_length(sourceTwo);
382 for (i = 0; i < len; i++) {
383 OsinfoEntity *entity = osinfo_list_get_nth(sourceTwo, i);
384 // If new list does not contain element, add to new list
385 if (!g_hash_table_lookup(newSet, osinfo_entity_get_id(entity))) {
386 osinfo_list_add(list, entity);
387 g_hash_table_insert(newSet, g_strdup(osinfo_entity_get_id(entity)), entity);
388 }
389 }
390
391 g_hash_table_unref(newSet);
392 }
393
394 /**
395 * osinfo_list_add_all:
396 * @list: the entity list
397 * @source: (transfer none): the list to add
398 *
399 * Adds all entities from @source to @list. Using one
400 * of the constructors in a subclass is preferable
401 * to this method.
402 */
osinfo_list_add_all(OsinfoList * list,OsinfoList * source)403 void osinfo_list_add_all(OsinfoList *list, OsinfoList *source)
404 {
405 int i, len;
406
407 g_return_if_fail(OSINFO_IS_LIST(list));
408 g_return_if_fail(osinfo_list_get_element_type(list) == osinfo_list_get_element_type(source));
409
410 len = osinfo_list_get_length(source);
411 for (i = 0; i < len; i++) {
412 OsinfoEntity *entity = osinfo_list_get_nth(source, i);
413 osinfo_list_add(list, entity);
414 }
415 }
416
417 /*
418 * Creates a list of the same type as sourceOne and sourceTwo after
419 * checking they are the same type. The created list elements are
420 * of the same type as the elements of sourceOne and sourceTwo
421 */
osinfo_list_new_same(OsinfoList * sourceOne,OsinfoList * sourceTwo)422 static OsinfoList *osinfo_list_new_same(OsinfoList *sourceOne,
423 OsinfoList *sourceTwo)
424 {
425 GType typeOne = G_OBJECT_TYPE(sourceOne);
426
427 if (sourceTwo != NULL) {
428 GType typeTwo = G_OBJECT_TYPE(sourceTwo);
429
430 g_return_val_if_fail(typeOne == typeTwo, NULL);
431 g_return_val_if_fail(OSINFO_IS_LIST(sourceTwo), NULL);
432 }
433
434 g_return_val_if_fail(OSINFO_IS_LIST(sourceOne), NULL);
435
436 return g_object_new(typeOne,
437 "element-type",
438 osinfo_list_get_element_type(sourceOne),
439 NULL);
440 }
441
442 /**
443 * osinfo_list_new_copy:
444 * @source: the list to copy
445 *
446 * Construct a new list that is filled with elements from @source
447 *
448 * Returns: (transfer full): a copy of the list
449 *
450 * Since: 0.2.2
451 */
osinfo_list_new_copy(OsinfoList * source)452 OsinfoList *osinfo_list_new_copy(OsinfoList *source)
453 {
454 OsinfoList *newList;
455
456 g_return_val_if_fail(OSINFO_IS_LIST(source), NULL);
457
458 newList = osinfo_list_new_same(source, NULL);
459
460 g_return_val_if_fail(OSINFO_IS_LIST(newList), NULL);
461
462 osinfo_list_add_all(newList, source);
463
464 return newList;
465 }
466
467 /**
468 * osinfo_list_new_filtered:
469 * @source: the list to copy
470 * @filter: the filter to apply
471 *
472 * Construct a new list that is filled with elements from @source that
473 * match @filter
474 *
475 * Returns: (transfer full): a filtered copy of the list
476 *
477 * Since: 0.2.2
478 */
osinfo_list_new_filtered(OsinfoList * source,OsinfoFilter * filter)479 OsinfoList *osinfo_list_new_filtered(OsinfoList *source, OsinfoFilter *filter)
480 {
481 OsinfoList *newList;
482
483 g_return_val_if_fail(OSINFO_IS_LIST(source), NULL);
484
485 newList = osinfo_list_new_same(source, NULL);
486
487 g_return_val_if_fail(OSINFO_IS_LIST(newList), NULL);
488
489 osinfo_list_add_filtered(newList, source, filter);
490
491 return newList;
492 }
493
494 /**
495 * osinfo_list_new_intersection:
496 * @sourceOne: the first list to copy
497 * @sourceTwo: the second list to copy
498 *
499 * Construct a new list that is filled with only the elements
500 * that are present in both @sourceOne and @sourceTwo.
501 *
502 * Returns: (transfer full): an intersection of the two lists
503 *
504 * Since: 0.2.2
505 */
osinfo_list_new_intersection(OsinfoList * sourceOne,OsinfoList * sourceTwo)506 OsinfoList *osinfo_list_new_intersection(OsinfoList *sourceOne,
507 OsinfoList *sourceTwo)
508 {
509 OsinfoList *newList;
510
511 g_return_val_if_fail(OSINFO_IS_LIST(sourceOne), NULL);
512 g_return_val_if_fail(OSINFO_IS_LIST(sourceTwo), NULL);
513
514 newList = osinfo_list_new_same(sourceOne, sourceTwo);
515
516 g_return_val_if_fail(OSINFO_IS_LIST(newList), NULL);
517
518 osinfo_list_add_intersection(newList, sourceOne, sourceTwo);
519
520 return newList;
521 }
522
523 /**
524 * osinfo_list_new_union:
525 * @sourceOne: the first list to copy
526 * @sourceTwo: the second list to copy
527 *
528 * Construct a new list that is filled with all the that are present in
529 * either @sourceOne and @sourceTwo. @sourceOne and @sourceTwo must be of
530 * the same type.
531 *
532 * Returns: (transfer full): a union of the two lists
533 *
534 * Since: 0.2.2
535 */
osinfo_list_new_union(OsinfoList * sourceOne,OsinfoList * sourceTwo)536 OsinfoList *osinfo_list_new_union(OsinfoList *sourceOne,
537 OsinfoList *sourceTwo)
538 {
539 OsinfoList *newList;
540
541 g_return_val_if_fail(OSINFO_IS_LIST(sourceOne), NULL);
542 g_return_val_if_fail(OSINFO_IS_LIST(sourceTwo), NULL);
543
544 newList = osinfo_list_new_same(sourceOne, sourceTwo);
545
546 g_return_val_if_fail(OSINFO_IS_LIST(newList), NULL);
547
548 osinfo_list_add_union(newList, sourceOne, sourceTwo);
549
550 return newList;
551 }
552