dfsm: Add all the missing API reference comments
[bendy-bus:bendy-bus.git] / dfsm / dfsm-ast-transition.c
1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 /*
3  * D-Bus Simulator
4  * Copyright (C) Philip Withnall 2011 <philip@tecnocode.co.uk>
5  * 
6  * D-Bus Simulator is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * D-Bus Simulator is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with D-Bus Simulator.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 /**
21  * SECTION:dfsm-ast-transition
22  * @short_description: AST transition node
23  * @stability: Unstable
24  * @include: dfsm/dfsm-ast-transition.h
25  *
26  * AST transition node containing the trigger, preconditions and statement list for a transition. It does not contain the from and to states for the
27  * transition (as in the simulation description), as these are provided by #DfsmAstObjectTransition wrappers around the #DfsmAstTransition.
28  */
29
30 #include "config.h"
31
32 #include <string.h>
33 #include <glib.h>
34 #include <glib/gi18n-lib.h>
35
36 #include "dfsm-ast-precondition.h"
37 #include "dfsm-ast-statement.h"
38 #include "dfsm-ast-statement-reply.h"
39 #include "dfsm-ast-statement-throw.h"
40 #include "dfsm-ast-transition.h"
41 #include "dfsm-internal.h"
42 #include "dfsm-parser.h"
43 #include "dfsm-parser-internal.h"
44 #include "dfsm-utils.h"
45
46 static void dfsm_ast_transition_dispose (GObject *object);
47 static void dfsm_ast_transition_finalize (GObject *object);
48 static void dfsm_ast_transition_sanity_check (DfsmAstNode *node);
49 static void dfsm_ast_transition_pre_check_and_register (DfsmAstNode *node, DfsmEnvironment *environment, GError **error);
50 static void dfsm_ast_transition_check (DfsmAstNode *node, DfsmEnvironment *environment, GError **error);
51
52 struct _DfsmAstTransitionPrivate {
53         DfsmAstTransitionTrigger trigger;
54         union {
55                 gchar *method_name; /* for DFSM_AST_TRANSITION_METHOD_CALL, otherwise NULL */
56                 gchar *property_name; /* for DFSM_AST_TRANSITION_PROPERTY_SET, otherwise NULL */
57         } trigger_params;
58         GPtrArray *preconditions; /* array of DfsmAstPreconditions */
59         GPtrArray *statements; /* array of DfsmAstStatements */
60         DfsmAstStatementReply *reply_statement; /* cache of the DfsmAstStatementReply in ->statements, if it exists */
61         DfsmAstStatementThrow *throw_statement; /* cache of the DfsmAstStatementThrow in ->statements, if it exists */
62 };
63
64 G_DEFINE_TYPE (DfsmAstTransition, dfsm_ast_transition, DFSM_TYPE_AST_NODE)
65
66 static void
67 dfsm_ast_transition_class_init (DfsmAstTransitionClass *klass)
68 {
69         GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
70         DfsmAstNodeClass *node_class = DFSM_AST_NODE_CLASS (klass);
71
72         g_type_class_add_private (klass, sizeof (DfsmAstTransitionPrivate));
73
74         gobject_class->dispose = dfsm_ast_transition_dispose;
75         gobject_class->finalize = dfsm_ast_transition_finalize;
76
77         node_class->sanity_check = dfsm_ast_transition_sanity_check;
78         node_class->pre_check_and_register = dfsm_ast_transition_pre_check_and_register;
79         node_class->check = dfsm_ast_transition_check;
80 }
81
82 static void
83 dfsm_ast_transition_init (DfsmAstTransition *self)
84 {
85         self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, DFSM_TYPE_AST_TRANSITION, DfsmAstTransitionPrivate);
86 }
87
88 static void
89 dfsm_ast_transition_dispose (GObject *object)
90 {
91         DfsmAstTransitionPrivate *priv = DFSM_AST_TRANSITION (object)->priv;
92
93         g_clear_object (&priv->throw_statement);
94         g_clear_object (&priv->reply_statement);
95
96         if (priv->statements != NULL) {
97                 g_ptr_array_unref (priv->statements);
98                 priv->statements = NULL;
99         }
100
101         if (priv->preconditions != NULL) {
102                 g_ptr_array_unref (priv->preconditions);
103                 priv->preconditions = NULL;
104         }
105
106         /* Chain up to the parent class */
107         G_OBJECT_CLASS (dfsm_ast_transition_parent_class)->dispose (object);
108 }
109
110 static void
111 dfsm_ast_transition_finalize (GObject *object)
112 {
113         DfsmAstTransitionPrivate *priv = DFSM_AST_TRANSITION (object)->priv;
114
115         switch (priv->trigger) {
116                 case DFSM_AST_TRANSITION_METHOD_CALL:
117                         g_free (priv->trigger_params.method_name);
118                         break;
119                 case DFSM_AST_TRANSITION_PROPERTY_SET:
120                         g_free (priv->trigger_params.property_name);
121                         break;
122                 case DFSM_AST_TRANSITION_ARBITRARY:
123                         /* Nothing to free here */
124                         break;
125                 default:
126                         g_assert_not_reached ();
127         }
128
129         /* Chain up to the parent class */
130         G_OBJECT_CLASS (dfsm_ast_transition_parent_class)->finalize (object);
131 }
132
133 static void
134 dfsm_ast_transition_sanity_check (DfsmAstNode *node)
135 {
136         DfsmAstTransitionPrivate *priv = DFSM_AST_TRANSITION (node)->priv;
137         guint i;
138
139         switch (priv->trigger) {
140                 case DFSM_AST_TRANSITION_METHOD_CALL:
141                         g_assert (priv->trigger_params.method_name != NULL);
142                         break;
143                 case DFSM_AST_TRANSITION_PROPERTY_SET:
144                         g_assert (priv->trigger_params.property_name != NULL);
145                         break;
146                 case DFSM_AST_TRANSITION_ARBITRARY:
147                         /* Nothing to do here */
148                         break;
149                 default:
150                         g_assert_not_reached ();
151         }
152
153         g_assert (priv->preconditions != NULL);
154
155         for (i = 0; i < priv->preconditions->len; i++) {
156                 g_assert (g_ptr_array_index (priv->preconditions, i) != NULL);
157                 dfsm_ast_node_sanity_check (DFSM_AST_NODE (g_ptr_array_index (priv->preconditions, i)));
158         }
159
160         g_assert (priv->statements != NULL);
161
162         for (i = 0; i < priv->statements->len; i++) {
163                 g_assert (g_ptr_array_index (priv->statements, i) != NULL);
164                 dfsm_ast_node_sanity_check (DFSM_AST_NODE (g_ptr_array_index (priv->statements, i)));
165         }
166
167         g_assert (priv->throw_statement == NULL || priv->reply_statement == NULL);
168
169         if (priv->throw_statement != NULL) {
170                 g_assert (DFSM_IS_AST_STATEMENT_THROW (priv->throw_statement));
171         }
172
173         if (priv->reply_statement != NULL) {
174                 g_assert (DFSM_IS_AST_STATEMENT_REPLY (priv->reply_statement));
175         }
176 }
177
178 static void
179 dfsm_ast_transition_pre_check_and_register (DfsmAstNode *node, DfsmEnvironment *environment, GError **error)
180 {
181         DfsmAstTransitionPrivate *priv = DFSM_AST_TRANSITION (node)->priv;
182         gboolean reply_statement_count = 0, throw_statement_count = 0;
183         guint i;
184
185         switch (priv->trigger) {
186                 case DFSM_AST_TRANSITION_METHOD_CALL:
187                         if (g_dbus_is_member_name (priv->trigger_params.method_name) == FALSE) {
188                                 g_set_error (error, DFSM_PARSE_ERROR, DFSM_PARSE_ERROR_AST_INVALID, _("Invalid D-Bus method name: %s"),
189                                              priv->trigger_params.method_name);
190                                 return;
191                         }
192
193                         break;
194                 case DFSM_AST_TRANSITION_PROPERTY_SET:
195                         if (g_dbus_is_member_name (priv->trigger_params.property_name) == FALSE) {
196                                 g_set_error (error, DFSM_PARSE_ERROR, DFSM_PARSE_ERROR_AST_INVALID, _("Invalid D-Bus property name: %s"),
197                                              priv->trigger_params.property_name);
198                                 return;
199                         }
200
201                         break;
202                 case DFSM_AST_TRANSITION_ARBITRARY:
203                         /* Nothing to do here */
204                         break;
205                 default:
206                         g_assert_not_reached ();
207         }
208
209         /* Check each of our preconditions, and also ensure that we only throw errors from preconditions if we're method-triggered. */
210         for (i = 0; i < priv->preconditions->len; i++) {
211                 DfsmAstPrecondition *precondition;
212
213                 precondition = DFSM_AST_PRECONDITION (g_ptr_array_index (priv->preconditions, i));
214
215                 dfsm_ast_node_pre_check_and_register (DFSM_AST_NODE (precondition), environment, error);
216
217                 if (*error != NULL) {
218                         return;
219                 }
220
221                 /* Have we illegally included an error in the precondition? */
222                 if (dfsm_ast_precondition_get_error_name (precondition) != NULL) {
223                         switch (priv->trigger) {
224                                 case DFSM_AST_TRANSITION_METHOD_CALL:
225                                         /* Nothing to do here. */
226                                         break;
227                                 case DFSM_AST_TRANSITION_PROPERTY_SET:
228                                 case DFSM_AST_TRANSITION_ARBITRARY:
229                                         /* Extraneous error in precondition. */
230                                         g_set_error (error, DFSM_PARSE_ERROR, DFSM_PARSE_ERROR_AST_INVALID,
231                                                      _("Unexpected ‘throwing’ clause on precondition. Preconditions on property-triggered and random "
232                                                        "transitions must not throw errors."));
233                                         return;
234                                 default:
235                                         g_assert_not_reached ();
236                         }
237                 }
238         }
239
240         /* Check each of our statements. */
241         for (i = 0; i < priv->statements->len; i++) {
242                 DfsmAstStatement *statement;
243
244                 statement = DFSM_AST_STATEMENT (g_ptr_array_index (priv->statements, i));
245
246                 dfsm_ast_node_pre_check_and_register (DFSM_AST_NODE (statement), environment, error);
247
248                 if (*error != NULL) {
249                         return;
250                 }
251
252                 /* What type of statement is it? */
253                 if (DFSM_IS_AST_STATEMENT_REPLY (statement)) {
254                         reply_statement_count++;
255
256                         g_clear_object (&priv->reply_statement);
257                         priv->reply_statement = g_object_ref (statement);
258                 } else if (DFSM_IS_AST_STATEMENT_THROW (statement)) {
259                         throw_statement_count++;
260
261                         g_clear_object (&priv->throw_statement);
262                         priv->throw_statement = g_object_ref (statement);
263                 }
264         }
265
266         /* Check that:
267          *  • if we're a method-triggered transition, we have exactly one reply or throw statement;
268          *  • if we're a random transition, we have no reply or throw statements; or
269          *  • if we're a property-triggered transition, we have no reply or throw statements. */
270         switch (priv->trigger) {
271                 case DFSM_AST_TRANSITION_METHOD_CALL:
272                         if (reply_statement_count == 0 && throw_statement_count == 0) {
273                                 /* No reply or throw statements. */
274                                 g_set_error (error, DFSM_PARSE_ERROR, DFSM_PARSE_ERROR_AST_INVALID,
275                                              _("Missing ‘reply’ or ‘throw’ statement in transition. Exactly one must be present in every transition."));
276                                 return;
277                         } else if (reply_statement_count + throw_statement_count != 1) {
278                                 /* Too many of either statement. */
279                                 g_set_error (error, DFSM_PARSE_ERROR, DFSM_PARSE_ERROR_AST_INVALID,
280                                              _("Too many ‘reply’ or ‘throw’ statements in transition. "
281                                                "Exactly one must be present in every transition."));
282                                 return;
283                         }
284
285                         break;
286                 case DFSM_AST_TRANSITION_PROPERTY_SET:
287                 case DFSM_AST_TRANSITION_ARBITRARY:
288                         if (reply_statement_count != 0 || throw_statement_count != 0) {
289                                 /* Extraneous reply or throw statement. */
290                                 g_set_error (error, DFSM_PARSE_ERROR, DFSM_PARSE_ERROR_AST_INVALID,
291                                              _("Unexpected ‘reply’ or ‘throw’ statement in transition. None must be present in property-triggered "
292                                                "and random transitions."));
293                                 return;
294                         }
295
296                         break;
297                 default:
298                         g_assert_not_reached ();
299         }
300 }
301
302 static void
303 dfsm_ast_transition_check (DfsmAstNode *node, DfsmEnvironment *environment, GError **error)
304 {
305         DfsmAstTransitionPrivate *priv = DFSM_AST_TRANSITION (node)->priv;
306         guint i;
307         GDBusMethodInfo *method_info = NULL;
308
309         switch (priv->trigger) {
310                 case DFSM_AST_TRANSITION_METHOD_CALL: {
311                         GPtrArray/*<GDBusInterfaceInfo>*/ *interfaces;
312
313                         interfaces = dfsm_environment_get_interfaces (environment);
314
315                         for (i = 0; i < interfaces->len; i++) {
316                                 GDBusInterfaceInfo *interface_info = (GDBusInterfaceInfo*) g_ptr_array_index (interfaces, i);
317
318                                 method_info = g_dbus_interface_info_lookup_method (interface_info, priv->trigger_params.method_name);
319
320                                 if (method_info != NULL) {
321                                         /* Found the interface defining method_name. */
322                                         break;
323                                 }
324                         }
325
326                         /* Failed to find a suitable interface? */
327                         if (method_info == NULL) {
328                                 g_set_error (error, DFSM_PARSE_ERROR, DFSM_PARSE_ERROR_AST_INVALID,
329                                              _("Undeclared D-Bus method referenced as a transition trigger: %s"), priv->trigger_params.method_name);
330                                 goto done;
331                         }
332
333                         /* Add the method's parameters to the environment so they're available when checking sub-nodes and the reply statement. */
334                         if (method_info->in_args != NULL) {
335                                 GDBusArgInfo **arg_infos;
336
337                                 for (arg_infos = method_info->in_args; *arg_infos != NULL; arg_infos++) {
338                                         GVariantType *parameter_type;
339
340                                         parameter_type = g_variant_type_new ((*arg_infos)->signature);
341                                         dfsm_environment_set_variable_type (environment, DFSM_VARIABLE_SCOPE_LOCAL, (*arg_infos)->name, parameter_type);
342                                         g_variant_type_free (parameter_type);
343                                 }
344                         }
345
346                         /* Check the type of the reply statement, if one exists. If there are no out args, the statement has to have unit type.
347                          * Otherwise, the statement has to be a struct of the required arity.
348                          * Note that this has to be done after the parameters have been added to the environmen, as the parameters might be referenced
349                          * by the reply statement. */
350                         if (priv->reply_statement != NULL) {
351                                 GVariantType *actual_out_type, *expected_out_type;
352
353                                 actual_out_type = dfsm_ast_expression_calculate_type (dfsm_ast_statement_reply_get_expression (priv->reply_statement),
354                                                                                       environment);
355                                 expected_out_type = dfsm_internal_dbus_arg_info_array_to_variant_type ((const GDBusArgInfo**) method_info->out_args);
356
357                                 if (g_variant_type_is_subtype_of (actual_out_type, expected_out_type) == FALSE) {
358                                         gchar *actual_out_type_string, *expected_out_type_string;
359
360                                         actual_out_type_string = g_variant_type_dup_string (actual_out_type);
361                                         expected_out_type_string = g_variant_type_dup_string (expected_out_type);
362
363                                         g_variant_type_free (expected_out_type);
364                                         g_variant_type_free (actual_out_type);
365
366                                         g_set_error (error, DFSM_PARSE_ERROR, DFSM_PARSE_ERROR_AST_INVALID,
367                                                      _("Type mismatch between formal and actual parameters to D-Bus reply statement: "
368                                                        "expects type ‘%s’ but received type ‘%s’."), expected_out_type_string, actual_out_type_string);
369
370                                         g_free (expected_out_type_string);
371                                         g_free (actual_out_type_string);
372
373                                         goto done;
374                                 }
375
376                                 g_variant_type_free (expected_out_type);
377                                 g_variant_type_free (actual_out_type);
378                         }
379
380                         break;
381                 }
382                 case DFSM_AST_TRANSITION_PROPERTY_SET: {
383                         GPtrArray/*<GDBusInterfaceInfo>*/ *interfaces;
384                         GDBusPropertyInfo *property_info;
385                         GVariantType *property_type;
386
387                         interfaces = dfsm_environment_get_interfaces (environment);
388
389                         for (i = 0; i < interfaces->len; i++) {
390                                 GDBusInterfaceInfo *interface_info = (GDBusInterfaceInfo*) g_ptr_array_index (interfaces, i);
391
392                                 property_info = g_dbus_interface_info_lookup_property (interface_info, priv->trigger_params.property_name);
393
394                                 if (property_info != NULL) {
395                                         /* Found the interface defining property_name. */
396                                         break;
397                                 }
398                         }
399
400                         /* Failed to find a suitable interface? */
401                         if (property_info == NULL) {
402                                 g_set_error (error, DFSM_PARSE_ERROR, DFSM_PARSE_ERROR_AST_INVALID,
403                                              _("Undeclared D-Bus property referenced as a transition trigger: %s"), priv->trigger_params.property_name);
404                                 goto done;
405                         }
406
407                         /* Warn if the property isn't writeable. */
408                         if ((property_info->flags & G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE) == 0) {
409                                 g_warning (_("D-Bus property ‘%s’ referenced as a transition trigger is not writeable."),
410                                            priv->trigger_params.property_name);
411                         }
412
413                         /* Add the special “value” parameter to the environment so it's available when checking sub-nodes. */
414                         property_type = g_variant_type_new (property_info->signature);
415                         dfsm_environment_set_variable_type (environment, DFSM_VARIABLE_SCOPE_LOCAL, "value", property_type);
416                         g_variant_type_free (property_type);
417
418                         break;
419                 }
420                 case DFSM_AST_TRANSITION_ARBITRARY:
421                         /* Nothing to do here */
422                         break;
423                 default:
424                         g_assert_not_reached ();
425         }
426
427         for (i = 0; i < priv->preconditions->len; i++) {
428                 DfsmAstPrecondition *precondition;
429
430                 precondition = DFSM_AST_PRECONDITION (g_ptr_array_index (priv->preconditions, i));
431
432                 dfsm_ast_node_check (DFSM_AST_NODE (precondition), environment, error);
433
434                 if (*error != NULL) {
435                         goto done;
436                 }
437         }
438
439         for (i = 0; i < priv->statements->len; i++) {
440                 DfsmAstStatement *statement;
441
442                 statement = DFSM_AST_STATEMENT (g_ptr_array_index (priv->statements, i));
443
444                 dfsm_ast_node_check (DFSM_AST_NODE (statement), environment, error);
445
446                 if (*error != NULL) {
447                         goto done;
448                 }
449         }
450
451 done:
452         /* Restore the environment if this is a method- or property-triggered transition. */
453         if (priv->trigger == DFSM_AST_TRANSITION_METHOD_CALL && method_info != NULL && method_info->in_args != NULL) {
454                 GDBusArgInfo **arg_infos;
455
456                 for (arg_infos = method_info->in_args; *arg_infos != NULL; arg_infos++) {
457                         dfsm_environment_unset_variable_value (environment, DFSM_VARIABLE_SCOPE_LOCAL, (*arg_infos)->name);
458                 }
459         } else if (priv->trigger == DFSM_AST_TRANSITION_PROPERTY_SET) {
460                 dfsm_environment_unset_variable_value (environment, DFSM_VARIABLE_SCOPE_LOCAL, "value");
461         }
462 }
463
464 /**
465  * dfsm_ast_transition_new:
466  * @details: details of the transition trigger (such as a method or property name)
467  * @precondition: array of #DfsmAstPrecondition<!-- -->s for the transition
468  * @statements: array of #DfsmAstStatement<!-- -->s to execute with the transition
469  *
470  * Create a new #DfsmAstTransition representing a single transition. The state pairs the transition can be applied to are stored in the #DfsmAstObject
471  * containing this transition, since the same #DfsmAstTransition could be applied to many different state pairs.
472  *
473  * Return value: (transfer full): a new AST node
474  */
475 DfsmAstTransition *
476 dfsm_ast_transition_new (const DfsmParserTransitionDetails *details, GPtrArray/*<DfsmAstPrecondition>*/ *preconditions,
477                          GPtrArray/*<DfsmAstStatement>*/ *statements)
478 {
479         DfsmAstTransition *transition;
480         DfsmAstTransitionPrivate *priv;
481
482         g_return_val_if_fail (details != NULL, NULL);
483         g_return_val_if_fail (preconditions != NULL, NULL);
484         g_return_val_if_fail (statements != NULL, NULL);
485
486         transition = g_object_new (DFSM_TYPE_AST_TRANSITION, NULL);
487         priv = transition->priv;
488
489         priv->trigger = details->transition_type;
490
491         switch (priv->trigger) {
492                 case DFSM_AST_TRANSITION_METHOD_CALL:
493                         priv->trigger_params.method_name = g_strdup (details->str);
494                         break;
495                 case DFSM_AST_TRANSITION_PROPERTY_SET:
496                         priv->trigger_params.property_name = g_strdup (details->str);
497                         break;
498                 case DFSM_AST_TRANSITION_ARBITRARY:
499                         /* Nothing to do. */
500                         break;
501                 default:
502                         g_assert_not_reached ();
503         }
504
505         priv->preconditions = g_ptr_array_ref (preconditions);
506         priv->statements = g_ptr_array_ref (statements);
507
508         return transition;
509 }
510
511 /**
512  * dfsm_ast_transition_get_preconditions:
513  * @self: a #DfsmAstTransition
514  *
515  * Returns an array of the transition's #DfsmAstPrecondition<!-- -->s. The array may be empty, but will never be %NULL.
516  *
517  * Return value: (transfer none) (element-type DfsmAstPrecondition): array of preconditions
518  */
519 GPtrArray/*<DfsmAstPrecondition>*/ *
520 dfsm_ast_transition_get_preconditions (DfsmAstTransition *self)
521 {
522         g_return_val_if_fail (DFSM_IS_AST_TRANSITION (self), NULL);
523
524         return self->priv->preconditions;
525 }
526
527 /**
528  * dfsm_ast_transition_check_preconditions:
529  * @self: a #DfsmAstTransition
530  * @environment: the environment to execute the transition in
531  * @output_sequence: (allow-none): an output sequence to append the precondition error to if necessary
532  * @will_throw_error: (allow-none) (out caller-allocates): return location for %TRUE if the transition will throw an error to @output_sequence on
533  * precondition failure, %FALSE otherwise
534  *
535  * Check the preconditions of the given transition in the state given by @environment. The @environment will not be modified.
536  *
537  * If the preconditions are satisfied, %TRUE will be returned; %FALSE will be returned otherwise. If the preconditions are not satisfied and they
538  * specified a D-Bus error to be thrown on failure, the error will be appended to @output_sequence (if @output_sequence is non-%NULL) and %FALSE will
539  * be returned. @will_throw_error will always reflect whether precondition failures will modify @output_sequence, so this function may be called with
540  * @output_sequence set to %NULL to determine whether precondition failures will cause D-Bus errors.
541  *
542  * Return value: %TRUE if the transition's preconditions are satisfied; %FALSE otherwise
543  */
544 gboolean
545 dfsm_ast_transition_check_preconditions (DfsmAstTransition *self, DfsmEnvironment *environment, DfsmOutputSequence *output_sequence,
546                                          gboolean *will_throw_error)
547 {
548         DfsmAstTransitionPrivate *priv;
549         guint i;
550
551         g_return_val_if_fail (DFSM_IS_AST_TRANSITION (self), FALSE);
552         g_return_val_if_fail (DFSM_IS_ENVIRONMENT (environment), FALSE);
553         g_return_val_if_fail (output_sequence == NULL || DFSM_IS_OUTPUT_SEQUENCE (output_sequence), FALSE);
554
555         priv = self->priv;
556
557         /* Check each of the preconditions in order and return when the first one fails. */
558         for (i = 0; i < priv->preconditions->len; i++) {
559                 DfsmAstPrecondition *precondition = DFSM_AST_PRECONDITION (g_ptr_array_index (priv->preconditions, i));
560
561                 if (dfsm_ast_precondition_check_is_satisfied (precondition, environment) == FALSE) {
562                         /* If called with an output_sequence, will we throw an error? */
563                         if (will_throw_error != NULL) {
564                                 *will_throw_error = (dfsm_ast_precondition_get_error_name (precondition) != NULL) ? TRUE : FALSE;
565                         }
566
567                         /* If an output sequence has been provided, append an error to it. */
568                         if (output_sequence != NULL) {
569                                 dfsm_ast_precondition_throw_error (precondition, output_sequence);
570                         }
571
572                         return FALSE;
573                 }
574         }
575
576         if (will_throw_error != NULL) {
577                 *will_throw_error = FALSE;
578         }
579
580         return TRUE;
581 }
582
583 /**
584  * dfsm_ast_transition_execute:
585  * @self: a #DfsmAstTransition
586  * @environment: the environment to execute the transition in
587  * @output_sequence: an output sequence to append the transition's effects to
588  *
589  * Execute a given state machine transition. This may modify the @environment. It assumes that dfsm_ast_transition_check_preconditions() has already
590  * been called for this transition and @environment and has returned %TRUE. It is an error to call this function otherwise.
591  *
592  * Any effects caused by the transition (such as D-Bus signal emissions, method replies and error replies to D-Bus method calls) will be appended to
593  * @output_sequence in execution order. The caller may then use dfsm_output_sequence_output() to push the effects out onto the bus.
594  *
595  * Return value: (transfer full): reply parameters from the transition, or %NULL
596  */
597 void
598 dfsm_ast_transition_execute (DfsmAstTransition *self, DfsmEnvironment *environment, DfsmOutputSequence *output_sequence)
599 {
600         DfsmAstTransitionPrivate *priv;
601         guint i;
602
603         g_return_if_fail (DFSM_IS_AST_TRANSITION (self));
604         g_return_if_fail (DFSM_IS_ENVIRONMENT (environment));
605         g_return_if_fail (DFSM_IS_OUTPUT_SEQUENCE (output_sequence));
606
607         priv = self->priv;
608
609         g_debug ("Executing transition %p in environment %p.", self, environment);
610
611         for (i = 0; i < priv->statements->len; i++) {
612                 DfsmAstStatement *statement;
613
614                 statement = g_ptr_array_index (priv->statements, i);
615                 dfsm_ast_statement_execute (statement, environment, output_sequence);
616         }
617 }
618
619 /**
620  * dfsm_ast_transition_get_trigger:
621  * @self: a #DfsmAstTransition
622  *
623  * Gets the type of triggering for this transition.
624  *
625  * Return value: trigger type for the transition
626  */
627 DfsmAstTransitionTrigger
628 dfsm_ast_transition_get_trigger (DfsmAstTransition *self)
629 {
630         g_return_val_if_fail (DFSM_IS_AST_TRANSITION (self), DFSM_AST_TRANSITION_ARBITRARY);
631
632         return self->priv->trigger;
633 }
634
635 /**
636  * dfsm_ast_transition_get_trigger_method_name:
637  * @self: a #DfsmAstTransition
638  *
639  * Gets the name of the D-Bus method triggering this transition when it's called by the client. It is only valid to call this method if
640  * dfsm_ast_transition_get_trigger() returns %DFSM_AST_TRANSITION_METHOD_CALL.
641  *
642  * Return value: name of the D-Bus method triggering this transition
643  */
644 const gchar *
645 dfsm_ast_transition_get_trigger_method_name (DfsmAstTransition *self)
646 {
647         g_return_val_if_fail (DFSM_IS_AST_TRANSITION (self), NULL);
648         g_return_val_if_fail (self->priv->trigger == DFSM_AST_TRANSITION_METHOD_CALL, NULL);
649
650         return self->priv->trigger_params.method_name;
651 }
652
653 /**
654  * dfsm_ast_transition_get_trigger_property_name:
655  * @self: a #DfsmAstTransition
656  *
657  * Gets the name of the D-Bus property triggering this transition when it's assigned to. It is only valid to call this method if
658  * dfsm_ast_transition_get_trigger() returns %DFSM_AST_TRANSITION_PROPERTY_SET.
659  *
660  * Return value: name of the D-Bus property triggering this transition
661  */
662 const gchar *
663 dfsm_ast_transition_get_trigger_property_name (DfsmAstTransition *self)
664 {
665         g_return_val_if_fail (DFSM_IS_AST_TRANSITION (self), NULL);
666         g_return_val_if_fail (self->priv->trigger == DFSM_AST_TRANSITION_PROPERTY_SET, NULL);
667
668         return self->priv->trigger_params.property_name;
669 }
670
671 /**
672  * dfsm_ast_transition_contains_throw_statement:
673  * @self: a #DfsmAstTransition
674  *
675  * Gets whether the transition contains a statement of type #DfsmAstStatementThrow. The alternatives are to contain a #DfsmAstStatementReply instead,
676  * or to contain neither statement, depending on the trigger type of the transition.
677  *
678  * It is only valid to call this method after successfully calling dfsm_ast_node_pre_check_and_register().
679  *
680  * Return value: %TRUE if the transition contains a #DfsmAstStatementThrow, %FALSE otherwise
681  */
682 gboolean
683 dfsm_ast_transition_contains_throw_statement (DfsmAstTransition *self)
684 {
685         g_return_val_if_fail (DFSM_IS_AST_TRANSITION (self), FALSE);
686
687         return (self->priv->throw_statement != NULL) ? TRUE : FALSE;
688 }
689
690 /**
691  * dfsm_ast_transition_get_statements:
692  * @self: a #DfsmAstTransition
693  *
694  * Returns an array of the transition's #DfsmAstStatement<!-- -->s. The array will always contain at least one element, and will never be %NULL.
695  *
696  * Return value: (transfer none) (element-type DfsmAstStatement): array of statements
697  */
698 GPtrArray/*<DfsmAstStatement>*/ *
699 dfsm_ast_transition_get_statements (DfsmAstTransition *self)
700 {
701         g_return_val_if_fail (DFSM_IS_AST_TRANSITION (self), NULL);
702
703         g_assert (self->priv->statements->len > 0);
704         return self->priv->statements;
705 }