/* D-Bus Java Implementation Copyright (c) 2005-2006 Matthew Johnson This program is free software; you can redistribute it and/or modify it under the terms of either the GNU Lesser General Public License Version 2 or the Academic Free Licence Version 2.1. Full licence texts are included in the COPYING file with this program. */ package org.freedesktop.dbus.bin; import static org.freedesktop.dbus.Gettext._; import static org.freedesktop.dbus.bin.IdentifierMangler.mangle; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.io.Reader; import java.io.StringReader; import java.text.MessageFormat; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.Vector; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.freedesktop.DBus.Introspectable; import org.freedesktop.dbus.DBusConnection; import org.freedesktop.dbus.Marshalling; import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.exceptions.DBusExecutionException; import org.freedesktop.dbus.types.DBusStructType; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Converts a DBus XML file into Java interface definitions. */ public class CreateInterface { @SuppressWarnings("unchecked") private static String collapseType(Type t, Set<String> imports, Map<StructStruct, Type[]> structs, boolean container, boolean fullnames) throws DBusException { if (t instanceof ParameterizedType) { String s; Class<? extends Object> c = (Class<? extends Object>) ((ParameterizedType) t).getRawType(); if (null != structs && t instanceof DBusStructType) { int num = 1; String name = "Struct"; while (null != structs.get(new StructStruct(name+num))) num++; name = name+num; structs.put(new StructStruct(name), ((ParameterizedType) t).getActualTypeArguments()); return name; } if (null != imports) imports.add(c.getName()); if (fullnames) return c.getName(); else s = c.getSimpleName(); s += '<'; Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); for (Type st: ts) s += collapseType(st, imports, structs, true, fullnames)+','; s = s.replaceAll(",$", ">"); return s; } else if (t instanceof Class) { Class<? extends Object> c = (Class<? extends Object>) t; if (c.isArray()) { return collapseType(c.getComponentType(), imports, structs, container, fullnames)+"[]"; } else { Package p = c.getPackage(); if (null != imports && !"java.lang".equals(p.getName())) imports.add(c.getName()); if (container) { if (fullnames) return c.getName(); else return c.getSimpleName(); } else { try { Field f = c.getField("TYPE"); Class<? extends Object> d = (Class<? extends Object>) f.get(c); return d.getSimpleName(); } catch (Exception e) { return c.getSimpleName(); } } } } else return ""; } private static String getJavaType(String dbus, Set<String> imports, Map<StructStruct,Type[]> structs, boolean container, boolean fullnames) throws DBusException { if (null == dbus || "".equals(dbus)) return ""; Vector<Type> v = new Vector<Type>(); int c = Marshalling.getJavaType(dbus, v, 1); Type t = v.get(0); return collapseType(t, imports, structs, container, fullnames); } public String comment = ""; boolean builtin; public CreateInterface(PrintStreamFactory factory, boolean builtin) { this.factory = factory; this.builtin = builtin; } @SuppressWarnings("fallthrough") String parseReturns(Vector<Element> out, Set<String> imports, Map<String,Integer> tuples, Map<StructStruct, Type[]> structs) throws DBusException { String[] names = new String[] { "Pair", "Triplet", "Quad", "Quintuple", "Sextuple", "Septuple" }; String sig = ""; String name = null; switch (out.size()) { case 0: sig += "void "; break; case 1: sig += getJavaType(out.get(0).getAttribute("type"), imports, structs, false, false)+" "; break; case 2: case 3: case 4: case 5: case 6: case 7: name = names[out.size() - 2]; default: if (null == name) name = "NTuple"+out.size(); tuples.put(name, out.size()); sig += name + "<"; for (Element arg: out) sig += getJavaType(arg.getAttribute("type"), imports, structs, true, false)+", "; sig = sig.replaceAll(", $","> "); break; } return sig; } String parseMethod(Element meth, Set<String> imports, Map<String,Integer> tuples, Map<StructStruct, Type[]> structs, Set<String> exceptions, Set<String> anns) throws DBusException { Vector<Element> in = new Vector<Element>(); Vector<Element> out = new Vector<Element>(); if (null == meth.getAttribute("name") || "".equals(meth.getAttribute("name"))) { System.err.println(_("ERROR: Method name was blank, failed")); System.exit(1); } String annotations = ""; String throwses = null; for (Node a: new IterableNodeList(meth.getChildNodes())) { if (Node.ELEMENT_NODE != a.getNodeType()) continue; checkNode(a, "arg", "annotation"); if ("arg".equals(a.getNodeName())) { Element arg = (Element) a; // methods default to in if ("out".equals(arg.getAttribute("direction"))) out.add(arg); else in.add(arg); } else if ("annotation".equals(a.getNodeName())) { Element e = (Element) a; if (e.getAttribute("name").equals("org.freedesktop.DBus.Method.Error")) { if (null == throwses) throwses = e.getAttribute("value"); else throwses += ", " + e.getAttribute("value"); exceptions.add(e.getAttribute("value")); } else annotations += parseAnnotation(e, imports, anns); } } String sig = ""; comment = ""; sig += parseReturns(out, imports, tuples, structs); sig += mangle(meth.getAttribute("name"))+"("; char defaultname = 'a'; String params = ""; for (Element arg: in) { String type = getJavaType(arg.getAttribute("type"), imports, structs, false, false); String name = arg.getAttribute("name"); if (null == name || "".equals(name)) name = ""+(defaultname++); params += type+" "+mangle(name)+", "; } return ("".equals(comment) ? "" : " /**\n" + comment + " */\n") + annotations + " public " + sig + params.replaceAll("..$", "")+")"+ (null == throwses? "": " throws "+throwses)+";"; } String parseSignal(Element signal, Set<String> imports, Map<StructStruct, Type[]> structs, Set<String> anns) throws DBusException { Map<String, String> params = new HashMap<String, String>(); Vector<String> porder = new Vector<String>(); char defaultname = 'a'; imports.add("org.freedesktop.dbus.DBusSignal"); imports.add("org.freedesktop.dbus.exceptions.DBusException"); String annotations = ""; for (Node a: new IterableNodeList(signal.getChildNodes())) { if (Node.ELEMENT_NODE != a.getNodeType()) continue; checkNode(a, "arg", "annotation"); if ("annotation".equals(a.getNodeName())) annotations += parseAnnotation((Element) a, imports, anns); else { Element arg = (Element) a; String type = getJavaType(arg.getAttribute("type"), imports, structs, false, false); String name = arg.getAttribute("name"); if (null == name || "".equals(name)) name = ""+(defaultname++); params.put(mangle(name), type); porder.add(mangle(name)); } } String out = ""; out += annotations; out += " public static class "+signal.getAttribute("name"); out += " extends DBusSignal\n {\n"; for (String name: porder) out += " public final "+params.get(name)+" "+name+";\n"; out += " public "+signal.getAttribute("name")+"(String path"; for (String name: porder) out += ", "+params.get(name)+" "+name; out += ") throws DBusException\n {\n super(path"; for (String name: porder) out += ", "+name; out += ");\n"; for (String name: porder) out += " this."+name+" = "+name+";\n"; out += " }\n"; out += " }\n"; return out; } String parseAnnotation(Element ann, Set<String> imports, Set<String> annotations) { String s = " @"+ann.getAttribute("name").replaceAll(".*\\.([^.]*)$","$1")+"("; if (null != ann.getAttribute("value") && !"".equals(ann.getAttribute("value"))) s += '"'+ann.getAttribute("value")+'"'; imports.add(ann.getAttribute("name")); annotations.add(ann.getAttribute("name")); return s += ")\n"; } void parseInterface(Element iface, PrintStream out, Map<String,Integer> tuples, Map<StructStruct, Type[]> structs, Set<String> exceptions, Set<String> anns) throws DBusException { if (null == iface.getAttribute("name") || "".equals(iface.getAttribute("name"))) { System.err.println(_("ERROR: Interface name was blank, failed")); System.exit(1); } out.println("package "+iface.getAttribute("name").replaceAll("\\.[^.]*$","")+";"); String methods = ""; String signals = ""; String annotations = ""; Set<String> imports = new TreeSet<String>(); imports.add("org.freedesktop.dbus.DBusInterface"); for (Node meth: new IterableNodeList(iface.getChildNodes())) { if (Node.ELEMENT_NODE != meth.getNodeType()) continue; checkNode(meth, "method", "signal", "property", "annotation"); if ("method".equals(meth.getNodeName())) methods += parseMethod((Element) meth, imports, tuples, structs, exceptions, anns) + "\n"; else if ("signal".equals(meth.getNodeName())) signals += parseSignal((Element) meth, imports, structs, anns); else if ("property".equals(meth.getNodeName())) System.err.println("WARNING: Ignoring property"); else if ("annotation".equals(meth.getNodeName())) annotations += parseAnnotation((Element) meth, imports, anns); } if (imports.size() > 0) for (String i: imports) out.println("import "+i+";"); out.print(annotations); out.print("public interface "+iface.getAttribute("name").replaceAll("^.*\\.([^.]*)$","$1")); out.println(" extends DBusInterface"); out.println("{"); out.println(signals); out.println(methods); out.println("}"); } void createException(String name, String pack, PrintStream out) throws DBusException { out.println("package "+pack+";"); out.println("import org.freedesktop.dbus.DBusExecutionException;"); out.print("public class "+name); out.println(" extends DBusExecutionException"); out.println("{"); out.println(" public "+name+"(String message)"); out.println(" {"); out.println(" super(message);"); out.println(" }"); out.println("}"); } void createAnnotation(String name, String pack, PrintStream out) throws DBusException { out.println("package "+pack+";"); out.println("import java.lang.annotation.Retention;"); out.println("import java.lang.annotation.RetentionPolicy;"); out.println("@Retention(RetentionPolicy.RUNTIME)"); out.println("public @interface "+name); out.println("{"); out.println(" String value();"); out.println("}"); } void createStruct(String name, Type[] type, String pack, PrintStream out, Map<StructStruct, Type[]> existing) throws DBusException, IOException { out.println("package "+pack+";"); Set<String> imports = new TreeSet<String>(); imports.add("org.freedesktop.dbus.Position"); imports.add("org.freedesktop.dbus.Struct"); Map<StructStruct, Type[]> structs = new HashMap<StructStruct, Type[]>(existing); String[] types = new String[type.length]; for (int i = 0; i < type.length; i++) types[i] = collapseType(type[i], imports, structs, false, false); for (String im: imports) out.println("import "+im+";"); out.println("public final class "+name+" extends Struct"); out.println("{"); int i = 0; char c = 'a'; String params = ""; for (String t: types) { out.println(" @Position("+i++ +")"); out.println(" public final "+t+" "+c+";"); params += t+" "+c+", "; c++; } out.println(" public "+name+"("+params.replaceAll("..$", "")+")"); out.println(" {"); for (char d = 'a'; d < c; d++) out.println(" this."+d+" = "+d+";"); out.println(" }"); out.println("}"); structs = StructStruct.fillPackages(structs, pack); Map<StructStruct, Type[]> tocreate = new HashMap<StructStruct, Type[]>(structs); for (StructStruct ss: existing.keySet()) tocreate.remove(ss); createStructs(tocreate, structs); } void createTuple(String name, int num, String pack, PrintStream out) throws DBusException { out.println("package "+pack+";"); out.println("import org.freedesktop.dbus.Position;"); out.println("import org.freedesktop.dbus.Tuple;"); out.println("/** Just a typed container class */"); out.print("public final class "+name); String types = " <"; for (char v = 'A'; v < 'A'+num; v++) types += v + ","; out.print(types.replaceAll(",$","> ")); out.println("extends Tuple"); out.println("{"); char t = 'A'; char n = 'a'; for (int i = 0; i < num; i++,t++,n++) { out.println(" @Position("+i+")"); out.println(" public final "+t+" "+n+";"); } out.print(" public "+name+"("); String sig = ""; t = 'A'; n = 'a'; for (int i = 0; i < num; i++,t++,n++) sig += t+" "+n+", "; out.println(sig.replaceAll(", $", ")")); out.println(" {"); for (char v = 'a'; v < 'a'+num; v++) out.println(" this."+v+" = "+v+";"); out.println(" }"); out.println("}"); } void parseRoot(Element root) throws DBusException, IOException { Map<StructStruct, Type[]> structs = new HashMap<StructStruct, Type[]>(); Set<String> exceptions = new TreeSet<String>(); Set<String> annotations = new TreeSet<String>(); for (Node iface: new IterableNodeList(root.getChildNodes())) { if (Node.ELEMENT_NODE != iface.getNodeType()) continue; checkNode(iface, "interface", "node"); if ("interface".equals(iface.getNodeName())) { Map<String, Integer> tuples = new HashMap<String, Integer>(); String name = ((Element) iface).getAttribute("name"); String file = name.replaceAll("\\.","/")+".java"; String path = file.replaceAll("/[^/]*$", ""); String pack = name.replaceAll("\\.[^.]*$",""); // don't create interfaces in org.freedesktop.DBus by default if (pack.startsWith("org.freedesktop.DBus") && !builtin) continue; factory.init(file, path); parseInterface((Element) iface, factory.createPrintStream(file), tuples, structs, exceptions, annotations); structs = StructStruct.fillPackages(structs, pack); createTuples(tuples, pack); } else if ("node".equals(iface.getNodeName())) parseRoot((Element) iface); else { System.err.println(_("ERROR: Unknown node: ")+iface.getNodeName()); System.exit(1); } } createStructs(structs, structs); createExceptions(exceptions); createAnnotations(annotations); } private void createAnnotations(Set<String> annotations) throws DBusException, IOException { for (String fqn: annotations) { String name = fqn.replaceAll("^.*\\.([^.]*)$", "$1"); String pack = fqn.replaceAll("\\.[^.]*$",""); // don't create things in org.freedesktop.DBus by default if (pack.startsWith("org.freedesktop.DBus") && !builtin) continue; String path = pack.replaceAll("\\.", "/"); String file = name.replaceAll("\\.","/")+".java"; factory.init(file, path); createAnnotation(name, pack, factory.createPrintStream(path, name)); } } private void createExceptions(Set<String> exceptions) throws DBusException, IOException { for (String fqn: exceptions) { String name = fqn.replaceAll("^.*\\.([^.]*)$", "$1"); String pack = fqn.replaceAll("\\.[^.]*$",""); // don't create things in org.freedesktop.DBus by default if (pack.startsWith("org.freedesktop.DBus") && !builtin) continue; String path = pack.replaceAll("\\.", "/"); String file = name.replaceAll("\\.","/")+".java"; factory.init(file, path); createException(name, pack, factory.createPrintStream(path, name)); } } private void createStructs(Map<StructStruct, Type[]> structs, Map<StructStruct, Type[]> existing) throws DBusException, IOException { for (StructStruct ss: structs.keySet()) { String file = ss.name.replaceAll("\\.","/")+".java"; String path = ss.pack.replaceAll("\\.", "/"); factory.init(file, path); createStruct(ss.name, structs.get(ss), ss.pack, factory.createPrintStream(path, ss.name), existing); } } private void createTuples(Map<String, Integer> typeMap, String pack) throws DBusException, IOException { for (String tname: typeMap.keySet()) createTuple(tname, typeMap.get(tname), pack, factory.createPrintStream(pack.replaceAll("\\.","/"), tname)); } public static abstract class PrintStreamFactory { public abstract void init(String file, String path); /** * @param path * @param tname * @return PrintStream * @throws IOException */ public PrintStream createPrintStream(String path, String tname) throws IOException { final String file = path+"/"+tname+".java"; return createPrintStream(file); } /** * @param file * @return PrintStream * @throws IOException */ public abstract PrintStream createPrintStream(final String file) throws IOException; } static class ConsoleStreamFactory extends PrintStreamFactory { @Override public void init(String file, String path) { } @Override public PrintStream createPrintStream(String file) throws IOException { System.out.println("/* File: "+file+" */"); return System.out; } public PrintStream createPrintStream(String path, String tname) throws IOException { return super.createPrintStream(path, tname); } } static class FileStreamFactory extends PrintStreamFactory { public void init(String file, String path) { new File(path).mkdirs(); } /** * @param file * @return * @throws IOException */ public PrintStream createPrintStream(final String file) throws IOException { return new PrintStream(new FileOutputStream(file)); } } static void checkNode(Node n, String... names) { String expected = ""; for (String name: names) { if (name.equals(n.getNodeName())) return; expected += name + " or "; } System.err.println(MessageFormat.format(_("ERROR: Expected {0}, got {1}, failed."), new Object[] { expected.replaceAll("....$", ""), n.getNodeName() })); System.exit(1); } private final PrintStreamFactory factory; static class Config { int bus = DBusConnection.SESSION; String busname = null; String object = null; File datafile = null; boolean printtree = false; boolean fileout = false; boolean builtin = false; } static void printSyntax() { printSyntax(System.err); } static void printSyntax(PrintStream o) { o.println("Syntax: CreateInterface <options> [file | busname object]"); o.println(" Options: --no-ignore-builtin --system -y --session -s --create-files -f --help -h --version -v"); } public static void version() { System.out.println("Java D-Bus Version "+System.getProperty("Version")); System.exit(1); } static Config parseParams(String[] args) { Config config = new Config(); for (String p: args) { if ("--system".equals(p) || "-y".equals(p)) config.bus = DBusConnection.SYSTEM; else if ("--session".equals(p) || "-s".equals(p)) config.bus = DBusConnection.SESSION; else if ("--no-ignore-builtin".equals(p)) config.builtin = true; else if ("--create-files".equals(p) || "-f".equals(p)) config.fileout = true; else if ("--print-tree".equals(p) || "-p".equals(p)) config.printtree = true; else if ("--help".equals(p) || "-h".equals(p)) { printSyntax(System.out); System.exit(0); } else if ("--version".equals(p) || "-v".equals(p)) { version(); System.exit(0); } else if (p.startsWith("-")) { System.err.println(_("ERROR: Unknown option: ")+p); printSyntax(); System.exit(1); } else { if (null == config.busname) config.busname = p; else if (null == config.object) config.object = p; else { printSyntax(); System.exit(1); } } } if (null == config.busname) { printSyntax(); System.exit(1); } else if (null == config.object) { config.datafile = new File(config.busname); config.busname = null; } return config; } public static void main(String[] args) throws Exception { Config config = parseParams(args); Reader introspectdata = null; if (null != config.busname) try { DBusConnection conn = DBusConnection.getConnection(config.bus); Introspectable in = conn.getRemoteObject(config.busname, config.object, Introspectable.class); String id = in.Introspect(); if (null == id) { System.err.println(_("ERROR: Failed to get introspection data")); System.exit(1); } introspectdata = new StringReader(id); conn.disconnect(); } catch (DBusException DBe) { System.err.println(_("ERROR: Failure in DBus Communications: ")+DBe.getMessage()); System.exit(1); } catch (DBusExecutionException DEe) { System.err.println(_("ERROR: Failure in DBus Communications: ")+DEe.getMessage()); System.exit(1); } else if (null != config.datafile) try { introspectdata = new InputStreamReader(new FileInputStream(config.datafile)); } catch (FileNotFoundException FNFe) { System.err.println(_("ERROR: Could not find introspection file: ")+FNFe.getMessage()); System.exit(1); } try { PrintStreamFactory factory = config.fileout ? new FileStreamFactory() : new ConsoleStreamFactory(); CreateInterface createInterface = new CreateInterface(factory, config.builtin); createInterface.createInterface(introspectdata); } catch (DBusException DBe) { System.err.println("ERROR: "+DBe.getMessage()); System.exit(1); } } /** Output the interface for the supplied xml reader * @param introspectdata The introspect data reader * @throws ParserConfigurationException If the xml parser could not be configured * @throws SAXException If a problem occurs reading the xml data * @throws IOException If an IO error occurs * @throws DBusException If the dbus related error occurs */ public void createInterface(Reader introspectdata) throws ParserConfigurationException, SAXException, IOException, DBusException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(new InputSource(introspectdata)); Element root = document.getDocumentElement(); checkNode(root, "node"); parseRoot(root); } }