4 * Purple is the legal property of its developers, whose names are too
5 * numerous to list here. Please refer to the COPYRIGHT file distributed
6 * with this source distribution
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or (at
11 * your option) any later version.
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
28 #include "memorypool.h"
30 #define PURPLE_TRIE_GET_PRIVATE(obj) \
31 (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_TRIE, PurpleTriePrivate))
33 /* A single internal (that don't have any children) consists
34 * of 256 + 4 pointers. That's 1040 bytes on 32-bit machine or 2080 bytes
37 * Thus, in 10500-byte pool block we can hold about 5-10 internal states.
38 * Threshold of 100 states means, we'd need 10-20 "small" blocks before
39 * switching to ~1-2 large blocks.
41 #define PURPLE_TRIE_LARGE_THRESHOLD 100
42 #define PURPLE_TRIE_STATES_SMALL_POOL_BLOCK_SIZE 10880
43 #define PURPLE_TRIE_STATES_LARGE_POOL_BLOCK_SIZE 102400
45 typedef struct _PurpleTrieRecord PurpleTrieRecord
;
46 typedef struct _PurpleTrieState PurpleTrieState
;
47 typedef struct _PurpleTrieRecordList PurpleTrieRecordList
;
51 gboolean reset_on_match
;
53 PurpleMemoryPool
*records_str_mempool
;
54 PurpleMemoryPool
*records_obj_mempool
;
55 PurpleTrieRecordList
*records
;
56 GHashTable
*records_map
;
57 gsize records_total_size
;
59 PurpleMemoryPool
*states_mempool
;
60 PurpleTrieState
*root_state
;
63 struct _PurpleTrieRecord
70 struct _PurpleTrieRecordList
72 PurpleTrieRecord
*rec
;
73 PurpleTrieRecordList
*next
;
74 PurpleTrieRecordList
*prev
;
79 struct _PurpleTrieState
81 PurpleTrieState
*parent
;
82 PurpleTrieState
**children
;
84 PurpleTrieState
*longest_suffix
;
86 PurpleTrieRecord
*found_word
;
91 PurpleTrieState
*state
;
93 PurpleTrieState
*root_state
;
94 gboolean reset_on_match
;
96 PurpleTrieReplaceCb replace_cb
;
97 PurpleTrieFindCb find_cb
;
101 /* TODO: an option to make it eager or lazy (now, it's eager) */
109 static GObjectClass
*parent_class
= NULL
;
110 static GParamSpec
*properties
[PROP_LAST
];
113 /*******************************************************************************
115 ******************************************************************************/
117 static PurpleTrieRecordList
*
118 purple_record_list_new(PurpleMemoryPool
*mpool
, PurpleTrieRecord
*rec
)
120 PurpleTrieRecordList
*node
;
122 node
= purple_memory_pool_alloc0(mpool
,
123 sizeof(PurpleTrieRecordList
), sizeof(gpointer
));
124 g_return_val_if_fail(node
!= NULL
, NULL
);
131 static PurpleTrieRecordList
*
132 purple_record_list_prepend(PurpleMemoryPool
*mpool
,
133 PurpleTrieRecordList
*old_head
, PurpleTrieRecord
*rec
)
135 PurpleTrieRecordList
*new_head
;
137 new_head
= purple_record_list_new(mpool
, rec
);
138 g_return_val_if_fail(new_head
!= NULL
, NULL
);
140 new_head
->next
= old_head
;
142 old_head
->prev
= new_head
;
147 static PurpleTrieRecordList
*
148 purple_record_list_copy(PurpleMemoryPool
*mpool
,
149 const PurpleTrieRecordList
*head
)
151 PurpleTrieRecordList
*new_head
= NULL
, *new_tail
= NULL
;
154 PurpleTrieRecordList
*node
;
156 node
= purple_record_list_new(mpool
, head
->rec
);
157 g_return_val_if_fail(node
!= NULL
, NULL
); /* there is no leak */
159 node
->prev
= new_tail
;
161 new_tail
->next
= node
;
172 static PurpleTrieRecordList
*
173 purple_record_list_remove(PurpleTrieRecordList
*head
,
174 PurpleTrieRecordList
*node
)
176 g_return_val_if_fail(head
!= NULL
, NULL
);
177 g_return_val_if_fail(node
!= NULL
, head
);
178 g_return_val_if_fail(head
->prev
== NULL
, NULL
);
181 if (head
->next
!= NULL
)
182 head
->next
->prev
= NULL
;
185 g_return_val_if_fail(node
->prev
!= NULL
, NULL
);
186 node
->prev
->next
= node
->next
;
187 if (node
->next
!= NULL
)
188 node
->next
->prev
= node
->prev
;
194 /*******************************************************************************
196 ******************************************************************************/
199 purple_trie_states_cleanup(PurpleTrie
*trie
)
201 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
203 g_return_if_fail(priv
!= NULL
);
205 if (priv
->root_state
!= NULL
) {
206 purple_memory_pool_cleanup(priv
->states_mempool
);
207 priv
->root_state
= NULL
;
211 /* Allocates a state and binds it to the parent. */
212 static PurpleTrieState
*
213 purple_trie_state_new(PurpleTrie
*trie
, PurpleTrieState
*parent
, guchar character
)
215 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
216 PurpleTrieState
*state
;
218 g_return_val_if_fail(priv
!= NULL
, NULL
);
220 state
= purple_memory_pool_alloc0(priv
->states_mempool
,
221 sizeof(PurpleTrieState
), sizeof(gpointer
));
222 g_return_val_if_fail(state
!= NULL
, NULL
);
227 state
->parent
= parent
;
228 if (parent
->children
== NULL
) {
229 parent
->children
= purple_memory_pool_alloc0(
230 priv
->states_mempool
,
231 /* PurpleTrieState *children[G_MAXUCHAR + 1] */
232 256 * sizeof(gpointer
),
236 if (parent
->children
== NULL
) {
237 purple_memory_pool_free(priv
->states_mempool
, state
);
242 parent
->children
[character
] = state
;
249 purple_trie_print(PurpleTrieState
*state
, int limit
)
251 GString
*str
= g_string_new(NULL
);
255 return g_strdup("{ LIMIT }");
257 if (state
->found_word
)
258 g_string_append(str
, "*");
259 g_string_append(str
, "{ ");
260 for (i
= 0; i
< 256; i
++) {
262 if (!state
->children
)
264 if (!state
->children
[i
])
267 g_string_append(str
, "(null)->");
269 g_string_append_printf(str
, "%c->", i
);
270 if (state
->children
[i
] == state
)
271 g_string_append(str
, "loop");
273 chp
= purple_trie_print(state
->children
[i
], limit
- 1);
274 g_string_append(str
, chp
);
275 g_string_append_c(str
, ' ');
279 g_string_append(str
, "}");
281 return g_string_free(str
, FALSE
);
286 purple_trie_states_build(PurpleTrie
*trie
)
288 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
289 PurpleTrieState
*root
;
290 PurpleMemoryPool
*reclist_mpool
;
291 PurpleTrieRecordList
*reclist
, *it
;
294 g_return_val_if_fail(priv
!= NULL
, FALSE
);
296 if (priv
->root_state
!= NULL
)
299 if (priv
->records_total_size
< PURPLE_TRIE_LARGE_THRESHOLD
) {
300 purple_memory_pool_set_block_size(priv
->states_mempool
,
301 PURPLE_TRIE_STATES_SMALL_POOL_BLOCK_SIZE
);
303 purple_memory_pool_set_block_size(priv
->states_mempool
,
304 PURPLE_TRIE_STATES_LARGE_POOL_BLOCK_SIZE
);
307 priv
->root_state
= root
= purple_trie_state_new(trie
, NULL
, '\0');
308 g_return_val_if_fail(root
!= NULL
, FALSE
);
309 g_assert(root
->longest_suffix
== NULL
);
311 /* reclist is a list of words not yet added to the trie. Shorter words
312 * are removed from the list, when they are fully added to the trie. */
313 reclist_mpool
= purple_memory_pool_new();
314 reclist
= purple_record_list_copy(reclist_mpool
, priv
->records
);
316 /* extra_data on every element of reclist will be a pointer to a trie
317 * node -- the prefix of the word with len of cur_len */
318 for (it
= reclist
; it
!= NULL
; it
= it
->next
) {
319 it
->extra_data
= root
;
322 /* Iterate over indexes of words -- every loop iteration checks certain
323 * index of all remaining words. Loop finishes when there are no words
324 * longer than cur_len. */
325 for (cur_len
= 0; reclist
!= NULL
; cur_len
++) {
326 for (it
= reclist
; it
; it
= it
->next
) {
327 PurpleTrieRecord
*rec
= it
->rec
;
328 guchar character
= rec
->word
[cur_len
];
329 PurpleTrieState
*prefix
= it
->extra_data
;
330 PurpleTrieState
*lon_suf_parent
;
332 g_assert(character
!= '\0');
334 if (prefix
->children
&& prefix
->children
[character
]) {
335 /* Word's prefix is already in the trie, added
336 * by the other word. */
337 prefix
= prefix
->children
[character
];
339 /* We need to create a new branch of trie. */
340 prefix
= purple_trie_state_new(trie
, prefix
,
344 g_object_unref(reclist_mpool
);
348 it
->extra_data
= prefix
;
349 /* prefix is now of length increased by one character. */
351 /* The whole word is now added to the trie. */
352 if (rec
->word
[cur_len
+ 1] == '\0') {
353 if (prefix
->found_word
== NULL
)
354 prefix
->found_word
= rec
;
356 purple_debug_warning("trie", "found "
357 "a collision of \"%s\" words",
361 /* "it" is not modified here, so it->next is
363 reclist
= purple_record_list_remove(reclist
, it
);
366 /* We need to fill the longest_suffix field -- a longest
367 * complete suffix of the prefix we created. We look for
368 * that suffix in any path starting in root and ending
369 * in the (cur_len - 1) level of trie. */
370 if (prefix
->longest_suffix
!= NULL
)
372 lon_suf_parent
= prefix
->parent
->longest_suffix
;
373 while (lon_suf_parent
) {
374 if (lon_suf_parent
->children
&&
375 lon_suf_parent
->children
[character
])
377 prefix
->longest_suffix
= lon_suf_parent
->
381 lon_suf_parent
= lon_suf_parent
->longest_suffix
;
383 if (prefix
->longest_suffix
== NULL
)
384 prefix
->longest_suffix
= root
;
385 if (prefix
->found_word
== NULL
) {
387 prefix
->longest_suffix
->found_word
;
392 g_object_unref(reclist_mpool
);
397 /*******************************************************************************
399 ******************************************************************************/
402 purple_trie_advance(PurpleTrieMachine
*m
, const guchar character
)
404 /* change state after processing a character */
406 /* Perfect fit - next character is the same, as the child of the
407 * prefix we reached so far. */
408 if (m
->state
->children
&& m
->state
->children
[character
]) {
409 m
->state
= m
->state
->children
[character
];
413 /* We reached root, that's a pity. */
414 if (m
->state
== m
->root_state
)
417 /* Let's try a bit shorter suffix. */
418 m
->state
= m
->state
->longest_suffix
;
423 purple_trie_replace_do_replacement(PurpleTrieMachine
*m
, GString
*out
)
425 gboolean was_replaced
= FALSE
;
428 /* if we reached a "found" state, let's process it */
429 if (!m
->state
->found_word
)
432 /* let's get back to the beginning of the word */
433 g_assert(out
->len
>= m
->state
->found_word
->word_len
- 1);
434 str_old_len
= out
->len
;
435 out
->len
-= m
->state
->found_word
->word_len
- 1;
437 was_replaced
= m
->replace_cb(out
, m
->state
->found_word
->word
,
438 m
->state
->found_word
->data
, m
->user_data
);
440 /* output was untouched, revert to the previous position */
442 out
->len
= str_old_len
;
445 if (was_replaced
|| m
->reset_on_match
)
446 m
->state
= m
->root_state
;
452 purple_trie_find_do_discovery(PurpleTrieMachine
*m
)
454 gboolean was_accepted
;
456 /* if we reached a "found" state, let's process it */
457 if (!m
->state
->found_word
)
461 was_accepted
= m
->find_cb(m
->state
->found_word
->word
,
462 m
->state
->found_word
->data
, m
->user_data
);
467 if (was_accepted
&& m
->reset_on_match
)
468 m
->state
= m
->root_state
;
474 purple_trie_replace(PurpleTrie
*trie
, const gchar
*src
,
475 PurpleTrieReplaceCb replace_cb
, gpointer user_data
)
477 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
478 PurpleTrieMachine machine
;
485 g_return_val_if_fail(replace_cb
!= NULL
, g_strdup(src
));
486 g_return_val_if_fail(priv
!= NULL
, NULL
);
488 purple_trie_states_build(trie
);
490 machine
.state
= priv
->root_state
;
491 machine
.root_state
= priv
->root_state
;
492 machine
.reset_on_match
= priv
->reset_on_match
;
493 machine
.replace_cb
= replace_cb
;
494 machine
.user_data
= user_data
;
496 out
= g_string_new(NULL
);
498 while (src
[i
] != '\0') {
499 guchar character
= src
[i
++];
500 gboolean was_replaced
;
502 purple_trie_advance(&machine
, character
);
503 was_replaced
= purple_trie_replace_do_replacement(&machine
, out
);
505 /* We skipped a character without finding any records,
506 * let's just copy it to the output. */
508 g_string_append_c(out
, character
);
511 return g_string_free(out
, FALSE
);
515 purple_trie_multi_replace(const GSList
*tries
, const gchar
*src
,
516 PurpleTrieReplaceCb replace_cb
, gpointer user_data
)
518 guint tries_count
, m_idx
;
519 PurpleTrieMachine
*machines
;
526 g_return_val_if_fail(replace_cb
!= NULL
, g_strdup(src
));
528 tries_count
= g_slist_length((GSList
*)tries
);
529 if (tries_count
== 0)
530 return g_strdup(src
);
532 /* Initialize all machines. */
533 machines
= g_new(PurpleTrieMachine
, tries_count
);
534 for (i
= 0; i
< tries_count
; i
++, tries
= tries
->next
) {
535 PurpleTrie
*trie
= tries
->data
;
536 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
544 purple_trie_states_build(trie
);
546 machines
[i
].state
= priv
->root_state
;
547 machines
[i
].root_state
= priv
->root_state
;
548 machines
[i
].reset_on_match
= priv
->reset_on_match
;
549 machines
[i
].replace_cb
= replace_cb
;
550 machines
[i
].user_data
= user_data
;
553 out
= g_string_new(NULL
);
555 while (src
[i
] != '\0') {
556 guchar character
= src
[i
++];
557 gboolean was_replaced
= FALSE
;
559 /* Advance every machine and possibly perform a replacement. */
560 for (m_idx
= 0; m_idx
< tries_count
; m_idx
++) {
561 purple_trie_advance(&machines
[m_idx
], character
);
564 was_replaced
= purple_trie_replace_do_replacement(
565 &machines
[m_idx
], out
);
568 /* We skipped a character without finding any records,
569 * let's just copy it to the output. */
571 g_string_append_c(out
, character
);
573 /* If we replaced a word, reset _all_ machines */
575 for (m_idx
= 0; m_idx
< tries_count
; m_idx
++) {
576 machines
[m_idx
].state
=
577 machines
[m_idx
].root_state
;
583 return g_string_free(out
, FALSE
);
587 purple_trie_find(PurpleTrie
*trie
, const gchar
*src
,
588 PurpleTrieFindCb find_cb
, gpointer user_data
)
590 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
591 PurpleTrieMachine machine
;
592 gulong found_count
= 0;
598 g_return_val_if_fail(priv
!= NULL
, 0);
600 purple_trie_states_build(trie
);
602 machine
.state
= priv
->root_state
;
603 machine
.root_state
= priv
->root_state
;
604 machine
.reset_on_match
= priv
->reset_on_match
;
605 machine
.find_cb
= find_cb
;
606 machine
.user_data
= user_data
;
609 while (src
[i
] != '\0') {
610 guchar character
= src
[i
++];
613 purple_trie_advance(&machine
, character
);
615 was_found
= purple_trie_find_do_discovery(&machine
);
625 purple_trie_multi_find(const GSList
*tries
, const gchar
*src
,
626 PurpleTrieFindCb find_cb
, gpointer user_data
)
628 guint tries_count
, m_idx
;
629 PurpleTrieMachine
*machines
;
630 gulong found_count
= 0;
636 tries_count
= g_slist_length((GSList
*)tries
);
637 if (tries_count
== 0)
640 /* Initialize all machines. */
641 machines
= g_new(PurpleTrieMachine
, tries_count
);
642 for (i
= 0; i
< tries_count
; i
++, tries
= tries
->next
) {
643 PurpleTrie
*trie
= tries
->data
;
644 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
652 purple_trie_states_build(trie
);
654 machines
[i
].state
= priv
->root_state
;
655 machines
[i
].root_state
= priv
->root_state
;
656 machines
[i
].reset_on_match
= priv
->reset_on_match
;
657 machines
[i
].find_cb
= find_cb
;
658 machines
[i
].user_data
= user_data
;
662 while (src
[i
] != '\0') {
663 guchar character
= src
[i
++];
664 gboolean was_found
= FALSE
;
666 /* Advance every machine and possibly perform a replacement. */
667 for (m_idx
= 0; m_idx
< tries_count
; m_idx
++) {
668 purple_trie_advance(&machines
[m_idx
], character
);
672 purple_trie_find_do_discovery(&machines
[m_idx
]);
677 /* If we replaced a word, reset _all_ machines */
679 for (m_idx
= 0; m_idx
< tries_count
; m_idx
++) {
680 if (!machines
[m_idx
].reset_on_match
)
682 machines
[m_idx
].state
=
683 machines
[m_idx
].root_state
;
693 /*******************************************************************************
695 ******************************************************************************/
698 purple_trie_add(PurpleTrie
*trie
, const gchar
*word
, gpointer data
)
700 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
701 PurpleTrieRecord
*rec
;
703 g_return_val_if_fail(priv
!= NULL
, FALSE
);
704 g_return_val_if_fail(word
!= NULL
, FALSE
);
705 g_return_val_if_fail(word
[0] != '\0', FALSE
);
707 if (g_hash_table_lookup(priv
->records_map
, word
) != NULL
) {
708 purple_debug_warning("trie", "record exists: %s", word
);
712 /* Every change in a trie invalidates longest_suffix map.
713 * These prefixes could be updated instead of cleaning the whole graph.
715 purple_trie_states_cleanup(trie
);
717 rec
= purple_memory_pool_alloc(priv
->records_obj_mempool
,
718 sizeof(PurpleTrieRecord
), sizeof(gpointer
));
719 rec
->word
= purple_memory_pool_strdup(priv
->records_str_mempool
, word
);
720 rec
->word_len
= strlen(word
);
721 g_assert(rec
->word_len
> 0);
724 priv
->records_total_size
+= rec
->word_len
;
725 priv
->records
= purple_record_list_prepend(priv
->records_obj_mempool
,
727 g_hash_table_insert(priv
->records_map
, rec
->word
, priv
->records
);
733 purple_trie_remove(PurpleTrie
*trie
, const gchar
*word
)
735 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
736 PurpleTrieRecordList
*it
;
738 g_return_if_fail(priv
!= NULL
);
739 g_return_if_fail(word
!= NULL
);
740 g_return_if_fail(word
[0] != '\0');
742 it
= g_hash_table_lookup(priv
->records_map
, word
);
746 /* see purple_trie_add */
747 purple_trie_states_cleanup(trie
);
749 priv
->records_total_size
-= it
->rec
->word_len
;
750 priv
->records
= purple_record_list_remove(priv
->records
, it
);
751 g_hash_table_remove(priv
->records_map
, it
->rec
->word
);
753 purple_memory_pool_free(priv
->records_str_mempool
, it
->rec
->word
);
754 purple_memory_pool_free(priv
->records_obj_mempool
, it
->rec
);
755 purple_memory_pool_free(priv
->records_obj_mempool
, it
);
759 purple_trie_get_size(PurpleTrie
*trie
)
761 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
763 g_return_val_if_fail(priv
!= NULL
, 0);
765 return g_hash_table_size(priv
->records_map
);
769 /*******************************************************************************
771 ******************************************************************************/
774 purple_trie_get_reset_on_match(PurpleTrie
*trie
)
776 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
778 g_return_val_if_fail(priv
, FALSE
);
780 return priv
->reset_on_match
;
784 purple_trie_set_reset_on_match(PurpleTrie
*trie
, gboolean reset
)
786 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
788 g_return_if_fail(priv
);
790 priv
->reset_on_match
= reset
;
791 g_object_notify_by_pspec(G_OBJECT(trie
), properties
[PROP_RESET_ON_MATCH
]);
794 /*******************************************************************************
796 ******************************************************************************/
799 purple_trie_new(void)
801 return g_object_new(PURPLE_TYPE_TRIE
, NULL
);
805 purple_trie_init(GTypeInstance
*instance
, gpointer klass
)
807 PurpleTrie
*trie
= PURPLE_TRIE(instance
);
808 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
810 priv
->records_obj_mempool
= purple_memory_pool_new();
811 priv
->records_str_mempool
= purple_memory_pool_new();
812 priv
->states_mempool
= purple_memory_pool_new();
813 purple_memory_pool_set_block_size(priv
->states_mempool
,
814 PURPLE_TRIE_STATES_SMALL_POOL_BLOCK_SIZE
);
816 priv
->records_map
= g_hash_table_new(g_str_hash
, g_str_equal
);
820 purple_trie_finalize(GObject
*obj
)
822 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(obj
);
824 g_hash_table_destroy(priv
->records_map
);
825 g_object_unref(priv
->records_obj_mempool
);
826 g_object_unref(priv
->records_str_mempool
);
827 g_object_unref(priv
->states_mempool
);
829 G_OBJECT_CLASS(parent_class
)->finalize(obj
);
833 purple_trie_get_property(GObject
*obj
, guint param_id
, GValue
*value
,
836 PurpleTrie
*trie
= PURPLE_TRIE(obj
);
837 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
840 case PROP_RESET_ON_MATCH
:
841 g_value_set_boolean(value
, priv
->reset_on_match
);
844 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj
, param_id
, pspec
);
849 purple_trie_set_property(GObject
*obj
, guint param_id
,
850 const GValue
*value
, GParamSpec
*pspec
)
852 PurpleTrie
*trie
= PURPLE_TRIE(obj
);
853 PurpleTriePrivate
*priv
= PURPLE_TRIE_GET_PRIVATE(trie
);
856 case PROP_RESET_ON_MATCH
:
857 priv
->reset_on_match
= g_value_get_boolean(value
);
860 G_OBJECT_WARN_INVALID_PROPERTY_ID(obj
, param_id
, pspec
);
865 purple_trie_class_init(PurpleTrieClass
*klass
)
867 GObjectClass
*obj_class
= G_OBJECT_CLASS(klass
);
869 parent_class
= g_type_class_peek_parent(klass
);
871 g_type_class_add_private(klass
, sizeof(PurpleTriePrivate
));
873 obj_class
->finalize
= purple_trie_finalize
;
874 obj_class
->get_property
= purple_trie_get_property
;
875 obj_class
->set_property
= purple_trie_set_property
;
877 properties
[PROP_RESET_ON_MATCH
] = g_param_spec_boolean("reset-on-match",
878 "Reset on match", "Determines, if the search state machine "
879 "should be reset to the initial state on every match. This "
880 "ensures, that every match is distinct from each other. "
881 "Please note, that it's not well-defined for a replace "
882 "operation, so it's better to leave this value default, unless "
883 "you perform only find operations.", TRUE
,
884 G_PARAM_READWRITE
| G_PARAM_CONSTRUCT
| G_PARAM_STATIC_STRINGS
);
886 g_object_class_install_properties(obj_class
, PROP_LAST
, properties
);
890 purple_trie_get_type(void)
892 static GType type
= 0;
894 if (G_UNLIKELY(type
== 0)) {
895 static const GTypeInfo info
= {
896 .class_size
= sizeof(PurpleTrieClass
),
897 .class_init
= (GClassInitFunc
)purple_trie_class_init
,
898 .instance_size
= sizeof(PurpleTrie
),
899 .instance_init
= purple_trie_init
,
902 type
= g_type_register_static(G_TYPE_OBJECT
,
903 "PurpleTrie", &info
, 0);