/*
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;
import static org.freedesktop.dbus.Gettext._;
import java.lang.reflect.Proxy;
import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.Random;
import java.util.Vector;
import org.freedesktop.DBus;
import org.freedesktop.dbus.exceptions.DBusException;
import cx.ath.matthew.debug.Debug;
/** Handles a peer to peer connection between two applications withou a bus daemon.
* <p>
* Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues.
* </p>
*/
public class DirectConnection extends AbstractConnection
{
/**
* Create a direct connection to another application.
* @param address The address to connect to. This is a standard D-Bus address, except that the additional parameter 'listen=true' should be added in the application which is creating the socket.
*/
public DirectConnection(String address) throws DBusException
{
super(address);
try {
transport = new Transport(addr, AbstractConnection.TIMEOUT);
} catch (IOException IOe) {
if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe);
throw new DBusException(_("Failed to connect to bus ")+IOe.getMessage());
} catch (ParseException Pe) {
if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Pe);
throw new DBusException(_("Failed to connect to bus ")+Pe.getMessage());
}
listen();
}
/**
* Creates a bus address for a randomly generated tcp port.
* @return a random bus address.
*/
public static String createDynamicTCPSession()
{
String address = "tcp:host=localhost";
int port;
try {
ServerSocket s = new ServerSocket();
s.bind(null);
port = s.getLocalPort();
s.close();
} catch (Exception e) {
Random r = new Random();
port = 32768 + (Math.abs(r.nextInt()) % 28232);
}
address += ",port="+port;
address += ",guid="+Transport.genGUID();
if (Debug.debug) Debug.print("Created Session address: "+address);
return address;
}
/**
* Creates a bus address for a randomly generated abstract unix socket.
* @return a random bus address.
*/
public static String createDynamicSession()
{
String address = "unix:";
String path = "/tmp/dbus-XXXXXXXXXX";
Random r = new Random();
do {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < 10; i++)
sb.append((char) ((Math.abs(r.nextInt()) % 26) + 65));
path = path.replaceAll("..........$", sb.toString());
if (Debug.debug) Debug.print(Debug.VERBOSE, "Trying path "+path);
} while ((new File(path)).exists());
address += "abstract="+path;
address += ",guid="+Transport.genGUID();
if (Debug.debug) Debug.print("Created Session address: "+address);
return address;
}
DBusInterface dynamicProxy(String path) throws DBusException
{
try {
DBus.Introspectable intro = (DBus.Introspectable) getRemoteObject(path, DBus.Introspectable.class);
String data = intro.Introspect();
String[] tags = data.split("[<>]");
Vector<String> ifaces = new Vector<String>();
for (String tag: tags) {
if (tag.startsWith("interface")) {
ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1"));
}
}
Vector<Class<? extends Object>> ifcs = new Vector<Class<? extends Object>>();
for(String iface: ifaces) {
int j = 0;
while (j >= 0) {
try {
ifcs.add(Class.forName(iface));
break;
} catch (Exception e) {}
j = iface.lastIndexOf(".");
char[] cs = iface.toCharArray();
if (j >= 0) {
cs[j] = '$';
iface = String.valueOf(cs);
}
}
}
if (ifcs.size() == 0) throw new DBusException(_("Could not find an interface to cast to"));
RemoteObject ro = new RemoteObject(null, path, null, false);
DBusInterface newi = (DBusInterface)
Proxy.newProxyInstance(ifcs.get(0).getClassLoader(),
ifcs.toArray(new Class[0]),
new RemoteInvocationHandler(this, ro));
importedObjects.put(newi, ro);
return newi;
} catch (Exception e) {
if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e);
throw new DBusException(MessageFormat.format(_("Failed to create proxy object for {0}; reason: {1}."), new Object[] { path, e.getMessage()}));
}
}
DBusInterface getExportedObject(String path) throws DBusException
{
ExportedObject o = null;
synchronized (exportedObjects) {
o = exportedObjects.get(path);
}
if (null != o && null == o.object.get()) {
unExportObject(path);
o = null;
}
if (null != o) return o.object.get();
return dynamicProxy(path);
}
/**
* Return a reference to a remote object.
* This method will always refer to the well known name (if given) rather than resolving it to a unique bus name.
* In particular this means that if a process providing the well known name disappears and is taken over by another process
* proxy objects gained by this method will make calls on the new proccess.
*
* This method will use bus introspection to determine the interfaces on a remote object and so
* <b>may block</b> and <b>may fail</b>. The resulting proxy object will, however, be castable
* to any interface it implements. It will also autostart the process if applicable. Also note
* that the resulting proxy may fail to execute the correct method with overloaded methods
* and that complex types may fail in interesting ways. Basically, if something odd happens,
* try specifying the interface explicitly.
*
* @param objectpath The path on which the process is exporting the object.
* @return A reference to a remote object.
* @throws ClassCastException If type is not a sub-type of DBusInterface
* @throws DBusException If busname or objectpath are incorrectly formatted.
*/
public DBusInterface getRemoteObject(String objectpath) throws DBusException
{
if (null == objectpath) throw new DBusException(_("Invalid object path: null"));
if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH)
throw new DBusException(_("Invalid object path: ")+objectpath);
return dynamicProxy(objectpath);
}
/**
* Return a reference to a remote object.
* This method will always refer to the well known name (if given) rather than resolving it to a unique bus name.
* In particular this means that if a process providing the well known name disappears and is taken over by another process
* proxy objects gained by this method will make calls on the new proccess.
* @param objectpath The path on which the process is exporting the object.
* @param type The interface they are exporting it on. This type must have the same full class name and exposed method signatures
* as the interface the remote object is exporting.
* @return A reference to a remote object.
* @throws ClassCastException If type is not a sub-type of DBusInterface
* @throws DBusException If busname or objectpath are incorrectly formatted or type is not in a package.
*/
public DBusInterface getRemoteObject(String objectpath, Class<? extends DBusInterface> type) throws DBusException
{
if (null == objectpath) throw new DBusException(_("Invalid object path: null"));
if (null == type) throw new ClassCastException(_("Not A DBus Interface"));
if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH)
throw new DBusException(_("Invalid object path: ")+objectpath);
if (!DBusInterface.class.isAssignableFrom(type)) throw new ClassCastException(_("Not A DBus Interface"));
// don't let people import things which don't have a
// valid D-Bus interface name
if (type.getName().equals(type.getSimpleName()))
throw new DBusException(_("DBusInterfaces cannot be declared outside a package"));
RemoteObject ro = new RemoteObject(null, objectpath, type, false);
DBusInterface i = (DBusInterface) Proxy.newProxyInstance(type.getClassLoader(),
new Class[] { type }, new RemoteInvocationHandler(this, ro));
importedObjects.put(i, ro);
return i;
}
protected <T extends DBusSignal> void removeSigHandler(DBusMatchRule rule, DBusSigHandler<T> handler) throws DBusException
{
SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource());
synchronized (handledSignals) {
Vector<DBusSigHandler<? extends DBusSignal>> v = handledSignals.get(key);
if (null != v) {
v.remove(handler);
if (0 == v.size()) {
handledSignals.remove(key);
}
}
}
}
protected <T extends DBusSignal> void addSigHandler(DBusMatchRule rule, DBusSigHandler<T> handler) throws DBusException
{
SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource());
synchronized (handledSignals) {
Vector<DBusSigHandler<? extends DBusSignal>> v = handledSignals.get(key);
if (null == v) {
v = new Vector<DBusSigHandler<? extends DBusSignal>>();
v.add(handler);
handledSignals.put(key, v);
} else
v.add(handler);
}
}
DBusInterface getExportedObject(String source, String path) throws DBusException
{
return getExportedObject(path);
}
}