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