Complex types needs an owned get accessor
[vala-dbus-binding-tool.git] / src / vala-dbus-binding-tool.vala
blobd7c4cc06074acc2ce695f1becfaa01aa5aff5a77
1 /*
2 * vala-dbus-binding-tool.vala
4 * (C) 2009 by Didier "Ptitjes" <ptitjes@free.fr>
5 * (C) 2009-2011 the freesmartphone.org team <smartphones-standards@linuxtogo.org>
7 * GPLv3
8 */
9 using GLib;
10 using Xml;
11 using Gee;
13 public errordomain GeneratorError {
14 FILE_NOT_FOUND,
15 CANT_CREATE_FILE,
16 UNKNOWN_DBUS_TYPE
19 public enum Synchrony {
20 AUTO,
21 FORCE_SYNC,
22 FORCE_ASYNC
25 internal class GeneratedNamespace {
26 public GeneratedNamespace parent;
27 public string name;
28 public Gee.Map<string, Xml.Node*> members
29 = new Gee.HashMap<string, Xml.Node*>(str_hash, str_equal, direct_equal);
30 public Gee.Map<string, GeneratedNamespace> namespaces
31 = new Gee.HashMap<string, GeneratedNamespace>(str_hash, str_equal, direct_equal);
34 public class BindingGenerator : Object {
36 private static Set<string> registered_names = new HashSet<string>(str_hash, str_equal);
37 private static int verbosity;
38 private static int errors;
39 private static bool synced;
40 private static bool gdbus;
42 static construct {
43 registered_names.add("using");
44 registered_names.add("namespace");
45 registered_names.add("public");
46 registered_names.add("private");
47 registered_names.add("internal");
48 registered_names.add("errordomain");
49 registered_names.add("class");
50 registered_names.add("struct");
51 registered_names.add("new");
52 registered_names.add("for");
53 registered_names.add("while");
54 registered_names.add("foreach");
55 registered_names.add("switch");
56 registered_names.add("case");
57 registered_names.add("static");
58 registered_names.add("unowned");
59 registered_names.add("weak");
60 registered_names.add("register");
61 registered_names.add("message");
62 registered_names.add("get_type");
63 registered_names.add("dispose");
64 registered_names.add("result");
67 public static void INFO(string msg) {
68 if (verbosity >= 1)
69 stdout.printf(@"[INFO] $msg\n");
72 public static void DEBUG(string msg) {
73 if (verbosity >= 2)
74 stdout.printf(@"[DEBUG] $msg\n");
77 public static void WARN(string msg) {
78 stderr.printf(@"[WARN] $msg\n");
81 public static void ERROR(string msg) {
82 stderr.printf(@"[ERROR] $msg\n");
83 errors++;
86 public static int main(string[] args) {
87 //FIXME: Convert to OptionEntry
88 string[] split_name = args[0].split("/");
89 string program_name = split_name[split_name.length - 1];
90 string command = string.joinv(" ", args);
92 string api_path = null;
93 string output_directory = null;
94 uint dbus_timeout = 120000;
95 synced = true;
97 Map<string,string> namespace_renaming = new HashMap<string,string>(str_hash, str_equal, str_equal);
99 for (int i = 1; i < args.length; i++) {
100 string arg = args[i];
102 string[] split_arg = arg.split("=");
103 switch (split_arg[0]) {
104 case "-h":
105 case "--help":
106 show_usage(program_name);
107 return 0;
108 case "-v":
109 verbosity++;
110 break;
111 case "--version":
112 show_version();
113 return 0;
114 case "--api-path":
115 api_path = split_arg[1];
116 break;
117 case "-d":
118 case "--directory":
119 output_directory = split_arg[1];
120 break;
121 case "--strip-namespace":
122 namespace_renaming.set(split_arg[1], "");
123 break;
124 case "--rename-namespace":
125 string[] ns_split = split_arg[1].split(":");
126 namespace_renaming.set(ns_split[0], ns_split[1]);
127 break;
128 case "--dbus-timeout":
129 dbus_timeout = int.parse( split_arg[1] );
130 break;
131 case "--no-synced":
132 synced = false;
133 break;
134 case "--gdbus":
135 gdbus = true;
136 break;
137 default:
138 stdout.printf("%s: Unknown option %s\n", program_name, arg);
139 show_usage(program_name);
140 return 1;
144 if (api_path == null)
145 api_path = "./";
146 if (output_directory == null)
147 output_directory = ".";
149 try {
150 generate(api_path, output_directory, namespace_renaming, command, dbus_timeout);
151 } catch (GLib.FileError ex) {
152 ERROR(ex.message);
153 return 1;
154 } catch (GeneratorError ex) {
155 ERROR(ex.message);
156 return 1;
159 if (errors > 0) {
160 stdout.printf( @"\n$errors errors detected in API files. The generated files will not be usable.\n" );
161 return 1;
163 return 0;
166 private static void show_version() {
167 stdout.printf(@"Vala D-Bus Binding Tool $(Config.PACKAGE_VERSION)\n");
168 stdout.printf("Written by Didier \"Ptitjes\" and the freesmartphone.org team\n");
169 stdout.printf("This is free software; see the source for copying conditions.\n");
170 stdout.printf("There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n");
173 private static void show_usage(string program_name) {
174 stdout.printf("Usage:\n");
175 stdout.printf(" %s [-v] [--version] [--help]\n", program_name);
176 stdout.printf(" %s [--gdbus] [--api-path=PATH] [--no-synced] [--dbus-timeout=TIMEOUT] [--directory=DIR] [--strip-namespace=NS]* [--rename-namespace=OLD_NS:NEW_NS]*\n", program_name);
179 public static void generate(string api_path, string output_directory,
180 Map<string,string> namespace_renaming, string command, uint dbus_timeout)
181 throws GeneratorError, GLib.FileError {
183 Parser.init();
185 BindingGenerator generator = new BindingGenerator(output_directory, namespace_renaming, command, dbus_timeout);
186 generator.generate_bindings(api_path);
188 Parser.cleanup();
191 private BindingGenerator(string output_directory, Map<string,string> namespace_renaming, string command, uint dbus_timeout) {
192 this.output_directory = output_directory;
193 this.namespace_renaming = namespace_renaming;
194 this.command = command;
195 this.dbus_timeout = dbus_timeout;
198 private string output_directory;
199 private Map<string,string> namespace_renaming;
200 private string command;
201 private uint dbus_timeout;
202 private bool inner_interface_strategy_concat = true;
204 private static const string FSO_NAMESPACE = "http://www.freesmartphone.org/schemas/DBusSpecExtension";
206 private static const string INTERFACE_ELTNAME = "interface";
207 private static const string METHOD_ELTNAME = "method";
208 private static const string SIGNAL_ELTNAME = "signal";
209 private static const string PROPERTY_ELTNAME = "property";
210 private static const string ARG_ELTNAME = "arg";
211 private static const string NAME_ATTRNAME = "name";
212 private static const string TYPE_ATTRNAME = "type";
213 private static const string DIRECTION_ATTRNAME = "direction";
214 private static const string REPLACED_BY_ATTRNAME = "replaced-by";
215 private static const string IN_ATTRVALUE = "in";
216 private static const string OUT_ATTRVALUE = "out";
217 private static const string ENUMERATION_ELTNAME = "enumeration";
218 private static const string MEMBER_ELTNAME = "member";
219 private static const string VALUE_ATTRNAME = "value";
220 private static const string ERRORDOMAIN_ELTNAME = "errordomain";
221 private static const string ERROR_ELTNAME = "error";
222 private static const string NO_CONTAINER_ATTRNAME = "no-container";
223 private static const string THROWS_ELTNAME = "throws";
224 private static const string STRUCT_ELTNAME = "struct";
225 private static const string FIELD_ELTNAME = "field";
226 private static const string ANNOTATION_ELTNAME = "annotation";
227 private static const string DEPRECATED_ELTNAME = "deprecated";
229 private void generate_bindings(string api_path)
230 throws GeneratorError, GLib.FileError {
232 if (api_path.has_suffix(".xml")) {
233 add_api_file(api_path);
234 } else {
235 GLib.Dir dir = GLib.Dir.open(api_path);
236 string name;
237 while ((name = dir.read_name()) != null) {
238 if (name.has_suffix(".xml")) {
239 add_api_file(Path.build_filename(api_path, name));
244 index_names(root_namespace);
245 generate_namespace(root_namespace);
248 private void add_api_file(string api_file) throws GeneratorError {
249 INFO(@"Adding API file $api_file");
250 // Parse the API document from path
251 Xml.Doc* api_doc = Parser.parse_file(api_file);
252 if (api_doc == null) {
253 throw new GeneratorError.FILE_NOT_FOUND(api_file);
256 api_docs.add(api_doc);
258 preprocess_binding_names(api_doc);
261 private FileStream output;
263 private void create_binding_file(string name) throws GeneratorError {
264 output = FileStream.open(name, "w");
265 if (output == null) {
266 throw new GeneratorError.CANT_CREATE_FILE(name);
269 output.printf(@"/* Generated by vala-dbus-binding-tool $(Config.PACKAGE_VERSION). Do not modify! */\n");
270 output.printf(@"/* Generated with: $command */\n");
271 if (!gdbus)
272 output.printf("using DBus;\n");
273 output.printf("using GLib;\n");
276 private Gee.List<Xml.Doc*> api_docs = new Gee.ArrayList<Xml.Doc*>();
278 private void preprocess_binding_names(Xml.Doc* api_doc) {
279 for (Xml.Node* iter = api_doc->get_root_element()->children; iter != null; iter = iter->next) {
280 //FIXME: Use $(iter->type) when enum to_string works
281 DEBUG(@" Processing $(iter->name) as type %d".printf(iter->type));
282 if (iter->type != ElementType.ELEMENT_NODE) {
283 DEBUG(@" not a node; continuing");
284 continue;
287 if (iter->name != INTERFACE_ELTNAME
288 && iter->name != ENUMERATION_ELTNAME
289 && iter->name != ERRORDOMAIN_ELTNAME
290 && iter->name != STRUCT_ELTNAME) {
291 DEBUG(@" not interface or enumeration or errordomain or struct; continuing");
292 continue;
295 string no_error_container_string = iter->get_ns_prop(NO_CONTAINER_ATTRNAME, FSO_NAMESPACE);
296 bool no_error_container = (no_error_container_string != null && no_error_container_string == "true");
298 string dbus_interface_name = iter->get_prop(NAME_ATTRNAME);
299 string[] split_name = dbus_interface_name.split(".");
300 string short_name;
301 int last_part;
302 if (iter->name == ERRORDOMAIN_ELTNAME && no_error_container) {
303 short_name = "Error";
304 last_part = split_name.length;
305 } else {
306 short_name = split_name[split_name.length - 1];
307 last_part = split_name.length - 1;
310 // Removing stripped root namespaces
311 int i = 0;
312 for (; i < last_part; i++) {
313 string part = split_name[i];
314 if (namespace_renaming.get(part) != "") break;
317 // Traversing inner namespaces
318 GeneratedNamespace ns = root_namespace;
319 for (; i < last_part; i++) {
320 string part = split_name[i];
322 if (namespace_renaming.has_key(part) && namespace_renaming.get(part) != "") {
323 part = namespace_renaming.get(part);
326 if (ns.members.has_key(part) && inner_interface_strategy_concat) {
327 if (ns.namespaces.has_key(part)) {
328 GeneratedNamespace child = ns.namespaces.get(part);
329 foreach (string interf_name in child.members.keys) {
330 Xml.Node* interf = child.members.get(interf_name);
331 ns.members.set(part + interf_name, interf);
333 ns.namespaces.unset(part);
334 child.parent = null;
337 break;
340 GeneratedNamespace child = null;
341 if (ns.namespaces.has_key(part)) {
342 child = ns.namespaces.get(part);
343 } else {
344 child = new GeneratedNamespace();
345 child.parent = ns;
346 child.name = part;
347 ns.namespaces.set(part, child);
350 if (ns.members.has_key(part)) {
351 child.members.set(part, ns.members.get(part));
352 ns.members.unset(part);
355 ns = child;
358 string interface_name = null;
359 if (inner_interface_strategy_concat) {
360 StringBuilder name_builder = new StringBuilder();
361 // Concatenating last inner namespaces
362 for (; i < last_part; i++) {
363 name_builder.append(split_name[i]);
365 name_builder.append(short_name);
366 interface_name = name_builder.str;
368 if (ns.namespaces.has_key(short_name)) {
369 GeneratedNamespace child = ns.namespaces.get(short_name);
370 foreach (string interf_name in child.members.keys) {
371 Xml.Node* interf = child.members.get(interf_name);
372 ns.members.set(short_name + interf_name, interf);
374 ns.namespaces.unset(short_name);
375 child.parent = null;
377 } else {
378 if (ns.namespaces.has_key(short_name)) {
379 ns = ns.namespaces.get(short_name);
381 interface_name = short_name;
384 if (!ns.members.has_key(interface_name)) {
385 ns.members.set(interface_name, iter);
386 } else {
387 Xml.Node* iter2 = ns.members.get(interface_name);
388 var name = iter2->get_prop(NAME_ATTRNAME);
389 ERROR(@"$interface_name has been added already as namespace $name");
394 private void index_names(GeneratedNamespace ns) {
395 if (ns.members.size > 0) {
396 string namespace_name = string.joinv(".", get_namespace_path(ns));
398 foreach (string name in ns.members.keys) {
399 Xml.Node* api = ns.members.get(name);
400 string dbus_name = api->get_prop(NAME_ATTRNAME);
401 if (api->name == ERRORDOMAIN_ELTNAME) {
402 INFO(@"Registering new errordomain $dbus_name");
403 error_name_index.set(dbus_name, namespace_name + "." + name);
404 } else {
405 name_index.set(dbus_name, namespace_name + "." + name);
410 foreach (string name in ns.namespaces.keys) {
411 GeneratedNamespace child = ns.namespaces.get(name);
413 index_names(child);
417 private string[] get_namespace_path(GeneratedNamespace ns) {
418 string[] reversed_namespace_names = new string[0];
419 GeneratedNamespace a_namespace = ns;
420 while (a_namespace.name != null) {
421 reversed_namespace_names += a_namespace.name;
422 a_namespace = a_namespace.parent;
425 string[] namespace_names = new string[0];
426 for (int i = reversed_namespace_names.length - 1; i >= 0; i--) {
427 namespace_names += reversed_namespace_names[i];
430 return namespace_names;
433 private GeneratedNamespace root_namespace = new GeneratedNamespace();
435 private Map<string, string> name_index = new HashMap<string, string>(str_hash, str_equal, str_equal);
436 private Map<string, string> error_name_index = new HashMap<string, string>(str_hash, str_equal, str_equal);
438 private void generate_namespace(GeneratedNamespace ns)
439 throws GeneratorError {
440 if (ns.members.size > 0) {
441 string[] namespace_names = get_namespace_path(ns);
443 create_binding_file(output_directory + "/" + string.joinv("-", namespace_names).down() + ".vala");
445 foreach (string name in namespace_names) {
446 output.printf("\n");
447 output.printf("%snamespace %s {\n", get_indent(), name);
448 update_indent(+1);
451 foreach (string name in ns.members.keys) {
452 Xml.Node* api = ns.members.get(name);
454 switch (api->name) {
455 case INTERFACE_ELTNAME:
456 generate_interface(name, api, Synchrony.AUTO);
457 generate_proxy_getter(api, name);
458 if ( synced )
459 generate_interface(name, api, Synchrony.FORCE_SYNC);
460 generate_proxy_getter(api, name, Synchrony.FORCE_SYNC);
461 break;
462 case ENUMERATION_ELTNAME:
463 generate_enumeration(name, api);
464 break;
465 case ERRORDOMAIN_ELTNAME:
466 generate_errordomain(name, api);
467 break;
468 case STRUCT_ELTNAME:
469 generate_explicit_struct(name, api);
470 break;
474 foreach (string name in namespace_names) {
475 update_indent(-1);
476 output.printf("%s}\n", get_indent());
479 output = null;
482 foreach (string name in ns.namespaces.keys) {
483 GeneratedNamespace child = ns.namespaces.get(name);
485 generate_namespace(child);
489 private Gee.Map<string, string> structs_to_generate
490 = new Gee.HashMap<string, string>(str_hash, str_equal, str_equal);
492 private void generate_interface(string interface_name, Xml.Node* node, Synchrony synchrony = Synchrony.AUTO)
493 throws GeneratorError {
494 string dbus_name = node->get_prop(NAME_ATTRNAME);
495 string namespace_name = get_namespace_name(interface_name);
497 assert( synchrony != Synchrony.FORCE_ASYNC ); // not supported yet, maybe never
499 var iface_name = ( synchrony == Synchrony.FORCE_SYNC ) ? interface_name + "Sync" : interface_name;
501 INFO(@"Generating interface $dbus_name");
503 output.printf("\n");
504 output.printf("%s[DBus (name = \"%s\", timeout = %u)]\n", get_indent(), dbus_name, dbus_timeout);
505 output.printf("%spublic interface %s : GLib.Object {\n", get_indent(), iface_name);
506 update_indent(+1);
508 generate_members(node, iface_name, get_namespace_name(dbus_name), synchrony);
510 update_indent(-1);
511 output.printf("%s}\n", get_indent());
513 if (structs_to_generate.size != 0) {
514 foreach (string name in structs_to_generate.keys) {
515 generate_struct(name, structs_to_generate.get(name), namespace_name);
517 structs_to_generate.clear();
521 private void generate_enumeration(string enumeration_name, Xml.Node* node) throws GeneratorError {
522 string dbus_name = node->get_prop(NAME_ATTRNAME);
523 string type = node->get_prop(TYPE_ATTRNAME);
524 bool string_enum = type == "s";
526 INFO(@"Generating enumeration $type for $dbus_name");
528 output.printf("\n");
529 output.printf("%s[DBus%s]\n", get_indent(), string_enum ? " (use_string_marshalling = true)" : "");
530 output.printf("%spublic enum %s {\n", get_indent(), enumeration_name);
531 update_indent(+1);
533 for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
534 if (iter->type != ElementType.ELEMENT_NODE)
535 continue;
537 switch (iter->name) {
538 case MEMBER_ELTNAME:
539 string member_name = normalized_to_upper_case(iter->get_prop(NAME_ATTRNAME));
540 string member_value = iter->get_prop(VALUE_ATTRNAME);
541 if (string_enum) {
542 output.printf("%s[DBus (value=\"%s\")]\n", get_indent(), member_value);
544 output.printf("%s%s%s%s\n", get_indent(), member_name, string_enum ? "" : " = %s".printf(member_value), iter->next == null ? "" : ",");
545 break;
549 update_indent(-1);
550 output.printf("%s}\n", get_indent());
553 private void generate_errordomain(string errordomain_name, Xml.Node* node)
554 throws GeneratorError {
555 string dbus_name = node->get_prop(NAME_ATTRNAME);
557 INFO(@"Generating errordomain $errordomain_name for $dbus_name");
559 output.printf("\n");
560 output.printf("%s[DBus (name = \"%s\")]\n", get_indent(), dbus_name);
561 output.printf("%spublic errordomain %s {\n", get_indent(), errordomain_name);
562 update_indent(+1);
564 for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
565 if (iter->type != ElementType.ELEMENT_NODE)
566 continue;
568 switch (iter->name) {
569 case ERROR_ELTNAME:
570 string dbus_error_name = iter->get_prop(NAME_ATTRNAME);
571 string error_name = camel_case_to_upper_case(dbus_error_name);
573 output.printf("%s[DBus (name = \"%s\")]\n", get_indent(), dbus_error_name);
574 output.printf("%s%s%s\n", get_indent(), error_name, iter->next == null ? "" : ",");
575 break;
579 update_indent(-1);
580 output.printf("%s}\n", get_indent());
583 private void generate_explicit_struct(string struct_name, Xml.Node* node)
584 throws GeneratorError {
585 string dbus_name = node->get_prop(NAME_ATTRNAME);
587 INFO(@"Generating explicit struct $struct_name for $dbus_name");
589 output.printf("\n");
590 output.printf("%spublic struct %s {\n", get_indent(), struct_name);
591 update_indent(+1);
593 string ctor_signature = "%spublic %s (".printf(get_indent(), struct_name);
594 string ctor_body = "";
596 for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
597 if (iter->type != ElementType.ELEMENT_NODE)
598 continue;
600 switch (iter->name) {
601 case FIELD_ELTNAME:
602 string field_name = transform_registered_name(iter->get_prop(NAME_ATTRNAME));
603 string field_type = "unknown";
604 try {
605 field_type = translate_type(iter->get_prop(TYPE_ATTRNAME),
606 iter->get_ns_prop(TYPE_ATTRNAME, FSO_NAMESPACE),
607 struct_name, get_namespace_name(dbus_name));
608 } catch (GeneratorError.UNKNOWN_DBUS_TYPE ex) {
609 ERROR(@"In struct $struct_name field $field_name : Unknown dbus type $(ex.message)");
612 output.printf("%spublic %s %s;\n", get_indent(), field_type, field_name);
613 ctor_signature += "%s %s, ".printf(field_type, field_name);
614 ctor_body += "%sthis.%s = %s;\n".printf(get_indent(+1), field_name, field_name);
615 break;
618 string constructor = "%s ) {\n%s%s}".printf( ctor_signature.substring( 0, ctor_signature.length-2 ), ctor_body, get_indent() );
620 output.printf("\n%s\n", constructor);
622 if (gdbus) {
623 INFO(@"Generating from_variant method for $struct_name");
624 output.printf("\n%spublic static %s from_variant (Variant v) {\n", get_indent(), struct_name);
625 update_indent(1);
626 output.printf("%sreturn v as %s;\n", get_indent(), struct_name);
627 update_indent(-1);
628 output.printf("%s}\n", get_indent());
630 update_indent(-1);
631 output.printf("%s}", get_indent());
634 private void generate_struct(string name, string content_signature, string dbus_namespace)
635 throws GeneratorError {
636 INFO(@"Generating struct $name w/ signature $content_signature in dbus namespace $dbus_namespace");
638 output.printf("\n");
639 output.printf("%spublic struct %s {\n", get_indent(), name);
640 update_indent(+1);
642 int attribute_number = 1;
643 string signature = content_signature;
644 string tail = null;
645 while (signature != "") {
646 string type = parse_type(signature, out tail, "", dbus_namespace);
647 output.printf("%spublic %s attr%d;\n", get_indent(), type, attribute_number);
648 attribute_number++;
649 signature = tail;
652 update_indent(-1);
653 output.printf("%s}\n", get_indent());
656 private void generate_members(Xml.Node* node, string interface_name, string dbus_namespace, Synchrony synchrony)
657 throws GeneratorError {
658 for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
659 if (iter->type != ElementType.ELEMENT_NODE)
660 continue;
662 switch (iter->name) {
663 case METHOD_ELTNAME:
664 generate_method(iter, interface_name, dbus_namespace, synchrony);
665 break;
666 case SIGNAL_ELTNAME:
667 generate_signal(iter, interface_name, dbus_namespace);
668 break;
669 case PROPERTY_ELTNAME:
670 generate_property(iter, interface_name, dbus_namespace);
671 break;
672 case ERROR_ELTNAME:
673 generate_error(iter, interface_name);
674 break;
679 private void generate_method(Xml.Node* node, string interface_name, string dbus_namespace, Synchrony synchrony)
680 throws GeneratorError {
682 string realname = node->get_prop(NAME_ATTRNAME);
683 string name = transform_registered_name(uncapitalize(node->get_prop(NAME_ATTRNAME)));
685 INFO(@" Generating method $name (originally $realname) for $interface_name");
687 int unknown_param_count = 0;
689 int out_param_count = get_out_parameter_count(node);
691 bool first_param = true;
692 bool first_error = true;
693 StringBuilder args_builder = new StringBuilder();
694 StringBuilder throws_builder = new StringBuilder();
695 string return_value_type = "void";
696 bool async_method = false;
697 bool noreply_method = false;
698 bool deprecated_method = false;
699 string deprecated_method_replaced_by = "";
701 for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
702 if (iter->type != ElementType.ELEMENT_NODE)
703 continue;
705 switch (iter->name) {
706 case ARG_ELTNAME:
707 string? param_name = transform_registered_name(iter->get_prop(NAME_ATTRNAME));
708 if(param_name == null || param_name == "") {
709 param_name = "param%i".printf(unknown_param_count);
710 unknown_param_count++;
712 string param_type = "unknown";
713 try {
714 param_type = translate_type(iter->get_prop(TYPE_ATTRNAME),
715 iter->get_ns_prop(TYPE_ATTRNAME, FSO_NAMESPACE),
716 get_struct_name(interface_name, param_name),
717 dbus_namespace);
718 } catch (GeneratorError.UNKNOWN_DBUS_TYPE ex) {
719 ERROR(@"In interface $interface_name method $name : Unknown dbus type $(ex.message)");
721 string? param_dir = iter->get_prop(DIRECTION_ATTRNAME);
723 switch (param_dir) {
724 case OUT_ATTRVALUE:
725 if (param_type == null) {
726 param_type = "void";
728 if (out_param_count != 1) {
729 if (!first_param) {
730 args_builder.append(", ");
733 args_builder.append("out ");
734 args_builder.append(param_type);
735 args_builder.append(" ");
736 args_builder.append(param_name);
737 first_param = false;
738 } else {
739 return_value_type = param_type;
741 break;
742 case IN_ATTRVALUE:
743 default:
744 if (!first_param) {
745 args_builder.append(", ");
748 args_builder.append(param_type);
749 args_builder.append(" ");
750 args_builder.append(param_name);
751 first_param = false;
752 break;
754 break;
755 case THROWS_ELTNAME:
756 string errordomain_name = null;
757 string fso_type = iter->get_prop(TYPE_ATTRNAME);
758 if (fso_type != null) {
759 errordomain_name = error_name_index.get(fso_type);
761 if (errordomain_name == null) {
762 ERROR(@"In interface $interface_name method $name : Unknown dbus error $(fso_type)");
763 errordomain_name = "<unknown>";
766 if (!first_error) {
767 throws_builder.append(", ");
769 throws_builder.append(errordomain_name);
770 first_error = false;
771 break;
772 case ANNOTATION_ELTNAME:
773 string annotation_name = iter->get_prop(NAME_ATTRNAME);
774 if (annotation_name == "org.freedesktop.DBus.GLib.Async") {
775 async_method = true;
777 if (annotation_name == "org.freedesktop.DBus.GLib.NoReply") {
778 noreply_method = true;
780 break;
781 case DEPRECATED_ELTNAME:
782 deprecated_method = true;
783 deprecated_method_replaced_by = iter->get_prop(REPLACED_BY_ATTRNAME);
784 break;
788 if (async_method && noreply_method) {
789 WARN(@"In interface $interface_name method $name : Requested both async and noreply; which is not supported by Vala. Will force sync.");
790 async_method = false;
793 if (noreply_method && out_param_count > 0) {
794 ERROR(@"In interface $interface_name method $name : noreply methods are not allowed to have out parameters!");
797 if (!first_error) {
798 throws_builder.append(", ");
800 throws_builder.append( gdbus ? "DBusError, IOError" : "DBus.Error");
802 switch ( synchrony )
804 case Synchrony.FORCE_SYNC:
805 async_method = false;
806 break;
807 case Synchrony.FORCE_ASYNC:
808 async_method = true;
809 break;
810 default:
811 /* AUTO, leave it like it is */
812 break;
815 output.printf("\n");
816 if (noreply_method) {
817 output.printf("%s[DBus (name = \"%s\", no_reply = true)]\n", get_indent(), realname);
818 } else {
819 output.printf("%s[DBus (name = \"%s\")]\n", get_indent(), realname);
821 if (deprecated_method) {
822 if (deprecated_method_replaced_by.length == 0)
823 output.printf("[Deprecated]\n");
824 else output.printf("[Deprecated (replacement = \"%s\")]".printf(deprecated_method_replaced_by));
826 output.printf("%spublic abstract%s %s %s(%s) throws %s;\n",
827 get_indent(), (async_method ? " async" : ""), return_value_type, name, args_builder.str, throws_builder.str);
830 private int get_out_parameter_count(Xml.Node* node) {
831 int out_param_count = 0;
832 for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
833 if (iter->type != ElementType.ELEMENT_NODE)
834 continue;
835 if (iter->name != ARG_ELTNAME)
836 continue;
837 if (iter->get_prop(DIRECTION_ATTRNAME) != OUT_ATTRVALUE)
838 continue;
840 out_param_count++;
842 return out_param_count;
845 private void generate_signal(Xml.Node* node, string interface_name, string dbus_namespace)
846 throws GeneratorError {
847 string realname = node->get_prop(NAME_ATTRNAME);
848 string name = transform_registered_name(uncapitalize(node->get_prop(NAME_ATTRNAME)));
850 INFO(@" Generating signal $name (originally $realname) for $interface_name");
852 int unknown_param_count = 0;
854 bool first_param = true;
855 StringBuilder args_builder = new StringBuilder();
856 for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
857 if (iter->type != ElementType.ELEMENT_NODE)
858 continue;
860 if (iter->name != ARG_ELTNAME)
861 continue;
863 string param_name = transform_registered_name(iter->get_prop(NAME_ATTRNAME));
864 if(param_name == null || param_name == "") {
865 param_name = "param%i".printf(unknown_param_count);
866 unknown_param_count++;
868 string param_type = "unknown";
869 try {
870 param_type = translate_type(iter->get_prop(TYPE_ATTRNAME),
871 iter->get_ns_prop(TYPE_ATTRNAME, FSO_NAMESPACE),
872 interface_name + capitalize(param_name),
873 dbus_namespace);
874 } catch (GeneratorError.UNKNOWN_DBUS_TYPE ex) {
875 ERROR(@"In interface $interface_name signal $name : Unknown dbus type $(ex.message)");
878 if (!first_param) {
879 args_builder.append(", ");
882 args_builder.append(param_type);
883 args_builder.append(" ");
884 args_builder.append(param_name);
885 first_param = false;
888 output.printf("\n");
889 output.printf("%s[DBus (name = \"%s\")]\n", get_indent(), realname);
890 output.printf("%spublic signal void %s(%s);\n",
891 get_indent(), name, args_builder.str);
894 private void generate_property(Xml.Node* node, string interface_name, string dbus_namespace)
896 string realname = node->get_prop(NAME_ATTRNAME);
897 string name = transform_registered_name(uncapitalize(node->get_prop(NAME_ATTRNAME)));
899 string typename = "unknown";
900 string rawtype = "";
901 try {
902 rawtype = node->get_prop(TYPE_ATTRNAME);
903 typename = translate_type(rawtype,
904 node->get_ns_prop(TYPE_ATTRNAME, FSO_NAMESPACE),
905 interface_name + capitalize(name),
906 dbus_namespace);
907 } catch (GeneratorError.UNKNOWN_DBUS_TYPE ex) {
908 ERROR(@"In interface $interface_name property $name : Unknown dbus type $(ex.message)");
911 string accesstype = "readwrite";
912 if (node->has_prop("access") != null) {
913 accesstype = node->get_prop("access");
914 if (accesstype != "readwrite" && accesstype != "readonly") {
915 ERROR(@"In interface $interface_name property $name : Unknown access type: $accesstype");
919 INFO(@" Generating property $name (originally $realname) of type $typename for $interface_name");
921 string owned_specifier = is_simple_type(rawtype) ? "" : "owned";
922 string accessimpl = (accesstype == "readonly") ? @"$owned_specifier get;" : @"$owned_specifier get; set;";
924 output.printf("\n");
925 output.printf("%s[DBus (name = \"%s\")]\n", get_indent(), realname);
926 output.printf("%spublic abstract %s %s { %s }\n", get_indent(), typename, name, accessimpl);
929 private void generate_error(Xml.Node* node, string interface_name)
930 throws GeneratorError {
933 private void generate_proxy_getter(Xml.Node* node, owned string interface_name, Synchrony synchrony = Synchrony.AUTO)
934 throws GeneratorError {
935 bool async_method = true;
936 switch ( synchrony )
938 case Synchrony.FORCE_SYNC:
939 async_method = false;
940 break;
941 case Synchrony.FORCE_ASYNC:
942 async_method = true;
943 break;
944 default:
945 /* AUTO, leave it like it is */
946 break;
948 if ( !async_method ) {
949 interface_name = interface_name + "Sync";
951 if (!gdbus )
953 output.printf(@"\n$(get_indent())public $(interface_name) get_$(uncapitalize(interface_name))_proxy(DBus.Connection con, string busname, DBus.ObjectPath path) {");
954 update_indent(+1);
955 output.printf(@"\n$(get_indent())return con.get_object(busname, path) as $(interface_name);");
956 update_indent(-1);
957 output.printf(@"\n$(get_indent())}");
961 private string translate_type(string type, string? fso_type, string type_name, string dbus_namespace)
962 throws GeneratorError {
963 string tail = null;
964 if (fso_type != null) {
965 var vala_type = name_index.get(fso_type);
966 if (vala_type == null) {
967 throw new GeneratorError.UNKNOWN_DBUS_TYPE(fso_type);
969 return vala_type + (type.has_prefix("a") ? "[]" : "");
971 return parse_type(type, out tail, type_name, dbus_namespace).replace("][", ",");
974 private bool is_simple_type(string type)
976 switch (type) {
977 case "b":
978 case "i":
979 case "n":
980 case "q":
981 case "t":
982 case "u":
983 case "x":
984 case "d":
985 return true;
987 return false;
990 private string parse_type(string type, out string tail, string type_name, string dbus_namespace)
991 throws GeneratorError {
992 tail = type.substring(1);
993 if (type.has_prefix("y")) {
994 return "uint8"; // uchar only works since post-vala 0.8.0 (see c4cf64b6590e5cce21febf98b1f3ff935d921fd5)
995 } else if (type.has_prefix("b")) {
996 return "bool";
997 } else if (type.has_prefix("n") || type.has_prefix("i")) {
998 return "int";
999 } else if (type.has_prefix("q") || type.has_prefix("u")) {
1000 return "uint";
1001 } else if (type.has_prefix("x")) {
1002 return "int64";
1003 } else if (type.has_prefix("t")) {
1004 return "uint64";
1005 } else if (type.has_prefix("d")) {
1006 return "double";
1007 } else if (type.has_prefix("s")) {
1008 return "string";
1009 } else if (type.has_prefix("o")) {
1010 return gdbus ? "GLib.ObjectPath" : "DBus.ObjectPath"; // needs to be prefixed post vala 0.9.2 (see 142ca8fe0e5b4b8058d4913e909ccc820b6f7768 and 9a650b7f3bb796c36e31a7c649c7f59e8292631e)
1011 } else if (type.has_prefix("v")) {
1012 return gdbus ? "GLib.Variant" : "GLib.Value";
1013 } else if (type.has_prefix("a{") && type.has_suffix("}")) {
1014 string tmp_type = get_subsignature(type, '{', '}', out tail);
1015 string tail2 = null;
1016 string tail3 = null;
1018 StringBuilder vala_type = new StringBuilder();
1019 vala_type.append("GLib.HashTable<");
1020 string foo = parse_type(tmp_type, out tail2, plural_to_singular(type_name) + "Key", dbus_namespace);
1021 vala_type.append(foo);
1022 vala_type.append(", ");
1024 string value_type = parse_type(tail2, out tail3, plural_to_singular(type_name), dbus_namespace);
1025 if (value_type == "GLib.Value") {
1026 value_type += "?";
1028 vala_type.append(value_type);
1029 vala_type.append(">");
1031 return vala_type.str;
1032 } else if (type.has_prefix("a")) {
1033 string tail2 = null;
1034 return parse_type(tail, out tail2, plural_to_singular(type_name), dbus_namespace) + "[]";
1035 } else if (type.has_prefix("(") && type.has_suffix(")")) {
1036 string sub_type = get_subsignature(type, '(', ')', out tail);
1037 int number = 2;
1038 string unique_type_name = type_name +"Struct";
1039 while (structs_to_generate.has_key(unique_type_name)) {
1040 unique_type_name = "%s%d".printf(unique_type_name, number++);
1043 if (!name_index.has_key(dbus_namespace + "." + unique_type_name)) {
1044 structs_to_generate.set(unique_type_name, sub_type);
1046 return unique_type_name;
1048 throw new GeneratorError.UNKNOWN_DBUS_TYPE(@"dbustype: '$type' unknown");
1051 private string get_struct_name(string interface_name, string param_name) {
1052 string striped_interface_name = strip_namespace(interface_name);
1053 string name = capitalize(param_name);
1054 return name.has_prefix(striped_interface_name) ? name : striped_interface_name + name;
1057 private string get_namespace_name(string interface_name) {
1058 long last_dot = interface_name.length - 1;
1059 while (last_dot >= 0 && interface_name[last_dot] != '.') {
1060 last_dot--;
1062 return interface_name.substring(0, last_dot);
1065 private string strip_namespace(string interface_name) {
1066 long last_dot = interface_name.length - 1;
1067 while (last_dot >= 0 && interface_name[last_dot] != '.') {
1068 last_dot--;
1070 return interface_name.substring(last_dot + 1, interface_name.length - last_dot - 1);
1073 private string capitalize(string type_name) {
1074 string[] parts = type_name.split("_");
1075 StringBuilder capitalized_name = new StringBuilder();
1076 foreach (string part in parts) {
1077 if (part != "") {
1078 capitalized_name.append(part.substring(0, 1).up());
1079 capitalized_name.append(part.substring(1, part.length - 1));
1082 return capitalized_name.str;
1085 private string uncapitalize(string name) {
1086 StringBuilder uncapitalized_name = new StringBuilder();
1087 for (int i = 0; i < name.length; i++) {
1088 unichar c = name[i];
1089 if (c.isupper()) {
1090 if (i > 0)
1091 uncapitalized_name.append_unichar('_');
1092 uncapitalized_name.append_unichar(c.tolower());
1093 } else {
1094 uncapitalized_name.append_unichar(c);
1097 return transform_registered_name(uncapitalized_name.str);
1100 private string normalized_to_upper_case(string name) {
1101 return name.replace("-", "_").up();
1104 private string camel_case_to_upper_case(string name) {
1105 return uncapitalize(name).up();
1108 private string transform_registered_name(string? name) {
1109 if (name != null && registered_names.contains(name)) {
1110 return name + "_";
1112 return name;
1115 private string plural_to_singular(string type_name) {
1116 if (type_name.has_suffix("ies"))
1117 return type_name.substring(0, type_name.length - 3) + "y";
1118 else if (type_name.has_suffix("ses"))
1119 return type_name.substring(0, type_name.length - 2);
1120 else if (type_name.has_suffix("us"))
1121 return type_name;
1122 else if (type_name.has_suffix("i"))
1123 return type_name.substring(0, type_name.length - 1) + "o";
1124 else if (type_name.has_suffix("s"))
1125 return type_name.substring(0, type_name.length - 1);
1126 else return type_name;
1129 private int indentSize = 0;
1131 private string get_indent(int offset = 0) {
1132 return string.nfill(indentSize + offset, '\t');
1135 private void update_indent(int increment) {
1136 indentSize += increment;
1139 private string get_subsignature( string s, char start, char end, out string tail ) {
1140 unowned char[] data = (char[])s;
1141 int iter = 0;
1142 int counter = 0;
1143 int begin = 0;
1144 char c;
1146 for(iter = 0; iter < s.length; iter++) {
1147 c = data[iter];
1148 if(c == start) {
1149 if( counter == 0 ) {
1150 begin = iter;
1152 counter ++;
1154 else if(c == end) {
1155 counter --;
1156 if(counter == 0) {
1157 break;
1161 tail = s.substring( iter + 1, -1 );
1162 var tmp = s.substring( begin + 1, iter - begin - 1);
1163 return tmp;