gjs r116 - in trunk: gi test/js



Author: otaylor
Date: Wed Nov 19 22:30:59 2008
New Revision: 116
URL: http://svn.gnome.org/viewvc/gjs?rev=116&view=rev

Log:
Allow "copy construction" of boxed types

gi/boxed.c: Allow a single argument to be passed to the constructor,
which can be:

 - Another object of the same type (copied with g_boxed_copy(),
   or, as proofing against future support for non-boxed structures,
   memcpy)
 - A hash table of properties to set as fields of the object.

test/js/testEverythingEncapsulated.js: Test copy construction

https://bugzilla.gnome.org/show_bug.cgi?id=560808

Modified:
   trunk/gi/boxed.c
   trunk/test/js/testEverythingEncapsulated.js

Modified: trunk/gi/boxed.c
==============================================================================
--- trunk/gi/boxed.c	(original)
+++ trunk/gi/boxed.c	Wed Nov 19 22:30:59 2008
@@ -54,6 +54,11 @@
 
 static gboolean struct_is_simple(GIStructInfo *info);
 
+static JSBool boxed_set_field_from_value(JSContext   *context,
+                                         Boxed       *priv,
+                                         GIFieldInfo *field_info,
+                                         jsval        value);
+
 static BoxedConstructInfo unthreadsafe_template_for_constructor = { NULL, NULL, JSVAL_NULL };
 
 static struct JSClass gjs_boxed_class;
@@ -152,6 +157,32 @@
     return JS_TRUE;
 }
 
+/* Check to see if jsval passed in is another Boxed object of the same,
+ * and if so, retrieves the Boxed private structure for it.
+ */
+static JSBool
+boxed_get_copy_source(JSContext *context,
+                      Boxed     *priv,
+                      jsval      value,
+                      Boxed    **source_priv_out)
+{
+    Boxed *source_priv;
+
+    if (!JSVAL_IS_OBJECT(value))
+        return JS_FALSE;
+
+    if (!priv_from_js_with_typecheck(context, JSVAL_TO_OBJECT(value), &source_priv))
+        return JS_FALSE;
+
+    if (strcmp(g_base_info_get_name((GIBaseInfo*) priv->info),
+               g_base_info_get_name((GIBaseInfo*) source_priv->info)) != 0)
+        return JS_FALSE;
+
+    *source_priv_out = source_priv;
+
+    return JS_TRUE;
+}
+
 static JSBool
 boxed_new(JSContext   *context,
           JSObject    *obj, /* "this" for constructor */
@@ -222,6 +253,140 @@
     return JS_FALSE;
 }
 
+/* When initializing a boxed object from a hash of properties, we don't want
+ * to do n O(n) lookups, so put temporarily put the fields into a hash table
+ * for fast lookup. We could also do this ahead of time and store it on proto->priv.
+ */
+static GHashTable *
+get_field_map(GIStructInfo *struct_info)
+{
+    GHashTable *result;
+    int n_fields;
+    int i;
+
+    result = g_hash_table_new_full(g_str_hash, g_str_equal,
+                                   NULL, (GDestroyNotify)g_base_info_unref);
+    n_fields = g_struct_info_get_n_fields(struct_info);
+
+    for (i = 0; i < n_fields; i++) {
+        GIFieldInfo *field_info = g_struct_info_get_field(struct_info, i);
+        g_hash_table_insert(result, (char *)g_base_info_get_name((GIBaseInfo *)field_info), field_info);
+    }
+
+    return result;
+}
+
+/* Initialize a newly created Boxed from an object that is a "hash" of
+ * properties to set as fieds of the object. We don't require that every field
+ * of the object be set.
+ */
+static JSBool
+boxed_init_from_props(JSContext   *context,
+                      JSObject    *obj,
+                      Boxed       *priv,
+                      jsval        props_value)
+{
+    JSObject *props;
+    JSObject *iter;
+    jsid prop_id;
+    GHashTable *field_map;
+    gboolean success;
+
+    success = FALSE;
+
+    if (!JSVAL_IS_OBJECT(props_value)) {
+        gjs_throw(context, "argument should be a hash with fields to set");
+        return JS_FALSE;
+    }
+
+    props = JSVAL_TO_OBJECT(props_value);
+
+    iter = JS_NewPropertyIterator(context, props);
+    if (iter == NULL) {
+        gjs_throw(context, "Failed to create property iterator for fields hash");
+        return JS_FALSE;
+    }
+
+    field_map = get_field_map(priv->info);
+
+    prop_id = JSVAL_VOID;
+    if (!JS_NextProperty(context, iter, &prop_id))
+        goto out;
+
+    while (prop_id != JSVAL_VOID) {
+        GIFieldInfo *field_info;
+        jsval nameval;
+        const char *name;
+        jsval value;
+
+        if (!JS_IdToValue(context, prop_id, &nameval))
+            goto out;
+
+        if (!gjs_get_string_id(nameval, &name))
+            goto out;
+
+        field_info = g_hash_table_lookup(field_map, name);
+        if (field_info == NULL) {
+            gjs_throw(context, "No field %s on boxed type %s",
+                      name, g_base_info_get_name((GIBaseInfo *)priv->info));
+            goto out;
+        }
+
+        if (!gjs_object_require_property(context, props, name, &value))
+            goto out;
+
+        if (!boxed_set_field_from_value(context, priv, field_info, value)) {
+            goto out;
+        }
+
+        prop_id = JSVAL_VOID;
+        if (!JS_NextProperty(context, iter, &prop_id))
+            goto out;
+    }
+
+    success = TRUE;
+
+out:
+    g_hash_table_destroy(field_map);
+
+    return success;
+}
+
+/* Do any initialization of a newly constructed object from the arguments passed
+ * in from Javascript.
+ */
+static JSBool
+boxed_init(JSContext   *context,
+           JSObject    *obj, /* "this" for constructor */
+           Boxed       *priv,
+           uintN        argc,
+           jsval       *argv)
+{
+    if (argc == 0)
+        return JS_TRUE;
+
+    if (argc == 1) {
+        Boxed *source_priv;
+
+        /* Short-cut to memcpy when possible */
+        if (priv->can_allocate_directly &&
+            boxed_get_copy_source (context, priv, argv[0], &source_priv)) {
+
+            memcpy(priv->gboxed, source_priv->gboxed,
+                   g_struct_info_get_size (priv->info));
+
+            return JS_TRUE;
+        }
+
+        return boxed_init_from_props(context, obj, priv, argv[0]);
+    }
+
+    gjs_throw(context, "Constructor with multiple arguments not supported for %s",
+              g_base_info_get_name((GIBaseInfo *)priv->info));
+
+    return JS_FALSE;
+}
+
 /* If we set JSCLASS_CONSTRUCT_PROTOTYPE flag, then this is called on
  * the prototype in addition to on each instance. When called on the
  * prototype, "obj" is the prototype, and "retval" is the prototype
@@ -302,8 +467,25 @@
         unthreadsafe_template_for_constructor.info = NULL;
 
         if (unthreadsafe_template_for_constructor.gboxed == NULL) {
+            Boxed *source_priv;
+
+            /* Short-circuit copy-construction in the case where we can use g_boxed_copy */
+            if (argc == 1 &&
+                boxed_get_copy_source(context, priv, argv[0], &source_priv)) {
+
+                GType gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) priv->info);
+                if (gtype != G_TYPE_NONE) {
+                    priv->gboxed = g_boxed_copy(gtype, source_priv->gboxed);
+                    return JS_TRUE;
+                }
+            }
+
             if (!boxed_new(context, obj, priv))
                 return JS_FALSE;
+
+            if (!boxed_init(context, obj, priv, argc, argv))
+                return JS_FALSE;
+
         } else if (!JSVAL_IS_NULL(unthreadsafe_template_for_constructor.parent_jsval)) {
             /* A structure nested inside a parent object; doens't have an independent allocation */
 
@@ -496,36 +678,19 @@
 }
 
 static JSBool
-boxed_field_setter (JSContext *context,
-                    JSObject  *obj,
-                    jsval      id,
-                    jsval     *value)
+boxed_set_field_from_value(JSContext   *context,
+                           Boxed       *priv,
+                           GIFieldInfo *field_info,
+                           jsval        value)
 {
-    Boxed *priv;
-    GIFieldInfo *field_info;
     GITypeInfo *type_info;
     GArgument arg;
     gboolean success = FALSE;
     gboolean need_release = FALSE;
 
-    priv = priv_from_js(context, obj);
-    if (!priv)
-        return JS_FALSE;
-
-    field_info = get_field_info(context, priv, id);
-    if (!field_info)
-        return JS_FALSE;
-
     type_info = g_field_info_get_type (field_info);
 
-    if (priv->gboxed == NULL) { /* direct access to proto field */
-        gjs_throw(context, "Can't set field %s.%s on prototype",
-                  g_base_info_get_name ((GIBaseInfo *)priv->info),
-                  g_base_info_get_name ((GIBaseInfo *)field_info));
-        goto out;
-    }
-
-    if (!gjs_value_to_g_argument(context, *value,
+    if (!gjs_value_to_g_argument(context, value,
                                  type_info,
                                  g_base_info_get_name ((GIBaseInfo *)field_info),
                                  GJS_ARGUMENT_FIELD,
@@ -549,13 +714,45 @@
                                 type_info,
                                 &arg);
 
-    g_base_info_unref ((GIBaseInfo *)field_info);
     g_base_info_unref ((GIBaseInfo *)type_info);
 
     return success;
 }
 
 static JSBool
+boxed_field_setter (JSContext *context,
+                    JSObject  *obj,
+                    jsval      id,
+                    jsval     *value)
+{
+    Boxed *priv;
+    GIFieldInfo *field_info;
+    gboolean success = FALSE;
+
+    priv = priv_from_js(context, obj);
+    if (!priv)
+        return JS_FALSE;
+
+    field_info = get_field_info(context, priv, id);
+    if (!field_info)
+        return JS_FALSE;
+
+    if (priv->gboxed == NULL) { /* direct access to proto field */
+        gjs_throw(context, "Can't set field %s.%s on prototype",
+                  g_base_info_get_name ((GIBaseInfo *)priv->info),
+                  g_base_info_get_name ((GIBaseInfo *)field_info));
+        goto out;
+    }
+
+    success = boxed_set_field_from_value (context, priv, field_info, *value);
+
+out:
+    g_base_info_unref ((GIBaseInfo *)field_info);
+
+    return success;
+}
+
+static JSBool
 define_boxed_class_fields (JSContext *context,
                            Boxed     *priv,
                            JSObject  *proto)
@@ -861,8 +1058,8 @@
                                            * Math - rarely correct)
                                            */
                                           boxed_constructor,
-                                          /* number of constructor args */
-                                          0,
+                                          /* number of constructor args (less can be passed) */
+                                          1,
                                           /* props of prototype */
                                           &gjs_boxed_proto_props[0],
                                           /* funcs of prototype */

Modified: trunk/test/js/testEverythingEncapsulated.js
==============================================================================
--- trunk/test/js/testEverythingEncapsulated.js	(original)
+++ trunk/test/js/testEverythingEncapsulated.js	Wed Nov 19 22:30:59 2008
@@ -10,9 +10,33 @@
     assertEquals(42.5, simple_boxed.some_double);
 }
 
+function testBoxedCopyConstructor()
+{
+    // "Copy" an object from a hash of field values
+    let simple_boxed = new Everything.TestSimpleBoxedA({ some_int: 42,
+							 some_int8: 43,
+							 some_double: 42.5 });
+
+    assertEquals(42, simple_boxed.some_int);
+    assertEquals(43, simple_boxed.some_int8);
+    assertEquals(42.5, simple_boxed.some_double);
+
+    // Make sure we catch bad field names
+    assertRaises(function() {
+	let t = new Everything.TestSimpleBoxedA({ junk: 42 });
+    });
+
+    // Copy an object from another object of the same type, shortcuts to the boxed copy
+    let copy = new Everything.TestSimpleBoxedA(simple_boxed);
+
+    assertEquals(42, copy.some_int);
+    assertEquals(43, copy.some_int8);
+    assertEquals(42.5, copy.some_double);
+ }
+
 function testNestedSimpleBoxed() {
     let simple_boxed = new Everything.TestSimpleBoxedB();
-    simple_boxed.some_int8 = 42
+    simple_boxed.some_int8 = 42;
     simple_boxed.nested_a.some_int = 43;
     assertEquals(42, simple_boxed.some_int8);
     assertEquals(43, simple_boxed.nested_a.some_int);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]