/*
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);
}
}