[glib/gvariant] gvarianttypeinfo cleanups
- From: Ryan Lortie <ryanl src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [glib/gvariant] gvarianttypeinfo cleanups
- Date: Fri, 29 Jan 2010 17:08:26 +0000 (UTC)
commit 33f2c3450aa16934e06d969c8475b3ae6e954a08
Author: Ryan Lortie <desrt desrt ca>
Date: Fri Jan 29 12:08:13 2010 -0500
gvarianttypeinfo cleanups
glib/gvariant-core.c | 4 +-
glib/gvariant-serialiser.c | 14 +-
glib/gvarianttypeinfo.c | 342 ++++++++++++++++++++++++-------------------
glib/gvarianttypeinfo.h | 80 +++++++++--
4 files changed, 270 insertions(+), 170 deletions(-)
---
diff --git a/glib/gvariant-core.c b/glib/gvariant-core.c
index 4738d0d..d2e0fe1 100644
--- a/glib/gvariant-core.c
+++ b/glib/gvariant-core.c
@@ -895,7 +895,7 @@ g_variant_get_type (GVariant *value)
g_return_val_if_fail (value != NULL, NULL);
g_variant_assert_invariant (value);
- return g_variant_type_info_get_type (value->type);
+ return G_VARIANT_TYPE (g_variant_type_info_get_type_string (value->type));
}
/**
@@ -1491,7 +1491,7 @@ GVariantClass
g_variant_classify (GVariant *value)
{
g_return_val_if_fail (value != NULL, 0);
- return g_variant_type_info_get_class (value->type);
+ return g_variant_type_info_get_type_char (value->type);
}
#define __G_VARIANT_CORE_C__
diff --git a/glib/gvariant-serialiser.c b/glib/gvariant-serialiser.c
index 259554d..57b32bb 100644
--- a/glib/gvariant-serialiser.c
+++ b/glib/gvariant-serialiser.c
@@ -147,7 +147,7 @@ g_variant_serialised_n_children (GVariantSerialised container)
{
g_variant_serialised_assert_invariant (container);
- switch (g_variant_type_info_get_class (container.type))
+ switch (g_variant_type_info_get_type_char (container.type))
{
case G_VARIANT_CLASS_VARIANT:
return 1;
@@ -236,7 +236,7 @@ g_variant_serialised_get_child (GVariantSerialised container,
{
g_variant_serialised_assert_invariant (container);
- switch (g_variant_type_info_get_class (container.type))
+ switch (g_variant_type_info_get_type_char (container.type))
{
case G_VARIANT_CLASS_VARIANT:
{
@@ -481,7 +481,7 @@ g_variant_serialiser_serialise (GVariantSerialised container,
const gpointer *children,
gsize n_children)
{
- switch (g_variant_type_info_get_class (container.type))
+ switch (g_variant_type_info_get_type_char (container.type))
{
case G_VARIANT_CLASS_VARIANT:
{
@@ -496,7 +496,7 @@ g_variant_serialiser_serialise (GVariantSerialised container,
*/
gvs_filler (&child, children[0]);
- type_string = g_variant_type_info_get_string (child.type);
+ type_string = g_variant_type_info_get_type_string (child.type);
type_length = strlen (type_string);
/* write the separator byte */
@@ -673,7 +673,7 @@ g_variant_serialiser_needed_size (GVariantTypeInfo *type,
const gpointer *children,
gsize n_children)
{
- switch (g_variant_type_info_get_class (type))
+ switch (g_variant_type_info_get_type_char (type))
{
case G_VARIANT_CLASS_VARIANT:
{
@@ -683,7 +683,7 @@ g_variant_serialiser_needed_size (GVariantTypeInfo *type,
g_assert_cmpint (n_children, ==, 1);
gvs_filler (&child, children[0]);
- type_string = g_variant_type_info_get_string (child.type);
+ type_string = g_variant_type_info_get_type_string (child.type);
return child.size + 1 + strlen (type_string);
}
@@ -910,7 +910,7 @@ g_variant_serialised_is_normal (GVariantSerialised value)
{
#define fail return FALSE
//#define fail g_assert_not_reached ()
- switch (g_variant_type_info_get_class (value.type))
+ switch (g_variant_type_info_get_type_char (value.type))
{
case G_VARIANT_CLASS_BYTE:
case G_VARIANT_CLASS_INT16:
diff --git a/glib/gvarianttypeinfo.c b/glib/gvarianttypeinfo.c
index f1e2145..8ad2437 100644
--- a/glib/gvarianttypeinfo.c
+++ b/glib/gvarianttypeinfo.c
@@ -22,17 +22,47 @@
#include "galias.h"
-struct OPAQUE_TYPE__GVariantTypeInfo
-{
- GVariantType *type;
- const gchar *str; /* XXX to make my debugging life easier... */
+/* < private >
+ * GVariantTypeInfo:
+ *
+ * This structure contains the necessary information to facilitate the
+ * serialisation and fast deserialisation of a given type of GVariant
+ * value. It also contains the type string itself. A GVariant instance
+ * holds a pointer to one of these structures to provide for efficient
+ * operation.
+ *
+ * The GVariantTypeInfo structures for all of the base types, plus the
+ * "variant" type are stored in a read-only static array.
+ *
+ * For containe types, a hash table and reference counting is used to
+ * ensure that only one of these structures exists for any given type.
+ * In general, a container GVariantTypeInfo will exist for a given type
+ * only if one or more GVariant instances of that type exist or if
+ * another GVariantTypeInfo has that type as a subtype. For example, if
+ * a process contains a single GVariant instance with type "(asv)", then
+ * container GVariantTypeInfo structures will exist for "(asv)" and
+ * for "as" (note that "s" and "v" always exist in the static array).
+ *
+ * The trickiest part of GVariantTypeInfo (and in fact, the major reason
+ * for its existance) is the storage of somewhat magical constants that
+ * allow for O(1) lookups of items in tuples. This is described below.
+ */
+struct _GVariantTypeInfo
+{
+ gchar *type_string;
gsize fixed_size;
- guchar info_class;
guchar alignment;
+
+ guchar info_class;
gint ref_count;
};
+/* For 'array' and 'maybe' types, we store some extra information on the
+ * end of the GVariantTypeInfo struct -- the element type (ie: "s" for
+ * "as"). The container GVariantTypeInfo structure holds a reference to
+ * the element typeinfo.
+ */
typedef struct
{
GVariantTypeInfo self;
@@ -40,6 +70,10 @@ typedef struct
GVariantTypeInfo *element;
} ArrayInfo;
+/* For 'tuple' and 'dict entry' types, we store extra information for
+ * each member -- its type and how to find it inside the serialised data
+ * in O(1) time using 4 variables -- 'i', 'a', 'b', and 'c'.
+ */
typedef struct
{
GVariantTypeInfo self;
@@ -48,29 +82,72 @@ typedef struct
gsize n_members;
} TupleInfo;
-/* == query == */
-const GVariantType *
-g_variant_type_info_get_type (GVariantTypeInfo *info)
+static const GVariantTypeInfo g_variant_type_info_basic_table[24] = {
+#define type_string(x) ((gchar *) (x))
+#define fixed_aligned(x) x, x - 1
+#define unaligned 0, 0
+#define aligned(x) 0, x - 1
+ /* 'b' */ { type_string(G_VARIANT_TYPE_BOOLEAN), fixed_aligned(1) },
+ /* 'c' */ { },
+ /* 'd' */ { type_string(G_VARIANT_TYPE_DOUBLE), fixed_aligned(8) },
+ /* 'e' */ { },
+ /* 'f' */ { },
+ /* 'g' */ { type_string(G_VARIANT_TYPE_SIGNATURE), unaligned },
+ /* 'h' */ { type_string(G_VARIANT_TYPE_HANDLE), fixed_aligned(4) },
+ /* 'i' */ { type_string(G_VARIANT_TYPE_INT32), fixed_aligned(4) },
+ /* 'j' */ { },
+ /* 'k' */ { },
+ /* 'l' */ { },
+ /* 'm' */ { },
+ /* 'n' */ { type_string(G_VARIANT_TYPE_INT16), fixed_aligned(2) },
+ /* 'o' */ { type_string(G_VARIANT_TYPE_OBJECT_PATH), unaligned },
+ /* 'p' */ { },
+ /* 'q' */ { type_string(G_VARIANT_TYPE_UINT16), fixed_aligned(2) },
+ /* 'r' */ { },
+ /* 's' */ { type_string(G_VARIANT_TYPE_STRING), unaligned },
+ /* 't' */ { type_string(G_VARIANT_TYPE_UINT64), fixed_aligned(8) },
+ /* 'u' */ { type_string(G_VARIANT_TYPE_UINT32), fixed_aligned(4) },
+ /* 'v' */ { type_string(G_VARIANT_TYPE_VARIANT), aligned(8) },
+ /* 'w' */ { },
+ /* 'x' */ { type_string(G_VARIANT_TYPE_INT64), fixed_aligned(8) },
+ /* 'y' */ { type_string(G_VARIANT_TYPE_BYTE), fixed_aligned(1) },
+#undef type_string
+#undef fixed_aligned
+#undef unaligned
+#undef aligned
+};
+
+static void
+g_variant_type_info_check (const GVariantTypeInfo *info,
+ char info_class)
{
- g_assert_cmpint (info->ref_count, >, 0);
+ g_assert (info->alignment == 0 || info->alignment == 1 ||
+ info->alignment == 3 || info->alignment == 7);
+ g_assert (info->type_string != NULL);
+ g_assert (!info_class || info->info_class == info_class);
- return info->type;
-}
+ if (info->info_class)
+ {
+ g_assert_cmpint (info->ref_count, >, 0);
+ }
+ else
+ {
+ gint index;
-const gchar *
-g_variant_type_info_get_string (GVariantTypeInfo *info)
-{
- g_assert_cmpint (info->ref_count, >, 0);
+ g_assert_cmpint (info->ref_count, ==, 0);
+ index = info->type_string[0] - 'b';
- return (const gchar *) info->type;
+ g_assert (g_variant_type_info_basic_table + index == info);
+ }
}
-GVariantClass
-g_variant_type_info_get_class (GVariantTypeInfo *info)
+/* == query == */
+const gchar *
+g_variant_type_info_get_type_string (GVariantTypeInfo *info)
{
- g_assert_cmpint (info->ref_count, >, 0);
+ g_variant_type_info_check (info, 0);
- return *info->str;
+ return info->type_string;
}
void
@@ -78,7 +155,7 @@ g_variant_type_info_query (GVariantTypeInfo *info,
guint *alignment,
gsize *fixed_size)
{
- g_assert_cmpint (info->ref_count, >, 0);
+ g_variant_type_info_check (info, 0);
if (alignment)
*alignment = info->alignment;
@@ -92,14 +169,18 @@ g_variant_type_info_query (GVariantTypeInfo *info,
static ArrayInfo *
ARRAY_INFO (GVariantTypeInfo *info)
{
- g_assert (info->info_class == ARRAY_INFO_CLASS);
+ g_variant_type_info_check (info, ARRAY_INFO_CLASS);
+
return (ArrayInfo *) info;
}
static void
array_info_free (GVariantTypeInfo *info)
{
- ArrayInfo *array_info = ARRAY_INFO (info);
+ ArrayInfo *array_info;
+
+ g_assert (info->info_class == ARRAY_INFO_CLASS);
+ array_info = (ArrayInfo *) info;
g_variant_type_info_unref (array_info->element);
g_slice_free (ArrayInfo, array_info);
@@ -135,21 +216,25 @@ g_variant_type_info_query_element (GVariantTypeInfo *info,
alignment, fixed_size);
}
-/* == tupleure == */
-#define TUPLE_INFO_CLASS 's'
+/* == tuple == */
+#define TUPLE_INFO_CLASS 'r'
static TupleInfo *
TUPLE_INFO (GVariantTypeInfo *info)
{
- g_assert (info->info_class == TUPLE_INFO_CLASS);
+ g_variant_type_info_check (info, TUPLE_INFO_CLASS);
+
return (TupleInfo *) info;
}
static void
tuple_info_free (GVariantTypeInfo *info)
{
- TupleInfo *tuple_info = TUPLE_INFO (info);
+ TupleInfo *tuple_info;
gint i;
+ g_assert (info->info_class == TUPLE_INFO_CLASS);
+ tuple_info = (TupleInfo *) info;
+
for (i = 0; i < tuple_info->n_members; i++)
g_variant_type_info_unref (tuple_info->members[i].type);
@@ -289,8 +374,6 @@ tuple_info_new (const GVariantType *type)
gsize
g_variant_type_info_n_members (GVariantTypeInfo *info)
{
- g_assert_cmpint (info->ref_count, >, 0);
-
return TUPLE_INFO (info)->n_members;
}
@@ -308,69 +391,6 @@ g_variant_type_info_member_info (GVariantTypeInfo *info,
return NULL;
}
-/* == base == */
-#define BASE_INFO_CLASS '\0'
-static GVariantTypeInfo *
-base_info_new (const GVariantClass class)
-{
- GVariantTypeInfo *info;
-
- info = g_slice_new (GVariantTypeInfo);
- info->info_class = BASE_INFO_CLASS;
-
- switch (class)
- {
- case G_VARIANT_CLASS_BOOLEAN:
- case G_VARIANT_CLASS_BYTE:
- info->alignment = 1 - 1;
- info->fixed_size = 1;
- break;
-
- case G_VARIANT_CLASS_UINT16:
- case G_VARIANT_CLASS_INT16:
- info->alignment = 2 - 1;
- info->fixed_size = 2;
- break;
-
- case G_VARIANT_CLASS_UINT32:
- case G_VARIANT_CLASS_INT32:
- case G_VARIANT_CLASS_HANDLE:
- info->alignment = 4 - 1;
- info->fixed_size = 4;
- break;
-
- case G_VARIANT_CLASS_UINT64:
- case G_VARIANT_CLASS_INT64:
- case G_VARIANT_CLASS_DOUBLE:
- info->alignment = 8 - 1;
- info->fixed_size = 8;
- break;
-
- case G_VARIANT_CLASS_VARIANT:
- info->alignment = 8 - 1;
- info->fixed_size = 0;
- break;
-
- case G_VARIANT_CLASS_STRING:
- case G_VARIANT_CLASS_OBJECT_PATH:
- case G_VARIANT_CLASS_SIGNATURE:
- info->alignment = 1 - 1;
- info->fixed_size = 0;
- break;
-
- default:
- g_error ("GVariantTypeInfo: not a valid type character: '%c'", class);
- }
-
- return info;
-}
-
-static void
-base_info_free (GVariantTypeInfo *info)
-{
- g_slice_free (GVariantTypeInfo, info);
-}
-
/* == new/ref/unref == */
static GStaticRecMutex g_variant_type_info_lock = G_STATIC_REC_MUTEX_INIT;
static GHashTable *g_variant_type_info_table;
@@ -378,56 +398,81 @@ static GHashTable *g_variant_type_info_table;
GVariantTypeInfo *
g_variant_type_info_get (const GVariantType *type)
{
- GVariantTypeInfo *info;
-
- if G_UNLIKELY (g_variant_type_info_table == NULL)
- g_variant_type_info_table = g_hash_table_new (g_variant_type_hash,
- g_variant_type_equal);
+ char type_char;
- g_static_rec_mutex_lock (&g_variant_type_info_lock);
- info = g_hash_table_lookup (g_variant_type_info_table, type);
+ type_char = g_variant_type_peek_string (type)[0];
- if (info == NULL)
+ if (type_char == G_VARIANT_TYPE_INFO_CHAR_MAYBE ||
+ type_char == G_VARIANT_TYPE_INFO_CHAR_ARRAY ||
+ type_char == G_VARIANT_TYPE_INFO_CHAR_TUPLE ||
+ type_char == G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY)
{
- GVariantClass class;
-
- class = *(const gchar *) type;
-
- switch (class)
- {
- case G_VARIANT_CLASS_MAYBE:
- case G_VARIANT_CLASS_ARRAY:
- info = array_info_new (type);
- break;
-
- case G_VARIANT_CLASS_TUPLE:
- case G_VARIANT_CLASS_DICT_ENTRY:
- info = tuple_info_new (type);
- break;
-
- default:
- info = base_info_new (class);
- break;
- }
-
- info->type = g_variant_type_copy (type);
- g_hash_table_insert (g_variant_type_info_table, info->type, info);
- info->ref_count = 1;
- info->str = (const gchar *) info->type;
+ GVariantTypeInfo *info;
+ gchar *type_string;
+
+ if G_UNLIKELY (g_variant_type_info_table == NULL)
+ g_variant_type_info_table = g_hash_table_new (g_str_hash,
+ g_str_equal);
+
+ type_string = g_variant_type_dup_string (type);
+
+ g_static_rec_mutex_lock (&g_variant_type_info_lock);
+ info = g_hash_table_lookup (g_variant_type_info_table, type_string);
+
+ if (info == NULL)
+ {
+ if (type_char == G_VARIANT_TYPE_INFO_CHAR_MAYBE ||
+ type_char == G_VARIANT_TYPE_INFO_CHAR_ARRAY)
+ {
+ info = array_info_new (type);
+ }
+ else /* tuple or dict entry */
+ {
+ info = tuple_info_new (type);
+ }
+
+ info->type_string = type_string;
+ info->ref_count = 1;
+
+ g_hash_table_insert (g_variant_type_info_table, type_string, info);
+ type_string = NULL;
+ }
+ else
+ g_variant_type_info_ref (info);
+
+ g_static_rec_mutex_unlock (&g_variant_type_info_lock);
+ g_variant_type_info_check (info, 0);
+ g_free (type_string);
+
+ return info;
}
else
- g_variant_type_info_ref (info);
+ {
+ const GVariantTypeInfo *info;
+ int index;
- g_static_rec_mutex_unlock (&g_variant_type_info_lock);
+ index = type_char - 'b';
+ g_assert (G_N_ELEMENTS (g_variant_type_info_basic_table) == 24);
+ g_assert_cmpint (0, <=, index);
+ g_assert_cmpint (index, <, 24);
- return info;
+ info = g_variant_type_info_basic_table + index;
+ g_variant_type_info_check (info, 0);
+
+ return (GVariantTypeInfo *) info;
+ }
}
GVariantTypeInfo *
g_variant_type_info_ref (GVariantTypeInfo *info)
{
- g_assert_cmpint (info->ref_count, >, 0);
- g_atomic_int_inc (&info->ref_count);
+ g_variant_type_info_check (info, 0);
+
+ if (info->info_class)
+ {
+ g_assert_cmpint (info->ref_count, >, 0);
+ g_atomic_int_inc (&info->ref_count);
+ }
return info;
}
@@ -435,33 +480,28 @@ g_variant_type_info_ref (GVariantTypeInfo *info)
void
g_variant_type_info_unref (GVariantTypeInfo *info)
{
- g_assert_cmpint (info->ref_count, >, 0);
+ g_variant_type_info_check (info, 0);
- if (g_atomic_int_dec_and_test (&info->ref_count))
+ if (info->info_class)
{
- g_static_rec_mutex_lock (&g_variant_type_info_lock);
- g_hash_table_remove (g_variant_type_info_table, info->type);
- g_static_rec_mutex_unlock (&g_variant_type_info_lock);
+ g_assert_cmpint (info->ref_count, >, 0);
- g_variant_type_free (info->type);
+ if (g_atomic_int_dec_and_test (&info->ref_count))
+ {
+ g_static_rec_mutex_lock (&g_variant_type_info_lock);
+ g_hash_table_remove (g_variant_type_info_table, info->type_string);
+ g_static_rec_mutex_unlock (&g_variant_type_info_lock);
- switch (info->info_class)
- {
- case ARRAY_INFO_CLASS:
- array_info_free (info);
- break;
+ g_free (info->type_string);
- case TUPLE_INFO_CLASS:
- tuple_info_free (info);
- break;
+ if (info->info_class == ARRAY_INFO_CLASS)
+ array_info_free (info);
- case BASE_INFO_CLASS:
- base_info_free (info);
- break;
+ else if (info->info_class == TUPLE_INFO_CLASS)
+ tuple_info_free (info);
- default:
- g_error ("GVariantTypeInfo with invalid class '%c'",
- info->info_class);
- }
+ else
+ g_assert_not_reached ();
+ }
}
}
diff --git a/glib/gvarianttypeinfo.h b/glib/gvarianttypeinfo.h
index c23af94..d34f885 100644
--- a/glib/gvarianttypeinfo.h
+++ b/glib/gvarianttypeinfo.h
@@ -20,9 +20,76 @@
#ifndef __G_VARIANT_TYPE_INFO_H__
#define __G_VARIANT_TYPE_INFO_H__
-#include "gvariant.h"
+#include <glib/gvarianttype.h>
-typedef struct OPAQUE_TYPE__GVariantTypeInfo GVariantTypeInfo;
+#define G_VARIANT_TYPE_INFO_CHAR_MAYBE 'm'
+#define G_VARIANT_TYPE_INFO_CHAR_ARRAY 'a'
+#define G_VARIANT_TYPE_INFO_CHAR_TUPLE '('
+#define G_VARIANT_TYPE_INFO_CHAR_DICT_ENTRY '{'
+#define g_variant_type_info_get_type_char(info) \
+ (g_variant_type_info_get_type_string(info)[0])
+
+typedef struct _GVariantTypeInfo GVariantTypeInfo;
+
+/* < private >
+ * GVariantMemberInfo:
+ *
+ * This structure describes how to rapidly construct a GVariant instance
+ * corresponding to a given child of a tuple or dictionary entry. It
+ * contains the type of the child, along with 4 constants that allow the
+ * bounds of the child's serialised data within the container's
+ * serialised data to be found very efficiently.
+ *
+ * Since dictionary entries are serialised as if they were tuples of 2
+ * items, the term "tuple" will be used here in the general sense to
+ * refer to tuples and dictionary entries.
+ *
+ * BACKGROUND:
+ * The serialised data for a tuple contains an array of "offsets" at
+ * the end. There is one "offset" in this array for each
+ * variable-sized item in the tuple (except for the last one). The
+ * offset points to the end point of that item's serialised data. The
+ * procedure for finding the start point is described below. An
+ * offset is not needed for the last item because the end point of the
+ * last item is merely the end point of the container itself (after
+ * the offsets array has been accounted for). An offset is not needed
+ * for fixed-sized items (like integers) because, due to their fixed
+ * size, the end point is a constant addition to the start point.
+ *
+ * It is clear that the starting point of a given item in the tuple is
+ * determined by the items that preceed it in the tuple. Logically,
+ * the start point of a particular item in a given type of tuple can
+ * be determined entirely by the end point of the nearest
+ * variable-sized item that came before it. In the case of "(isis)"
+ * for example, in order to find out the start point of the last
+ * string, one must start at the end point of the first string, align
+ * to 4 (for the integer's alignment) and then add 4 (for storing the
+ * integer). That's the point where the string starts (since no
+ * special alignment is required for strings).
+ *
+ * Of course, this process requires iterating over the types in the
+ * tuple up to the item of interest. As it turns out, it is possible
+ * to determine 3 constants 'a', 'b', and 'c' for each item in the
+ * tuple, such that, given the ending offset of the nearest previous
+ * variable-sized item (prev_end), a very simple calculation can be
+ * performed to determine the start of the item of interest.
+ *
+ * The constants in this structure are used as follows:
+ *
+ * First, among the array of offets contained in the tuple, 'i' is the
+ * index of the offset that refers to the end of the variable-sized item
+ * preceeding the item of interest. If no variable-sized items preceed
+ * this item, then 'i' will be -1.
+ *
+ * Let 'prev_end' be the end offset of the previous item (or 0 in the
+ * case that there was no such item). The start address of this item
+ * can then be calculate using 'a', 'b', and 'c':
+ *
+ * item_start = ((prev_end + a) & b) | c;
+ *
+ * For details about how 'a', 'b' and 'c' are calculated, see the
+ * comments at the point of the implementation in gvariantypeinfo.c.
+ */
typedef struct
{
@@ -32,16 +99,9 @@ typedef struct
gint8 b, c;
} GVariantMemberInfo;
-#define STRUCT_MEMBER_LAST (-1)
-#define STRUCT_MEMBER_VARIABLE (-2)
-
/* query */
G_GNUC_INTERNAL
-const GVariantType * g_variant_type_info_get_type (GVariantTypeInfo *typeinfo);
-G_GNUC_INTERNAL
-GVariantClass g_variant_type_info_get_class (GVariantTypeInfo *typeinfo);
-G_GNUC_INTERNAL
-const gchar * g_variant_type_info_get_string (GVariantTypeInfo *typeinfo);
+const gchar * g_variant_type_info_get_type_string (GVariantTypeInfo *typeinfo);
G_GNUC_INTERNAL
void g_variant_type_info_query (GVariantTypeInfo *typeinfo,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]