[json-glib] core: Add immutability support to core objects
- From: Emmanuele Bassi <ebassi src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [json-glib] core: Add immutability support to core objects
- Date: Tue, 1 Mar 2016 16:06:45 +0000 (UTC)
commit 58f479b60eb2db4c73605d469d68a8ffd8679327
Author: Philip Withnall <philip withnall collabora co uk>
Date: Sat Nov 7 14:17:31 2015 +0100
core: Add immutability support to core objects
Add an immutable mode to JsonNode, JsonObject, JsonArray and JsonValue.
This is an optional mode which objects enter by calling json_*_seal().
It is a one-way transition, which means that we can build and manipulate
objects as much as desired, before sealing them and enjoying the
benefits of immutable objects: no need to take copies when handling
them, persistent hash values (still to be implemented).
https://bugzilla.gnome.org/show_bug.cgi?id=756121
doc/reference/json-glib-sections.txt | 8 +
json-glib/json-array.c | 48 +++++
json-glib/json-builder.c | 106 +++++++++++
json-glib/json-builder.h | 2 +
json-glib/json-node.c | 127 +++++++++++++-
json-glib/json-object.c | 51 ++++++
json-glib/json-parser.c | 108 ++++++++++++
json-glib/json-parser.h | 2 +
json-glib/json-types-private.h | 7 +
json-glib/json-types.h | 13 ++
json-glib/json-value.c | 23 +++
json-glib/tests/node.c | 320 ++++++++++++++++++++++++++++++++++
12 files changed, 810 insertions(+), 5 deletions(-)
---
diff --git a/doc/reference/json-glib-sections.txt b/doc/reference/json-glib-sections.txt
index 015a442..2dccde9 100644
--- a/doc/reference/json-glib-sections.txt
+++ b/doc/reference/json-glib-sections.txt
@@ -5,6 +5,8 @@ JsonObject
json_object_new
json_object_ref
json_object_unref
+json_object_seal
+json_object_is_immutable
<SUBSECTION>
json_object_add_member
@@ -51,6 +53,8 @@ json_array_new
json_array_sized_new
json_array_ref
json_array_unref
+json_array_seal
+json_array_is_immutable
<SUBSECTION>
json_array_add_element
@@ -106,6 +110,8 @@ json_node_init_array
json_node_new
json_node_copy
json_node_free
+json_node_is_immutable
+json_node_seal
<SUBSECTION>
json_node_set_array
@@ -156,6 +162,7 @@ JsonParserError
JsonParser
JsonParserClass
json_parser_new
+json_parser_new_immutable
json_parser_load_from_file
json_parser_load_from_data
json_parser_load_from_stream
@@ -325,6 +332,7 @@ _JSON_EXTERN
JsonBuilder
JsonBuilderClass
json_builder_new
+json_builder_new_immutable
json_builder_get_root
json_builder_reset
<SUBSECTION>
diff --git a/json-glib/json-array.c b/json-glib/json-array.c
index af67b4a..05cde4a 100644
--- a/json-glib/json-array.c
+++ b/json-glib/json-array.c
@@ -134,6 +134,54 @@ json_array_unref (JsonArray *array)
}
/**
+ * json_array_seal:
+ * @array: a #JsonArray
+ *
+ * Seals the #JsonArray, making it immutable to further changes. This will
+ * recursively seal all elements in the array too.
+ *
+ * If the @array is already immutable, this is a no-op.
+ *
+ * Since: UNRELEASED
+ */
+void
+json_array_seal (JsonArray *array)
+{
+ guint i;
+
+ g_return_if_fail (array != NULL);
+ g_return_if_fail (array->ref_count > 0);
+
+ if (array->immutable)
+ return;
+
+ /* Propagate to all members. */
+ for (i = 0; i < array->elements->len; i++)
+ json_node_seal (g_ptr_array_index (array->elements, i));
+
+ array->immutable = TRUE;
+}
+
+/**
+ * json_array_is_immutable:
+ * @array: a #JsonArray
+ *
+ * Check whether the given @array has been marked as immutable by calling
+ * json_array_seal() on it.
+ *
+ * Since: UNRELEASED
+ * Returns: %TRUE if the @array is immutable
+ */
+gboolean
+json_array_is_immutable (JsonArray *array)
+{
+ g_return_val_if_fail (array != NULL, FALSE);
+ g_return_val_if_fail (array->ref_count > 0, FALSE);
+
+ return array->immutable;
+}
+
+/**
* json_array_get_elements:
* @array: a #JsonArray
*
diff --git a/json-glib/json-builder.c b/json-glib/json-builder.c
index d3a5cef..6531b0c 100644
--- a/json-glib/json-builder.c
+++ b/json-glib/json-builder.c
@@ -2,6 +2,7 @@
*
* This file is part of JSON-GLib
* Copyright (C) 2010 Luca Bruno <lethalman88 gmail com>
+ * Copyright (C) 2015 Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -18,6 +19,7 @@
*
* Author:
* Luca Bruno <lethalman88 gmail com>
+ * Philip Withnall <philip withnall collabora co uk>
*/
/**
@@ -50,8 +52,17 @@ struct _JsonBuilderPrivate
{
GQueue *stack;
JsonNode *root;
+ gboolean immutable;
};
+enum
+{
+ PROP_IMMUTABLE = 1,
+ PROP_LAST
+};
+
+static GParamSpec *builder_props[PROP_LAST] = { NULL, };
+
typedef enum
{
JSON_BUILDER_MODE_OBJECT,
@@ -133,11 +144,70 @@ json_builder_finalize (GObject *gobject)
}
static void
+json_builder_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ JsonBuilderPrivate *priv = JSON_BUILDER (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_IMMUTABLE:
+ /* Construct-only. */
+ priv->immutable = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+json_builder_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ JsonBuilderPrivate *priv = JSON_BUILDER (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_IMMUTABLE:
+ g_value_set_boolean (value, priv->immutable);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
json_builder_class_init (JsonBuilderClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ /**
+ * JsonBuilder:immutable:
+ *
+ * Whether the #JsonNode tree built by the #JsonBuilder should be immutable
+ * when created. Making the output immutable on creation avoids the expense
+ * of traversing it to make it immutable later.
+ *
+ * Since: UNRELEASED
+ */
+ builder_props[PROP_IMMUTABLE] =
+ g_param_spec_boolean ("immutable",
+ "Immutable Output",
+ "Whether the builder output is immutable.",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
+
+ gobject_class->set_property = json_builder_set_property;
+ gobject_class->get_property = json_builder_get_property;
gobject_class->finalize = json_builder_finalize;
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, builder_props);
}
static void
@@ -180,6 +250,21 @@ json_builder_new (void)
}
/**
+ * json_builder_new_immutable:
+ *
+ * Creates a new #JsonBuilder instance with its #JsonBuilder:immutable property
+ * set to %TRUE to create immutable output trees.
+ *
+ * Since: UNRELEASED
+ * Returns: (transfer full): a new #JsonBuilder
+ */
+JsonBuilder *
+json_builder_new_immutable (void)
+{
+ return g_object_new (JSON_TYPE_BUILDER, "immutable", TRUE, NULL);
+}
+
+/**
* json_builder_get_root:
* @builder: a #JsonBuilder
*
@@ -199,6 +284,11 @@ json_builder_get_root (JsonBuilder *builder)
if (builder->priv->root)
root = json_node_copy (builder->priv->root);
+ /* Sanity check. */
+ g_return_val_if_fail (!builder->priv->immutable ||
+ root == NULL ||
+ json_node_is_immutable (root), NULL);
+
return root;
}
@@ -292,10 +382,16 @@ json_builder_end_object (JsonBuilder *builder)
state = g_queue_pop_head (builder->priv->stack);
+ if (builder->priv->immutable)
+ json_object_seal (state->data.object);
+
if (g_queue_is_empty (builder->priv->stack))
{
builder->priv->root = json_node_new (JSON_NODE_OBJECT);
json_node_take_object (builder->priv->root, json_object_ref (state->data.object));
+
+ if (builder->priv->immutable)
+ json_node_seal (builder->priv->root);
}
json_builder_state_free (state);
@@ -378,10 +474,16 @@ json_builder_end_array (JsonBuilder *builder)
state = g_queue_pop_head (builder->priv->stack);
+ if (builder->priv->immutable)
+ json_array_seal (state->data.array);
+
if (g_queue_is_empty (builder->priv->stack))
{
builder->priv->root = json_node_new (JSON_NODE_ARRAY);
json_node_take_array (builder->priv->root, json_array_ref (state->data.array));
+
+ if (builder->priv->immutable)
+ json_node_seal (builder->priv->root);
}
json_builder_state_free (state);
@@ -444,6 +546,10 @@ json_builder_add_value (JsonBuilder *builder,
g_return_val_if_fail (json_builder_is_valid_add_mode (builder), NULL);
state = g_queue_peek_head (builder->priv->stack);
+
+ if (builder->priv->immutable)
+ json_node_seal (node);
+
switch (state->mode)
{
case JSON_BUILDER_MODE_MEMBER:
diff --git a/json-glib/json-builder.h b/json-glib/json-builder.h
index 45d4b5d..b35230e 100644
--- a/json-glib/json-builder.h
+++ b/json-glib/json-builder.h
@@ -80,6 +80,8 @@ GType json_builder_get_type (void) G_GNUC_CONST;
JSON_AVAILABLE_IN_1_0
JsonBuilder *json_builder_new (void);
+JSON_AVAILABLE_IN_1_2
+JsonBuilder *json_builder_new_immutable (void);
JSON_AVAILABLE_IN_1_0
JsonNode *json_builder_get_root (JsonBuilder *builder);
JSON_AVAILABLE_IN_1_0
diff --git a/json-glib/json-node.c b/json-glib/json-node.c
index fb95f42..092a27f 100644
--- a/json-glib/json-node.c
+++ b/json-glib/json-node.c
@@ -3,6 +3,7 @@
* This file is part of JSON-GLib
* Copyright (C) 2007 OpenedHand Ltd.
* Copyright (C) 2009 Intel Corp.
+ * Copyright (C) 2015 Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -19,6 +20,7 @@
*
* Author:
* Emmanuele Bassi <ebassi linux intel com>
+ * Philip Withnall <philip withnall collabora co uk>
*/
#include "config.h"
@@ -44,6 +46,15 @@
* #JsonObject or the #JsonArray using json_node_get_object() or
* json_node_get_array() respectively, and then retrieve the nodes
* they contain.
+ *
+ * A #JsonNode may be marked as immutable using json_node_seal(). This marks the
+ * node and all its descendents as read-only, and means that subsequent calls to
+ * setter functions (such as json_node_set_array()) on them will abort as a
+ * programmer error. By marking a node tree as immutable, it may be referenced
+ * in multiple places and its hash value cached for fast lookups, without the
+ * possibility of a value deep within the tree changing and affecting hash
+ * values. Immutable #JsonNodes may be passed to functions which retain a
+ * reference to them without needing to take a copy.
*/
G_DEFINE_BOXED_TYPE (JsonNode, json_node, json_node_copy, json_node_free);
@@ -367,8 +378,12 @@ json_node_new (JsonNodeType type)
* json_node_copy:
* @node: a #JsonNode
*
- * Copies @node. If the node contains complex data types then the reference
- * count of the objects is increased.
+ * Copies @node. If the node contains complex data types, their reference
+ * counts are increased, regardless of whether the node is mutable or
+ * immutable.
+ *
+ * The copy will be immutable if, and only if, @node is immutable. However,
+ * there should be no need to copy an immutable node.
*
* Return value: (transfer full): the copied #JsonNode
*/
@@ -381,6 +396,10 @@ json_node_copy (JsonNode *node)
copy = g_slice_new0 (JsonNode);
copy->type = node->type;
+ copy->immutable = node->immutable;
+
+ if (node->immutable)
+ g_debug ("Copying immutable JsonNode %p", node);
switch (copy->type)
{
@@ -415,6 +434,8 @@ json_node_copy (JsonNode *node)
* Sets @objects inside @node. The reference count of @object is increased.
*
* If @object is %NULL, the node’s existing object is cleared.
+ *
+ * It is an error to call this on an immutable node.
*/
void
json_node_set_object (JsonNode *node,
@@ -422,6 +443,7 @@ json_node_set_object (JsonNode *node,
{
g_return_if_fail (node != NULL);
g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_OBJECT);
+ g_return_if_fail (!node->immutable);
if (node->data.object != NULL)
json_object_unref (node->data.object);
@@ -438,6 +460,8 @@ json_node_set_object (JsonNode *node,
* @object: (transfer full): a #JsonObject
*
* Sets @object inside @node. The reference count of @object is not increased.
+ *
+ * It is an error to call this on an immutable node.
*/
void
json_node_take_object (JsonNode *node,
@@ -445,6 +469,7 @@ json_node_take_object (JsonNode *node,
{
g_return_if_fail (node != NULL);
g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_OBJECT);
+ g_return_if_fail (!node->immutable);
if (node->data.object)
{
@@ -499,7 +524,9 @@ json_node_dup_object (JsonNode *node)
* @node: a #JsonNode initialized to %JSON_NODE_ARRAY
* @array: a #JsonArray
*
- * Sets @array inside @node and increases the #JsonArray reference count
+ * Sets @array inside @node and increases the #JsonArray reference count.
+ *
+ * It is an error to call this on an immutable node.
*/
void
json_node_set_array (JsonNode *node,
@@ -507,6 +534,7 @@ json_node_set_array (JsonNode *node,
{
g_return_if_fail (node != NULL);
g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_ARRAY);
+ g_return_if_fail (!node->immutable);
if (node->data.array)
json_array_unref (node->data.array);
@@ -523,6 +551,8 @@ json_node_set_array (JsonNode *node,
* @array: (transfer full): a #JsonArray
*
* Sets @array into @node without increasing the #JsonArray reference count.
+ *
+ * It is an error to call this on an immutable node.
*/
void
json_node_take_array (JsonNode *node,
@@ -530,6 +560,7 @@ json_node_take_array (JsonNode *node,
{
g_return_if_fail (node != NULL);
g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_ARRAY);
+ g_return_if_fail (!node->immutable);
if (node->data.array)
{
@@ -627,7 +658,9 @@ json_node_get_value (JsonNode *node,
* @node: a #JsonNode initialized to %JSON_NODE_VALUE
* @value: the #GValue to set
*
- * Sets @value inside @node. The passed #GValue is copied into the #JsonNode
+ * Sets @value inside @node. The passed #GValue is copied into the #JsonNode.
+ *
+ * It is an error to call this on an immutable node.
*/
void
json_node_set_value (JsonNode *node,
@@ -636,6 +669,7 @@ json_node_set_value (JsonNode *node,
g_return_if_fail (node != NULL);
g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE);
g_return_if_fail (G_VALUE_TYPE (value) != G_TYPE_INVALID);
+ g_return_if_fail (!node->immutable);
if (node->data.value == NULL)
node->data.value = json_value_alloc ();
@@ -697,6 +731,72 @@ json_node_free (JsonNode *node)
}
/**
+ * json_node_seal:
+ * @node: a #JsonNode
+ *
+ * Seals the #JsonNode, making it immutable to further changes. In order to be
+ * sealed, the @node must have a type and value set. The value will be
+ * recursively sealed — if the node holds an object, that #JsonObject will be
+ * sealed, etc.
+ *
+ * If the @node is already immutable, this is a no-op.
+ *
+ * Since: UNRELEASED
+ */
+void
+json_node_seal (JsonNode *node)
+{
+ g_return_if_fail (node != NULL);
+ g_return_if_fail (node->type >= JSON_NODE_OBJECT &&
+ node->type <= JSON_NODE_NULL);
+
+ if (node->immutable)
+ return;
+
+ switch (node->type)
+ {
+ case JSON_NODE_OBJECT:
+ g_return_if_fail (node->data.object != NULL);
+ json_object_seal (node->data.object);
+ break;
+ case JSON_NODE_ARRAY:
+ g_return_if_fail (node->data.array != NULL);
+ json_array_seal (node->data.array);
+ break;
+ case JSON_NODE_NULL:
+ break;
+ case JSON_NODE_VALUE:
+ g_return_if_fail (node->data.value != NULL);
+ json_value_seal (node->data.value);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ node->immutable = TRUE;
+}
+
+/**
+ * json_node_is_immutable:
+ * @node: a #JsonNode
+ *
+ * Check whether the given @node has been marked as immutable by calling
+ * json_node_seal() on it.
+ *
+ * Since: UNRELEASED
+ * Returns: %TRUE if the @node is immutable
+ */
+gboolean
+json_node_is_immutable (JsonNode *node)
+{
+ g_return_val_if_fail (node != NULL, FALSE);
+ g_return_val_if_fail (node->type >= JSON_NODE_OBJECT &&
+ node->type <= JSON_NODE_NULL, FALSE);
+
+ return node->immutable;
+}
+
+/**
* json_node_type_name:
* @node: a #JsonNode
*
@@ -755,7 +855,10 @@ json_node_type_get_name (JsonNodeType node_type)
* @node: a #JsonNode
* @parent: (transfer none): the parent #JsonNode of @node
*
- * Sets the parent #JsonNode of @node
+ * Sets the parent #JsonNode of @node.
+ *
+ * It is an error to call this with an immutable @parent. @node may be
+ * immutable.
*
* Since: 0.8
*/
@@ -764,6 +867,8 @@ json_node_set_parent (JsonNode *node,
JsonNode *parent)
{
g_return_if_fail (node != NULL);
+ g_return_if_fail (parent == NULL ||
+ !json_node_is_immutable (parent));
node->parent = parent;
}
@@ -792,6 +897,8 @@ json_node_get_parent (JsonNode *node)
*
* Sets @value as the string content of the @node, replacing any existing
* content.
+ *
+ * It is an error to call this on an immutable node.
*/
void
json_node_set_string (JsonNode *node,
@@ -799,6 +906,7 @@ json_node_set_string (JsonNode *node,
{
g_return_if_fail (node != NULL);
g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE);
+ g_return_if_fail (!node->immutable);
if (node->data.value == NULL)
node->data.value = json_value_init (json_value_alloc (), JSON_VALUE_STRING);
@@ -854,6 +962,8 @@ json_node_dup_string (JsonNode *node)
*
* Sets @value as the integer content of the @node, replacing any existing
* content.
+ *
+ * It is an error to call this on an immutable node.
*/
void
json_node_set_int (JsonNode *node,
@@ -861,6 +971,7 @@ json_node_set_int (JsonNode *node,
{
g_return_if_fail (node != NULL);
g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE);
+ g_return_if_fail (!node->immutable);
if (node->data.value == NULL)
node->data.value = json_value_init (json_value_alloc (), JSON_VALUE_INT);
@@ -905,6 +1016,8 @@ json_node_get_int (JsonNode *node)
*
* Sets @value as the double content of the @node, replacing any existing
* content.
+ *
+ * It is an error to call this on an immutable node.
*/
void
json_node_set_double (JsonNode *node,
@@ -912,6 +1025,7 @@ json_node_set_double (JsonNode *node,
{
g_return_if_fail (node != NULL);
g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE);
+ g_return_if_fail (!node->immutable);
if (node->data.value == NULL)
node->data.value = json_value_init (json_value_alloc (), JSON_VALUE_DOUBLE);
@@ -956,6 +1070,8 @@ json_node_get_double (JsonNode *node)
*
* Sets @value as the boolean content of the @node, replacing any existing
* content.
+ *
+ * It is an error to call this on an immutable node.
*/
void
json_node_set_boolean (JsonNode *node,
@@ -963,6 +1079,7 @@ json_node_set_boolean (JsonNode *node,
{
g_return_if_fail (node != NULL);
g_return_if_fail (JSON_NODE_TYPE (node) == JSON_NODE_VALUE);
+ g_return_if_fail (!node->immutable);
if (node->data.value == NULL)
node->data.value = json_value_init (json_value_alloc (), JSON_VALUE_BOOLEAN);
diff --git a/json-glib/json-object.c b/json-glib/json-object.c
index 214ffef..574d04d 100644
--- a/json-glib/json-object.c
+++ b/json-glib/json-object.c
@@ -117,6 +117,57 @@ json_object_unref (JsonObject *object)
}
}
+/**
+ * json_object_seal:
+ * @object: a #JsonObject
+ *
+ * Seals the #JsonObject, making it immutable to further changes. This will
+ * recursively seal all members of the object too.
+ *
+ * If the @object is already immutable, this is a no-op.
+ *
+ * Since: UNRELEASED
+ */
+void
+json_object_seal (JsonObject *object)
+{
+ JsonObjectIter iter;
+ JsonNode *node;
+
+ g_return_if_fail (object != NULL);
+ g_return_if_fail (object->ref_count > 0);
+
+ if (object->immutable)
+ return;
+
+ /* Propagate to all members. */
+ json_object_iter_init (&iter, object);
+
+ while (json_object_iter_next (&iter, NULL, &node))
+ json_node_seal (node);
+
+ object->immutable = TRUE;
+}
+
+/**
+ * json_object_is_immutable:
+ * @object: a #JsonObject
+ *
+ * Check whether the given @object has been marked as immutable by calling
+ * json_object_seal() on it.
+ *
+ * Since: UNRELEASED
+ * Returns: %TRUE if the @object is immutable
+ */
+gboolean
+json_object_is_immutable (JsonObject *object)
+{
+ g_return_val_if_fail (object != NULL, FALSE);
+ g_return_val_if_fail (object->ref_count > 0, FALSE);
+
+ return object->immutable;
+}
+
static inline void
object_set_member_internal (JsonObject *object,
const gchar *member_name,
diff --git a/json-glib/json-parser.c b/json-glib/json-parser.c
index 812f038..8f1c40c 100644
--- a/json-glib/json-parser.c
+++ b/json-glib/json-parser.c
@@ -4,6 +4,7 @@
*
* Copyright © 2007, 2008, 2009 OpenedHand Ltd
* Copyright © 2009, 2010 Intel Corp.
+ * Copyright © 2015 Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -20,6 +21,7 @@
*
* Author:
* Emmanuele Bassi <ebassi linux intel com>
+ * Philip Withnall <philip withnall collabora co uk>
*/
/**
@@ -58,6 +60,7 @@ struct _JsonParserPrivate
guint has_assignment : 1;
guint is_filename : 1;
+ gboolean immutable : 1;
};
static const gchar symbol_names[] =
@@ -96,6 +99,14 @@ enum
static guint parser_signals[LAST_SIGNAL] = { 0, };
+enum
+{
+ PROP_IMMUTABLE = 1,
+ PROP_LAST
+};
+
+static GParamSpec *parser_props[PROP_LAST] = { NULL, };
+
G_DEFINE_QUARK (json-parser-error-quark, json_parser_error)
G_DEFINE_TYPE_WITH_PRIVATE (JsonParser, json_parser, G_TYPE_OBJECT)
@@ -148,14 +159,73 @@ json_parser_finalize (GObject *gobject)
}
static void
+json_parser_set_property (GObject *gobject,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ JsonParserPrivate *priv = JSON_PARSER (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_IMMUTABLE:
+ /* Construct-only. */
+ priv->immutable = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+json_parser_get_property (GObject *gobject,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ JsonParserPrivate *priv = JSON_PARSER (gobject)->priv;
+
+ switch (prop_id)
+ {
+ case PROP_IMMUTABLE:
+ g_value_set_boolean (value, priv->immutable);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
+ break;
+ }
+}
+
+static void
json_parser_class_init (JsonParserClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->set_property = json_parser_set_property;
+ gobject_class->get_property = json_parser_get_property;
gobject_class->dispose = json_parser_dispose;
gobject_class->finalize = json_parser_finalize;
/**
+ * JsonParser:immutable:
+ *
+ * Whether the #JsonNode tree built by the #JsonParser should be immutable
+ * when created. Making the output immutable on creation avoids the expense
+ * of traversing it to make it immutable later.
+ *
+ * Since: UNRELEASED
+ */
+ parser_props[PROP_IMMUTABLE] =
+ g_param_spec_boolean ("immutable",
+ "Immutable Output",
+ "Whether the parser output is immutable.",
+ FALSE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
+
+ g_object_class_install_properties (gobject_class, PROP_LAST, parser_props);
+
+ /**
* JsonParser::parse-start:
* @parser: the #JsonParser that received the signal
*
@@ -424,6 +494,9 @@ json_parse_value (JsonParser *parser,
break;
}
+ if (priv->immutable && *node != NULL)
+ json_node_seal (*node);
+
return G_TOKEN_NONE;
}
@@ -522,6 +595,8 @@ json_parse_array (JsonParser *parser,
JSON_NOTE (PARSER, "Array element %d completed", idx);
json_node_set_parent (element, priv->current_node);
+ if (priv->immutable)
+ json_node_seal (element);
json_array_add_element (array, element);
g_signal_emit (parser, parser_signals[ARRAY_ELEMENT], 0,
@@ -535,7 +610,13 @@ json_parse_array (JsonParser *parser,
array_done:
json_scanner_get_next_token (scanner);
+ /* We can guarantee that all the array elements are immutable, so we
+ * can skip the formal loop over them to seal them again. */
+ array->immutable = TRUE;
+
json_node_take_array (priv->current_node, array);
+ if (priv->immutable)
+ json_node_seal (priv->current_node);
json_node_set_parent (priv->current_node, old_current);
g_signal_emit (parser, parser_signals[ARRAY_END], 0, array);
@@ -697,6 +778,8 @@ json_parse_object (JsonParser *parser,
JSON_NOTE (PARSER, "Object member '%s' completed", name);
json_node_set_parent (member, priv->current_node);
+ if (priv->immutable)
+ json_node_seal (member);
json_object_set_member (object, name, member);
g_signal_emit (parser, parser_signals[OBJECT_MEMBER], 0,
@@ -710,7 +793,13 @@ json_parse_object (JsonParser *parser,
json_scanner_get_next_token (scanner);
+ /* We can guarantee that all the object members are immutable, so we
+ * can skip the formal loop over them to seal them again. */
+ object->immutable = TRUE;
+
json_node_take_object (priv->current_node, object);
+ if (priv->immutable)
+ json_node_seal (priv->current_node);
json_node_set_parent (priv->current_node, old_current);
g_signal_emit (parser, parser_signals[OBJECT_END], 0, object);
@@ -876,6 +965,21 @@ json_parser_new (void)
return g_object_new (JSON_TYPE_PARSER, NULL);
}
+/**
+ * json_parser_new_immutable:
+ *
+ * Creates a new #JsonParser instance with its #JsonParser:immutable property
+ * set to %TRUE to create immutable output trees.
+ *
+ * Since: UNRELEASED
+ * Returns: (transfer full): a new #JsonParser
+ */
+JsonParser *
+json_parser_new_immutable (void)
+{
+ return g_object_new (JSON_TYPE_PARSER, "immutable", TRUE, NULL);
+}
+
static gboolean
json_parser_load (JsonParser *parser,
const gchar *data,
@@ -1097,6 +1201,10 @@ json_parser_get_root (JsonParser *parser)
{
g_return_val_if_fail (JSON_IS_PARSER (parser), NULL);
+ /* Sanity check. */
+ g_return_val_if_fail (!parser->priv->immutable ||
+ json_node_is_immutable (parser->priv->root), NULL);
+
return parser->priv->root;
}
diff --git a/json-glib/json-parser.h b/json-glib/json-parser.h
index a65558e..c7223d2 100644
--- a/json-glib/json-parser.h
+++ b/json-glib/json-parser.h
@@ -147,6 +147,8 @@ GType json_parser_get_type (void) G_GNUC_CONST;
JSON_AVAILABLE_IN_1_0
JsonParser *json_parser_new (void);
+JSON_AVAILABLE_IN_1_2
+JsonParser *json_parser_new_immutable (void);
JSON_AVAILABLE_IN_1_0
gboolean json_parser_load_from_file (JsonParser *parser,
const gchar *filename,
diff --git a/json-glib/json-types-private.h b/json-glib/json-types-private.h
index f9ac064..34a3160 100644
--- a/json-glib/json-types-private.h
+++ b/json-glib/json-types-private.h
@@ -43,6 +43,7 @@ struct _JsonNode
{
/*< private >*/
JsonNodeType type;
+ gboolean immutable : 1;
union {
JsonObject *object;
@@ -69,6 +70,7 @@ struct _JsonValue
JsonValueType type;
volatile gint ref_count;
+ gboolean immutable : 1;
union {
gint64 v_int;
@@ -83,6 +85,7 @@ struct _JsonArray
GPtrArray *elements;
volatile gint ref_count;
+ gboolean immutable : 1;
};
struct _JsonObject
@@ -93,6 +96,7 @@ struct _JsonObject
GList *members_ordered;
volatile gint ref_count;
+ gboolean immutable : 1;
};
typedef struct
@@ -146,6 +150,9 @@ void json_value_set_string (JsonValue *value,
G_GNUC_INTERNAL
const gchar * json_value_get_string (const JsonValue *value);
+G_GNUC_INTERNAL
+void json_value_seal (JsonValue *value);
+
G_END_DECLS
#endif /* __JSON_TYPES_PRIVATE_H__ */
diff --git a/json-glib/json-types.h b/json-glib/json-types.h
index 1ee84ff..5f2a084 100644
--- a/json-glib/json-types.h
+++ b/json-glib/json-types.h
@@ -276,6 +276,10 @@ gboolean json_node_get_boolean (JsonNode *node);
JSON_AVAILABLE_IN_1_0
gboolean json_node_is_null (JsonNode *node);
+JSON_AVAILABLE_IN_1_2
+void json_node_seal (JsonNode *node);
+gboolean json_node_is_immutable (JsonNode *node);
+
/*
* JsonObject
*/
@@ -368,6 +372,11 @@ void json_object_foreach_member (JsonObject *object,
JsonObjectForeach func,
gpointer data);
+JSON_AVAILABLE_IN_1_2
+void json_object_seal (JsonObject *object);
+JSON_AVAILABLE_IN_1_2
+gboolean json_object_is_immutable (JsonObject *object);
+
/**
* JsonObjectIter:
*
@@ -467,6 +476,10 @@ JSON_AVAILABLE_IN_1_0
void json_array_foreach_element (JsonArray *array,
JsonArrayForeach func,
gpointer data);
+JSON_AVAILABLE_IN_1_2
+void json_array_seal (JsonArray *array);
+JSON_AVAILABLE_IN_1_2
+gboolean json_array_is_immutable (JsonArray *array);
#ifdef G_DEFINE_AUTOPTR_CLEANUP_FUNC
G_DEFINE_AUTOPTR_CLEANUP_FUNC (JsonArray, json_array_unref)
diff --git a/json-glib/json-value.c b/json-glib/json-value.c
index b6d47e1..148cf8d 100644
--- a/json-glib/json-value.c
+++ b/json-glib/json-value.c
@@ -2,6 +2,7 @@
*
* This file is part of JSON-GLib
* Copyright (C) 2012 Emmanuele Bassi <ebassi gnome org>
+ * Copyright (C) 2015 Collabora Ltd.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -18,6 +19,7 @@
*
* Author:
* Emmanuele Bassi <ebassi linux intel com>
+ * Philip Withnall <philip withnall collabora co uk>
*/
#include "config.h"
@@ -165,12 +167,32 @@ json_value_free (JsonValue *value)
}
}
+/**
+ * json_value_seal:
+ * @value: a #JsonValue
+ *
+ * Seals the #JsonValue, making it immutable to further changes.
+ *
+ * If the @value is already immutable, this is a no-op.
+ *
+ * Since: UNRELEASED
+ */
+void
+json_value_seal (JsonValue *value)
+{
+ g_return_if_fail (JSON_VALUE_IS_VALID (value));
+ g_return_if_fail (value->ref_count > 0);
+
+ value->immutable = TRUE;
+}
+
#define _JSON_VALUE_DEFINE_SET(Type,EType,CType,VField) \
void \
json_value_set_##Type (JsonValue *value, CType VField) \
{ \
g_return_if_fail (JSON_VALUE_IS_VALID (value)); \
g_return_if_fail (JSON_VALUE_HOLDS (value, JSON_VALUE_##EType)); \
+ g_return_if_fail (!value->immutable); \
\
value->data.VField = VField; \
\
@@ -202,6 +224,7 @@ json_value_set_string (JsonValue *value,
{
g_return_if_fail (JSON_VALUE_IS_VALID (value));
g_return_if_fail (JSON_VALUE_HOLDS_STRING (value));
+ g_return_if_fail (!value->immutable);
g_free (value->data.v_str);
value->data.v_str = g_strdup (v_str);
diff --git a/json-glib/tests/node.c b/json-glib/tests/node.c
index 6aad5bc..23bda63 100644
--- a/json-glib/tests/node.c
+++ b/json-glib/tests/node.c
@@ -242,6 +242,311 @@ test_gvalue_autopromotion (void)
json_node_free (node);
}
+/* Test that creating then sealing a node containing an int causes it to be
+ * immutable. */
+static void
+test_seal_int (void)
+{
+ JsonNode *node = NULL;
+
+ node = json_node_init_int (json_node_alloc (), 1);
+
+ g_assert_false (json_node_is_immutable (node));
+ json_node_seal (node);
+ g_assert_true (json_node_is_immutable (node));
+ json_node_free (node);
+}
+
+/* Test that creating then sealing a node containing a double causes it to be
+ * immutable. */
+static void
+test_seal_double (void)
+{
+ JsonNode *node = NULL;
+
+ node = json_node_init_double (json_node_alloc (), 15.2);
+ g_assert_false (json_node_is_immutable (node));
+ json_node_seal (node);
+ g_assert_true (json_node_is_immutable (node));
+ json_node_free (node);
+}
+
+/* Test that creating then sealing a node containing a boolean causes it to be
+ * immutable. */
+static void
+test_seal_boolean (void)
+{
+ JsonNode *node = NULL;
+
+ node = json_node_init_boolean (json_node_alloc (), TRUE);
+ g_assert_false (json_node_is_immutable (node));
+ json_node_seal (node);
+ g_assert_true (json_node_is_immutable (node));
+ json_node_free (node);
+}
+
+/* Test that creating then sealing a node containing a string causes it to be
+ * immutable. */
+static void
+test_seal_string (void)
+{
+ JsonNode *node = NULL;
+
+ node = json_node_init_string (json_node_alloc (), "hi there");
+ g_assert_false (json_node_is_immutable (node));
+ json_node_seal (node);
+ g_assert_true (json_node_is_immutable (node));
+ json_node_free (node);
+}
+
+/* Test that creating then sealing a node containing a null causes it to be
+ * immutable. */
+static void
+test_seal_null (void)
+{
+ JsonNode *node = NULL;
+
+ node = json_node_init_null (json_node_alloc ());
+ g_assert_false (json_node_is_immutable (node));
+ json_node_seal (node);
+ g_assert_true (json_node_is_immutable (node));
+ json_node_free (node);
+}
+
+/* Test that creating then sealing a node containing an object causes it to be
+ * immutable. */
+static void
+test_seal_object (void)
+{
+ JsonNode *node = NULL;
+ JsonObject *object = NULL;
+
+ object = json_object_new ();
+ node = json_node_init_object (json_node_alloc (), object);
+
+ g_assert_false (json_object_is_immutable (object));
+ g_assert_false (json_node_is_immutable (node));
+ json_node_seal (node);
+ g_assert_true (json_node_is_immutable (node));
+ g_assert_true (json_object_is_immutable (object));
+
+ json_node_free (node);
+ json_object_unref (object);
+}
+
+/* Test that creating then sealing a node containing an array causes it to be
+ * immutable. */
+static void
+test_seal_array (void)
+{
+ JsonNode *node = NULL;
+ JsonArray *array = NULL;
+
+ array = json_array_new ();
+ node = json_node_init_array (json_node_alloc (), array);
+
+ g_assert_false (json_array_is_immutable (array));
+ g_assert_false (json_node_is_immutable (node));
+ json_node_seal (node);
+ g_assert_true (json_node_is_immutable (node));
+ g_assert_true (json_array_is_immutable (array));
+
+ json_node_free (node);
+ json_array_unref (array);
+}
+
+/* Test that an immutable node containing an int cannot be modified. */
+static void
+test_immutable_int (void)
+{
+ if (g_test_subprocess ())
+ {
+ JsonNode *node = NULL;
+
+ node = json_node_init_int (json_node_alloc (), 5);
+ json_node_seal (node);
+
+ /* Boom. */
+ json_node_set_int (node, 1);
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_int: "
+ "assertion '!node->immutable' failed*");
+}
+
+/* Test that an immutable node containing a double cannot be modified. */
+static void
+test_immutable_double (void)
+{
+ if (g_test_subprocess ())
+ {
+ JsonNode *node = NULL;
+
+ node = json_node_init_double (json_node_alloc (), 5.6);
+ json_node_seal (node);
+
+ /* Boom. */
+ json_node_set_double (node, 1.1);
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_double: "
+ "assertion '!node->immutable' failed*");
+}
+
+/* Test that an immutable node containing a boolean cannot be modified. */
+static void
+test_immutable_boolean (void)
+{
+ if (g_test_subprocess ())
+ {
+ JsonNode *node = NULL;
+
+ node = json_node_init_boolean (json_node_alloc (), TRUE);
+ json_node_seal (node);
+
+ /* Boom. */
+ json_node_set_boolean (node, FALSE);
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_boolean: "
+ "assertion '!node->immutable' failed*");
+}
+
+/* Test that an immutable node containing a string cannot be modified. */
+static void
+test_immutable_string (void)
+{
+ if (g_test_subprocess ())
+ {
+ JsonNode *node = NULL;
+
+ node = json_node_init_string (json_node_alloc (), "bonghits");
+ json_node_seal (node);
+
+ /* Boom. */
+ json_node_set_string (node, "asdasd");
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_string: "
+ "assertion '!node->immutable' failed*");
+}
+
+/* Test that an immutable node containing an object cannot be modified. */
+static void
+test_immutable_object (void)
+{
+ if (g_test_subprocess ())
+ {
+ JsonNode *node = NULL;
+
+ node = json_node_init_object (json_node_alloc (), json_object_new ());
+ json_node_seal (node);
+
+ /* Boom. */
+ json_node_set_object (node, json_object_new ());
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_object: "
+ "assertion '!node->immutable' failed*");
+}
+
+/* Test that an immutable node containing an array cannot be modified. */
+static void
+test_immutable_array (void)
+{
+ if (g_test_subprocess ())
+ {
+ JsonNode *node = NULL;
+
+ node = json_node_init_array (json_node_alloc (), json_array_new ());
+ json_node_seal (node);
+
+ /* Boom. */
+ json_node_set_array (node, json_array_new ());
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_array: "
+ "assertion '!node->immutable' failed*");
+}
+
+/* Test that an immutable node containing a value cannot be modified. */
+static void
+test_immutable_value (void)
+{
+ if (g_test_subprocess ())
+ {
+ JsonNode *node = NULL;
+ GValue val = G_VALUE_INIT;
+
+ node = json_node_init_int (json_node_alloc (), 5);
+ json_node_seal (node);
+
+ /* Boom. */
+ g_value_init (&val, G_TYPE_INT);
+ g_value_set_int (&val, 50);
+ json_node_set_value (node, &val);
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_value: "
+ "assertion '!node->immutable' failed*");
+}
+
+/* Test that an immutable node can be reparented but not to an immutable
+ * parent. */
+static void
+test_immutable_parent (void)
+{
+ if (g_test_subprocess ())
+ {
+ JsonNode *node = NULL;
+ JsonNode *parent_mutable = NULL;
+ JsonNode *parent_immutable = NULL;
+ JsonObject *object_mutable = NULL;
+ JsonObject *object_immutable = NULL;
+
+ node = json_node_init_int (json_node_alloc (), 5);
+ json_node_seal (node);
+
+ object_mutable = json_object_new ();
+ object_immutable = json_object_new ();
+
+ parent_mutable = json_node_init_object (json_node_alloc (),
+ object_mutable);
+ parent_immutable = json_node_init_object (json_node_alloc (),
+ object_immutable);
+
+ json_node_seal (parent_immutable);
+
+ /* Can we reparent the immutable node? */
+ json_object_set_member (object_mutable, "test", node);
+ json_node_set_parent (node, parent_mutable);
+
+ json_object_remove_member (object_mutable, "test");
+ json_node_set_parent (node, NULL);
+
+ /* Boom. */
+ json_node_set_parent (node, parent_immutable);
+ }
+
+ g_test_trap_subprocess (NULL, 0, 0);
+ g_test_trap_assert_failed ();
+ g_test_trap_assert_stderr ("*Json-CRITICAL **: json_node_set_parent: *");
+}
+
int
main (int argc,
char *argv[])
@@ -260,6 +565,21 @@ main (int argc,
g_test_add_func ("/nodes/get/double", test_get_double);
g_test_add_func ("/nodes/gvalue", test_gvalue);
g_test_add_func ("/nodes/gvalue/autopromotion", test_gvalue_autopromotion);
+ g_test_add_func ("/nodes/seal/int", test_seal_int);
+ g_test_add_func ("/nodes/seal/double", test_seal_double);
+ g_test_add_func ("/nodes/seal/boolean", test_seal_boolean);
+ g_test_add_func ("/nodes/seal/string", test_seal_string);
+ g_test_add_func ("/nodes/seal/null", test_seal_null);
+ g_test_add_func ("/nodes/seal/object", test_seal_object);
+ g_test_add_func ("/nodes/seal/array", test_seal_array);
+ g_test_add_func ("/nodes/immutable/int", test_immutable_int);
+ g_test_add_func ("/nodes/immutable/double", test_immutable_double);
+ g_test_add_func ("/nodes/immutable/boolean", test_immutable_boolean);
+ g_test_add_func ("/nodes/immutable/string", test_immutable_string);
+ g_test_add_func ("/nodes/immutable/object", test_immutable_object);
+ g_test_add_func ("/nodes/immutable/array", test_immutable_array);
+ g_test_add_func ("/nodes/immutable/value", test_immutable_value);
+ g_test_add_func ("/nodes/immutable/parent", test_immutable_parent);
return g_test_run ();
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]