diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 43176688..3c8749cb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,9 +14,6 @@ android { versionName = "0.29" ndk.abiFilters.clear() ndk.abiFilters.add("arm64-v8a") - ndk.abiFilters.add("armeabi-v7a") - ndk.abiFilters.add("x86") - ndk.abiFilters.add("x86_64") externalNativeBuild { cmake { cppFlags += "" @@ -42,7 +39,7 @@ android { srcDir("src/main/lib/powerampapi/poweramp_api_lib/res/") } jniLibs { - srcDir("src/main/cpp/lib") + srcDir("/work/android-root/lib") } } } @@ -82,4 +79,5 @@ dependencies { implementation("org.osmdroid:osmdroid-android:6.1.16") implementation("no.nordicsemi.android.support.v18:scanner:1.6.0") implementation("no.nordicsemi.android:ble:2.7.2") + implementation("com.google.guava:guava:33.1.0-android") } diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 01bde151..705b0bc6 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -14,6 +14,17 @@ project("sync") add_subdirectory(libslirp) +find_program(BASH bash) + +set(ENV{ANDROID_NDK_HOME} ${CMAKE_ANDROID_NDK}) +set(ENV{ABI} ${CMAKE_ANDROID_ARCH_ABI}) +execute_process(COMMAND ${BASH} ${CMAKE_CURRENT_SOURCE_DIR}/build_depends.sh) + +link_directories(/tmp/android-root/lib/${CMAKE_ANDROID_ARCH_ABI}) +include_directories(/tmp/android-root/include) + +set(ENV{PKG_CONFIG_PATH} /tmp/android-root/lib/${CMAKE_ANDROID_ARCH_ABI}/pkgconfig) + # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. diff --git a/app/src/main/cpp/build_depends.sh b/app/src/main/cpp/build_depends.sh new file mode 100755 index 00000000..2ddd3e14 --- /dev/null +++ b/app/src/main/cpp/build_depends.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# +# AsteroidOSSync +# Copyright (c) 2024 AsteroidOS +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +set -Eeo pipefail +ANDROID_NDK_HOME=${ANDROID_NDK_HOME:?please supply a valid \$ANDROID_SDK_HOME} +ABI=${ABI:?please supply a valid android \$ABI} +case "${ABI}" in + arm64-v8a) + LINUX_ABI=aarch64 + ;; + armeabi-v7a) + LINUX_ABI=arm + ;; + x86_64) + LINUX_ABI=x86_64 + ;; + x86) + LINUX_ABI=i686-pc + ;; + *) + exit 1 + ;; +esac +SYSROOT=${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot + +>&2 echo "Android NDK in ${ANDROID_NDK_HOME}" +export PATH=$PATH:${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin + +PREFIX=${PREFIX:-/tmp/android-root/} + +GLIB_VERSION=${GLIB_VERSION:-2.80.0} +GLIB_URL=https://download.gnome.org/sources/glib/${GLIB_VERSION%.*}/glib-${GLIB_VERSION}.tar.xz +GLIB_CACHE=${XDG_CACHE_DIR:-/tmp}/glib-${GLIB_VERSION}.tar.xz + +LIBICONV_VERSION=${LIBICONV_VERSION:-1.17} +LIBICONV_URL=https://ftp.gnu.org/pub/gnu/libiconv/libiconv-${LIBICONV_VERSION}.tar.gz +LIBICONV_CACHE=${XDG_CACHE_DIR:-/tmp}/libiconv-${LIBICONV_VERSION}.tar.gz +export CFLAGS=--sysroot="${SYSROOT}" +export CPPFLAGS=--sysroot="${SYSROOT}" +export CC=${LINUX_ABI}-linux-android21-clang +export CXX=${LINUX_ABI}-linux-android21-clang++ +export AR=llvm-ar +export RANLIB=llvm-ranlib + +pushd "$(mktemp -d)" + + [[ ! -f "${LIBICONV_CACHE}" ]] \ + && wget -O "${LIBICONV_CACHE}" "${LIBICONV_URL}" + bsdtar --strip-components=1 -xf "${LIBICONV_CACHE}" + + mkdir -p build + pushd build + + ../configure --host=${LINUX_ABI}-linux-android --with-sysroot="${SYSROOT}" --prefix="${PREFIX}" --libdir="${PREFIX}/lib/${ABI}" + make -j14 + make install + + popd # build + +popd + +pushd "$(mktemp -d)" + + [[ ! -f "${GLIB_CACHE}" ]] \ + && wget -O "${GLIB_CACHE}" "${GLIB_URL}" + bsdtar --strip-components=1 -xf "${GLIB_CACHE}" + + >&2 echo "Will build GLib" + + _CROSS_FILE=$(mktemp) + >&2 echo "Will setup cross" + cat <"${_CROSS_FILE}" +[built-in options] +c_args = ['-I${PREFIX}/include'] +c_link_args = ['-L${PREFIX}/lib/${ABI}'] + +[constants] +arch = '${LINUX_ABI}-linux-android' + +[binaries] +ar = 'llvm-ar' +c = '${LINUX_ABI}-linux-android21-clang' +as = [c] +cpp = '${LINUX_ABI}-linux-android21-clang++' +ranlib = 'llvm-ranlib' +strip = 'llvm-strip' +pkgconfig = '/usr/bin/pkg-config' +cmake = '/usr/bin/cmake' + +[properties] +sys_root = '${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/sysroot' +pkg_config_libdir = '${PREFIX}/lib/${ABI}/pkgconfig' + +[host_machine] +system = 'android' +cpu_family = '${LINUX_ABI}' +cpu = '${LINUX_ABI}' +endian = 'little' +EOF. + + patch <&2 echo "Will configure in ${PWD}/_builddir/" + >&2 meson setup ./_builddir/ ./ --cross-file="${_CROSS_FILE}" --prefix="${PREFIX}" --libdir="lib/${ABI}" + >&2 echo "Will build" + >&2 ninja -C ./_builddir/ + >&2 echo "Will install" + >&2 ninja -C ./_builddir/ install + >&2 echo "All depends ready" + +popd \ No newline at end of file diff --git a/app/src/main/cpp/sync.cpp b/app/src/main/cpp/sync.cpp index e0d3bb53..840cf4b6 100644 --- a/app/src/main/cpp/sync.cpp +++ b/app/src/main/cpp/sync.cpp @@ -1,5 +1,6 @@ #include #include +//#include #include "libslirp/src/libvdeslirp.h" @@ -46,14 +47,14 @@ JNIEXPORT void JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_finali env->SetLongField(thisObject, fid, 0L); } -JNIEXPORT long JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeRecv +JNIEXPORT jlong JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeRecv (JNIEnv* env, jobject thisObject, jobject dbb, jlong offset, jlong count) { void *buf = reinterpret_cast(env->GetDirectBufferAddress(dbb)) + offset; return vdeslirp_recv(GET_MYSLIRP(env, thisObject), buf, count); } -JNIEXPORT long JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeSend +JNIEXPORT jlong JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeSend (JNIEnv* env, jobject thisObject, jobject dbb, jlong offset, jlong count) { void *buf = reinterpret_cast(env->GetDirectBufferAddress(dbb)) + offset; @@ -75,4 +76,31 @@ JNIEXPORT jobject JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_get return ret; } +JNIEXPORT jint JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeAddUnixFwd + (JNIEnv* env, jobject thisObject, jstring path, jstring ip, jint port) { + const char *c_path = env->GetStringUTFChars(path, nullptr); + const char *c_ip = env->GetStringUTFChars(ip, nullptr); + + struct in_addr addr{ inet_addr(c_ip) }; + int rv = vdeslirp_add_unixfwd(GET_MYSLIRP(env, thisObject), const_cast(c_path), &addr, port); + + env->ReleaseStringUTFChars(path, c_path); + env->ReleaseStringUTFChars(ip, c_ip); + + return rv; +} + +JNIEXPORT jint JNICALL Java_org_asteroidos_sync_connectivity_SlirpService_vdeAddFwd + (JNIEnv* env, jobject thisObject, jboolean udp, jstring hostip, jint hostport, jstring ip, jint port) { + const char *c_hostip = env->GetStringUTFChars(hostip, nullptr); + const char *c_ip = env->GetStringUTFChars(ip, nullptr); + + int rv = vdeslirp_add_fwd(GET_MYSLIRP(env, thisObject), udp, (struct in_addr){inet_addr(c_hostip) }, hostport, (struct in_addr){inet_addr(c_ip) }, port); + + env->ReleaseStringUTFChars(hostip, c_hostip); + env->ReleaseStringUTFChars(ip, c_ip); + + return rv; +} + } diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGI.java b/app/src/main/java/cx/ath/matthew/cgi/CGI.java new file mode 100644 index 00000000..b63fffa8 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGI.java @@ -0,0 +1,565 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.Vector; +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +/** + * This is the main class you have to extend with your CGI program. + * You should implement the cgi() method. + * + * @author Matthew Johnson <src@matthew.ath.cx> + */ +abstract public class CGI +{ + private CGIErrorHandler errorhandler = new DefaultErrorHandler(); + private boolean headers_sent = false; + private HashMap headers = new HashMap(); + private Vector cookies = new Vector(); + private LinkedList pagedata = new LinkedList(); + private LinkedList rawdata = new LinkedList(); + + private native String getenv(String var); + /** MUST pass String.class and ALWAYS returns a String[] */ + private native Object[] getfullenv(Class c); + private native void setenv(String var, String value); + { + System.loadLibrary("cgi-java"); + } + + /** + * Called by CGIs to send a header to the output + * + * @param variable The header variable to set. + * @param value The value of the variable. + * + * @throws CGIHeaderSentException if the headers have already been sent. + * + * @see #flush + */ + public final void header(String variable, String value) throws CGIHeaderSentException + { + // only send headers once + if (headers_sent) throw new CGIHeaderSentException(); + + // buffer the variable (Map so that each header is only set once) + headers.put(variable.toLowerCase(), value); + } + + /** + * Sets a Cookie in the web browser, with extended attributes. + * Calls header() so must be called before sending any output. + * + * A parameter will not be sent if it is null. + * + * @param variable The cookie variable to set. + * @param value The value of the variable. + * @param path The path that the cookie will be returned for. + * @param domain The domain that the cookie will be returned for. + * @param expires The expiry date of the cookie. + * @param secure Will only send the cookie over HTTPS if this is true. + * + * @throws CGIHeaderSentException if the headers have already been sent. + * + * @see #flush + * @see #header + */ + public final void setcookie(String variable, String value, String path, String domain, Date expires, boolean secure) throws CGIHeaderSentException + { + if (headers_sent) throw new CGIHeaderSentException(); + + //Set-Cookie: NAME=VALUE; expires=DATE; + //path=PATH; domain=DOMAIN_NAME; secure + //Wdy, DD-Mon-YYYY HH:MM:SS GMT + DateFormat df = new SimpleDateFormat("E, dd-MMM-yyyy HH:mm:ss zzz"); + String cookie = variable+"="+value; + if (null != path) cookie += "; path="+path; + if (null != domain) cookie += "; domain="+domain; + if (null != expires) cookie += "; expires="+df.format(expires); + if (secure) cookie += "; secure"; + cookies.add("Set-Cookie: "+ cookie); + } + + /** + * Sets a Cookie in the web browser. + * Calls header() so must be called before sending any output. + * + * @param variable The cookie variable to set. + * @param value The value of the variable. + * + * @throws CGIHeaderSentException if the headers have already been sent. + * + * @see #flush + * @see #header + */ + public final void setcookie(String variable, String value) throws CGIHeaderSentException + { + if (headers_sent) throw new CGIHeaderSentException(); + + //Set-Cookie: NAME=VALUE; expires=DATE; + //path=PATH; domain=DOMAIN_NAME; secure + cookies.add("Set-Cookie: "+ variable+"="+value); + } + + /** + * Called by CGIs to send byte data to the output. + * The data is buffered until the CGI exits, or a call of flush. + * + * @param data The page data. + * @throws CGIInvalidContentFormatException if text data has already been sent. + * + * @see #flush + */ + public final void out(byte[] data) throws CGIInvalidContentFormatException + { + if (pagedata.size() > 0) throw new CGIInvalidContentFormatException(); + rawdata.add(data); + } + + /** + * Called by CGIs to send a string to the output. + * The data is buffered until the CGI exits, or a call of flush. + * + * @param data The page data. + * @throws CGIInvalidContentFormatException if raw data has already been sent. + * + * @see #flush + */ + public final void out(String data) throws CGIInvalidContentFormatException + { + if (rawdata.size() > 0) throw new CGIInvalidContentFormatException(); + pagedata.add(data); + } + + /** + * This will return an OutputStream that you can write data + * directly to. Calling this method will cause the output to be + * flushed and the Headers sent. At the moment this is not buffered + * and will be sent directly to the client. Subsequent calls + * to out() will appear after data written to the output stream. + * + * @see #out + * @return an OutputStream + */ + public final OutputStream getOutputStream() throws IOException + { + flush(); + return System.out; + } + + /** + * Flushes the output. + * Note that you cannot send a header after a flush. + * If you want to send both text and binary data in a page + * you may do so either side of a flush. + * + * @see #header + */ + public final void flush() throws IOException + { + if (!headers_sent) { + // don't send headers again + headers_sent = true; + // send headers + Iterator i = headers.keySet().iterator(); + while (i.hasNext()) { + String key = (String) i.next(); + String value = (String) headers.get(key); + System.out.println(key + ": " + value); + } + // send cookies + i = cookies.iterator(); + while (i.hasNext()) { + System.out.println((String) i.next()); + } + System.out.println(); + } + + // send data + if (pagedata.size() >0) { + Iterator j = pagedata.iterator(); + while (j.hasNext()) { + System.out.println((String) j.next()); + } + pagedata.clear(); + } else if (rawdata.size() > 0) { + Iterator j = rawdata.iterator(); + while (j.hasNext()) { + System.out.write((byte[]) j.next()); + } + pagedata.clear(); + } + System.out.flush(); + } + + /** + * Sets a custom exception handler. + * Gets called when an exception is thrown. + * The default error handler prints the error nicely in HTML + * and then exits gracefully. + * + * @param handler The new exception handler + */ + protected final void setErrorHandler(CGIErrorHandler handler) + { + errorhandler = handler; + } + + /** + * Override this method in your CGI program. + * + * @param POST A Map of variable =$gt; value for the POST variables. + * @param GET A Map of variable =$gt; value for the GET variables. + * @param ENV A Map of variable =$gt; value for the Webserver environment variables. + * @param COOKIES A Map of variable =$gt; value for the browser-sent cookies. + * @param params An array of parameters passed to the CGI (GET with no variable assignments) + * + * @throws Exception You can throw anything, it will be caught by the error handler. + */ + abstract protected void cgi(Map POST, Map GET, Map ENV, Map COOKIES, String[] params) throws Exception; + + /** + * Reads variables from a String like a=b&c=d to a Map {a => b, c => d}. + * + * @param s String to read from. + * @param seperator seperator character between variables (eg &) + * @param values whether or not this string has values for variables + * + * @return a Map with values, a Vector without + */ + private Object readVariables(String s, char seperator, boolean values) + { + HashMap vars = new HashMap(); + Vector varv = new Vector(); + String temp = ""; + String variable = null; + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == seperator) { // new variable + if (null != temp) temp = temp.trim(); + if (values) { + if (variable == null) {variable = temp; temp = "";} + else variable.trim(); + if (!variable.equals("")) { + Object o = vars.get(variable); + if (o == null) + vars.put(variable.trim(), temp); + else if (o instanceof String) { + LinkedList l = new LinkedList(); + l.add(o); + l.add(temp); + vars.put(variable.trim(), l); + } else if (o instanceof LinkedList) + ((LinkedList) o).add(temp); + } + temp = ""; + } + else { + varv.add(temp); + temp = ""; + } + variable = null; + continue; + } + if (values && c == '=') { + variable = temp; + temp = ""; + continue; + } + switch (c) { + case '%': // escaped character + try { + char a = s.charAt(++i); + char b = s.charAt(++i); + int ch = 0; + switch (a) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ch += 0x10 * (a - '0'); + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + ch += 0x10 * (a - 'a' + 0xa); + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + ch += 0x10 * (a - 'A' + 0xA); + break; + } + switch (b) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ch += (b - '0'); + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + ch += (b - 'a' + 0xa); + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + ch += (b - 'A' + 0xA); + break; + } + temp += (char) ch; + } catch (StringIndexOutOfBoundsException SIOOBe) { + // this means someone has included an invalid escape sequence. + // Invalid URIs can just be thrown on the floor. + } + break; + // + is a space + case '+': + temp += ' '; + break; + default: + temp += c; + } + } + if (values) { + if (variable == null) {variable = temp; temp = "";} + else variable.trim(); + //out("DEBUG variable read: "+variable+"/"+temp); + if (!variable.equals("")) { + Object o = vars.get(variable); + if (o == null) + vars.put(variable.trim(), temp); + else if (o instanceof String) { + LinkedList l = new LinkedList(); + l.add(o); + l.add(temp); + vars.put(variable.trim(), l); + } else if (o instanceof LinkedList) + ((LinkedList) o).add(temp); + } + + return vars; + } + else { + varv.add(temp); + return varv; + } + } + + /** + * Sets up the POST variables + */ + private Map getPOST() + { + try { + String s = ""; + while(System.in.available() > 0) + s += (char) System.in.read(); + //out("DEBUG: POST STRING: "+s); + return (Map) readVariables(s, '&', true); + } catch (IOException IOe) { + try { + out("ERROR: IOException: "+IOe); + } catch (CGIInvalidContentFormatException CGIICFe) { + System.err.println("ERROR: IOException: "+IOe); + } + return new HashMap(); + } + } + + /** + * Sets up the COOKIEs + */ + private Map getCOOKIE() + { + String s = getenv("HTTP_COOKIE"); + if (null == s) + return new HashMap(); + else + return (Map) readVariables(s, ';', true); + } + + /** + * Sets up the GET variables + */ + private Map getGET() + { + String s = getenv("QUERY_STRING"); + if (null == s) + return new HashMap(); + else + return (Map) readVariables(s, '&', true); + } + + /** + * Sets up the ENV variables + */ + private Map getENV() + { + Map m = new HashMap(); + String[] env = (String[]) getfullenv(String.class); + for (int i = 0; i < env.length; i++){ + if (null == env[i]) continue; + String[] e = env[i].split("="); + if (1 == e.length) + m.put(e[0], ""); + else + m.put(e[0], e[1]); + } + +/* + m.put("SERVER_SOFTWARE", getenv("SERVER_SOFTWARE")); + m.put("SERVER_NAME", getenv("SERVER_NAME")); + m.put("GATEWAY_INTERFACE", getenv("GATEWAY_INTERFACE")); + m.put("SERVER_PROTOCOL", getenv("SERVER_PROTOCOL")); + m.put("SERVER_PORT", getenv("SERVER_PORT")); + m.put("REQUEST_METHOD", getenv("REQUEST_METHOD")); + m.put("PATH_INFO", getenv("PATH_INFO")); + m.put("PATH_TRANSLATED", getenv("PATH_TRANSLATED")); + m.put("SCRIPT_NAME", getenv("SCRIPT_NAME")); + m.put("QUERY_STRING", getenv("QUERY_STRING")); + m.put("REMOTE_HOST", getenv("REMOTE_HOST")); + m.put("REMOTE_ADDR", getenv("REMOTE_ADDR")); + m.put("AUTH_TYPE", getenv("AUTH_TYPE")); + m.put("REMOTE_USER", getenv("REMOTE_USER")); + m.put("REMOTE_IDENT", getenv("REMOTE_IDENT")); + m.put("CONTENT_TYPE", getenv("CONTENT_TYPE")); + m.put("CONTENT_LENGTH", getenv("CONTENT_LENGTH")); + m.put("HTTP_ACCEPT", getenv("HTTP_ACCEPT")); + m.put("HTTP_USER_AGENT", getenv("HTTP_USER_AGENT")); + m.put("HTTP_COOKIE", getenv("HTTP_COOKIE")); + m.put("HTTP_ACCEPT_CHARSET", getenv("HTTP_ACCEPT_CHARSET")); + m.put("HTTP_ACCEPT_ENCODING", getenv("HTTP_ACCEPT_ENCODING")); + m.put("HTTP_CACHE_CONTROL", getenv("HTTP_CACHE_CONTROL")); + m.put("HTTP_REFERER", getenv("HTTP_REFERER")); + m.put("HTTP_X_FORWARDED_FOR", getenv("HTTP_X_FORWARDED_FOR")); + m.put("HTTP_HOST", getenv("HTTP_HOST")); + m.put("REQUEST_URI", getenv("REQUEST_URI")); + m.put("DOCUMENT_ROOT", getenv("DOCUMENT_ROOT")); + m.put("PATH", getenv("PATH")); + m.put("SERVER_ADDR", getenv("SERVER_ADDR")); + m.put("SCRIPT_FILENAME", getenv("SCRIPT_FILENAME")); + m.put("HTTP_COOKIE2", getenv("HTTP_COOKIE2")); + m.put("HTTP_CONNECTION", getenv("HTTP_CONNECTION")); + m.put("LANG", getenv("LANG")); + m.put("REDIRECT_LANG", getenv("REDIRECT_LANG")); + */ + return m; + } + + /** + * Sets up the param variables + */ + private String[] getParams(String args) + { + Vector v = (Vector) readVariables(args, ',', false); + String[] params = new String[v.size()]; + Iterator i = v.iterator(); + for (int j = 0; j < params.length; j++) + params[j] = (String) i.next(); + return params; + } + + /** + * This method sets up all the CGI variables and calls the cgi() method, then writes out the page data. + */ + public final void doCGI(String[] args) + { + CGI cgiclass = null; + // wrap everything in a try, we need to handle all our own errors. + try { + // setup the CGI variables + Map POST = getPOST(); + Map GET = getGET(); + Map ENV = getENV(); + Map COOKIE = getCOOKIE(); + String[] params = new String[] {}; + if (args.length >= 1) + params = getParams(args[0]); + + // instantiate CGI class + /* Class c = Class.forName(args[0]); + cgiclass = (CGI) c.newInstance(); */ + + // set default headers + /*cgiclass.*/header("Content-type", "text/html"); + + // execute the CGI + /*cgiclass.*/cgi(POST, GET, ENV, COOKIE, params); + + // send the output / remaining output + /*cgiclass.*/flush(); + } + + // yes, we really want to do this. CGI programs can't send errors. Print nicely to the screen. + catch (Exception e) { + errorhandler.print(/*null == cgiclass ? false : cgiclass.*/headers_sent, e); + } + catch (Throwable t) { + t.printStackTrace(); // this is bad enough to produce stderr errors + errorhandler.print(/*null == cgiclass ? false : cgiclass.*/headers_sent, new Exception(t.toString())); + } + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java b/app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java new file mode 100644 index 00000000..e0798cb7 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGIErrorHandler.java @@ -0,0 +1,41 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +/** + * Interface to handle exceptions in the CGI. + */ +public interface CGIErrorHandler +{ + /** + * This is called if an exception is not caught in the CGI. + * It should handle printing the error message nicely to the user, + * and then exit gracefully. + */ + public void print(boolean headers_sent, Exception e); +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java b/app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java new file mode 100644 index 00000000..4df8cc70 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGIHeaderSentException.java @@ -0,0 +1,39 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +/** + * Thrown if the headers have already been sent and CGI.header is called. + */ +public class CGIHeaderSentException extends Exception +{ + public CGIHeaderSentException() + { + super("Headers already sent by CGI"); + } +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java b/app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java new file mode 100644 index 00000000..281533d3 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGIInvalidContentFormatException.java @@ -0,0 +1,39 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +/** + * Thrown if both raw and text data are set in the same page. + */ +public class CGIInvalidContentFormatException extends Exception +{ + public CGIInvalidContentFormatException() + { + super("Cannot send both raw and text data"); + } +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/CGITools.java b/app/src/main/java/cx/ath/matthew/cgi/CGITools.java new file mode 100644 index 00000000..9a379459 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CGITools.java @@ -0,0 +1,48 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +abstract class CGITools +{ + /** + * Escape a character in a string. + * @param in String to escape in. + * @param c Character to escape. + * @return in with c replaced with \c + */ + public static String escapeChar(String in, char c) + { + String out = ""; + for (int i = 0; i < in.length(); i++) { + if (in.charAt(i) == c) out += '\\'; + out += in.charAt(i); + } + return out; + } +} + diff --git a/app/src/main/java/cx/ath/matthew/cgi/CheckBox.java b/app/src/main/java/cx/ath/matthew/cgi/CheckBox.java new file mode 100644 index 00000000..350390d4 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/CheckBox.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class CheckBox extends Field +{ + boolean checked; + public CheckBox(String name, String label, boolean checked) + { + this.name = name; + this.label = label; + this.checked = checked; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java b/app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java new file mode 100644 index 00000000..f5b812fd --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/DefaultErrorHandler.java @@ -0,0 +1,67 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +/** + * Interface to handle exceptions in the CGI. + */ +public class DefaultErrorHandler implements CGIErrorHandler +{ + /** + * This is called if an exception is not caught in the CGI. + * It should handle printing the error message nicely to the user, + * and then exit gracefully. + */ + public void print(boolean headers_sent, Exception e) + { + if (!headers_sent) { + System.out.println("Content-type: text/html"); + System.out.println(""); + System.out.println(""); + System.out.println(""); + System.out.println("Exception in CGI"); + System.out.println(""); + } + System.out.println("
"); + System.out.println("

"+e.getClass().toString()+"

"); + System.out.println("

"); + System.out.println("Exception Message: "+e.getMessage()); + System.out.println("

"); + System.out.println("

"); + System.out.println("Stack Trace:"); + System.out.println("

"); + System.out.println("
");
+      e.printStackTrace(System.out);
+      System.out.println("
"); + System.out.println("
"); + if (!headers_sent) { + System.out.println(""); + } + System.exit(1); + } +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/DisplayField.java b/app/src/main/java/cx/ath/matthew/cgi/DisplayField.java new file mode 100644 index 00000000..ec62a7ff --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/DisplayField.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class DisplayField extends Field +{ + String value; + public DisplayField(String label, String value) + { + this.name = ""; + this.label = label; + this.value = value; + } + protected String print() + { + return value; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/DropDown.java b/app/src/main/java/cx/ath/matthew/cgi/DropDown.java new file mode 100644 index 00000000..bdc45346 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/DropDown.java @@ -0,0 +1,131 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +import java.util.List; + +public class DropDown extends Field +{ + Object[] values; + Object defval; + boolean indexed = false; + /** + * Create a new DropDown list. + * + * @param name The HTML field name. + * @param label The label to display + * @param values The values for the drop down list + * @param defval If this parameter is set then this element will be selected by default. + * @param indexed If this is set to true, then indexes will be returned, rather than values. + */ + public DropDown(String name, String label, Object[] values, Object defval, boolean indexed) + { + this.name = name; + this.label = label; + this.values = values; + this.indexed = indexed; + this.defval = defval; + } + /** + * Create a new DropDown list. + * + * @param name The HTML field name. + * @param label The label to display + * @param values The values for the drop down list + * @param defval If this parameter is set then this element will be selected by default. + * @param indexed If this is set to true, then indexes will be returned, rather than values. + */ + public DropDown(String name, String label, Object[] values, int defval, boolean indexed) + { + this.name = name; + this.label = label; + this.values = values; + if (defval < 0) + this.defval = null; + else + this.defval = values[defval]; + this.indexed = indexed; + } + /** + * Create a new DropDown list. + * + * @param name The HTML field name. + * @param label The label to display + * @param values The values for the drop down list + * @param defval If this parameter is set then this element will be selected by default. + * @param indexed If this is set to true, then indexes will be returned, rather than values. + */ + public DropDown(String name, String label, List values, Object defval, boolean indexed) + { + this.name = name; + this.label = label; + this.values = (Object[]) values.toArray(new Object[] {}); + this.defval = defval; + this.indexed = indexed; + } + /** + * Create a new DropDown list. + * + * @param name The HTML field name. + * @param label The label to display + * @param values The values for the drop down list + * @param defval If this parameter is set then this element will be selected by default. + * @param indexed If this is set to true, then indexes will be returned, rather than values. + */ + public DropDown(String name, String label, List values, int defval, boolean indexed) + { + this.name = name; + this.label = label; + this.values = (Object[]) values.toArray(new Object[] {}); + if (defval < 0) + this.defval = null; + else + this.defval = values.get(defval); + this.indexed = indexed; + } + protected String print() + { + String s = ""; + s += "\n"; + return s; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/Field.java b/app/src/main/java/cx/ath/matthew/cgi/Field.java new file mode 100644 index 00000000..d859952b --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/Field.java @@ -0,0 +1,38 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public abstract class Field +{ + protected String name; + protected String label; + protected abstract String print(); +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java b/app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java new file mode 100644 index 00000000..0a397399 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/HTMLForm.java @@ -0,0 +1,141 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +import java.util.Iterator; +import java.util.Vector; + + +/** + * Class to manage drawing HTML forms + */ +public class HTMLForm +{ + private String target; + private String submitlabel; + private String tableclass; + private Vector fields; + private boolean post = true; + + /** + * @param target The module to submit to + */ + public HTMLForm(String target) + { + this(target, "Submit", null); + } + + /** + * @param target The module to submit to + * @param submitlabel The string to display on the submit button + */ + public HTMLForm(String target, String submitlabel) + { + this(target, submitlabel, null); + } + + /** + * @param target The module to submit to + * @param submitlabel The string to display on the submit button + * @param tableclass The class= parameter for the generated table + */ + public HTMLForm(String target, String submitlabel, String tableclass) + { + this.target = target; + this.submitlabel = submitlabel; + this.tableclass = tableclass; + fields = new Vector(); + } + + /** + * Add a field to be displayed in the form. + * + * @param field A Field subclass. + */ + public void addField(Field field) + { + fields.add(field); + } + + /** + * Set GET method rather than POST + * @param enable Enable/Disable GET + */ + public void setGET(boolean enable) + { + post = !enable; + } + + /** + * Shows the form. + * @param cgi The CGI instance that is handling output + */ + public void display(CGI cgi) + { + try { + cgi.out("
"); + if (null == tableclass) + cgi.out(""); + else + cgi.out("
"); + + Iterator i = fields.iterator(); + while (i.hasNext()) { + Field f = (Field) i.next(); + if (f instanceof NewTable) { + cgi.out(f.print()); + } + if (!(f instanceof HiddenField) && !(f instanceof SubmitButton) && !(f instanceof NewTable)) { + cgi.out(" "); + cgi.out(" "); + cgi.out(" "); + cgi.out(" "); + } + } + cgi.out(" "); + cgi.out(" "); + cgi.out(" "); + cgi.out("
"+f.label+""+f.print()+"
"); + i = fields.iterator(); + while (i.hasNext()) { + Field f = (Field) i.next(); + if (f instanceof HiddenField || f instanceof SubmitButton) { + cgi.out(" "+f.print()); + } + } + cgi.out(" "); + cgi.out("
"); + cgi.out("
"); + } catch (CGIInvalidContentFormatException CGIICFe) {} + } +} + + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/HiddenField.java b/app/src/main/java/cx/ath/matthew/cgi/HiddenField.java new file mode 100644 index 00000000..523f85bc --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/HiddenField.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class HiddenField extends Field +{ + String value; + public HiddenField(String name, String value) + { + this.name = name; + this.label = ""; + this.value = value; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java b/app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java new file mode 100644 index 00000000..39904bdf --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/MultipleDropDown.java @@ -0,0 +1,115 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +/* + * + * TODO To change the template for this generated file go to + * Window - Preferences - Java - Code Style - Code Templates + */ +package cx.ath.matthew.cgi; + +import java.util.List; + +/** + * @author Agent + * + * TODO To change the template for this generated type comment go to + * Window - Preferences - Java - Code Style - Code Templates + */ +public class MultipleDropDown extends DropDown { + + /** + * @param name + * @param label + * @param values + * @param defval + * @param indexed + */ + public MultipleDropDown(String name, String label, String[] values, + String defval, boolean indexed) { + super(name, label, values, defval, indexed); + // TODO Auto-generated constructor stub + } + + /** + * @param name + * @param label + * @param values + * @param defval + * @param indexed + */ + public MultipleDropDown(String name, String label, String[] values, + int defval, boolean indexed) { + super(name, label, values, defval, indexed); + // TODO Auto-generated constructor stub + } + + /** + * @param name + * @param label + * @param values + * @param defval + * @param indexed + */ + public MultipleDropDown(String name, String label, List values, + String defval, boolean indexed) { + super(name, label, values, defval, indexed); + // TODO Auto-generated constructor stub + } + + /** + * @param name + * @param label + * @param values + * @param defval + * @param indexed + */ + public MultipleDropDown(String name, String label, List values, int defval, + boolean indexed) { + super(name, label, values, defval, indexed); + // TODO Auto-generated constructor stub + } + + protected String print() + { + String s = ""; + s += "\n"; + return s; + } + +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/NewTable.java b/app/src/main/java/cx/ath/matthew/cgi/NewTable.java new file mode 100644 index 00000000..1638b74f --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/NewTable.java @@ -0,0 +1,42 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; +public class NewTable extends Field { + + private String name; + private String cssClass; + + public NewTable (String name, String css) { + this.name = name; + this.cssClass = css; + } + + protected String print() { + return "\n"; + } +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/Password.java b/app/src/main/java/cx/ath/matthew/cgi/Password.java new file mode 100644 index 00000000..046fc0c0 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/Password.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class Password extends Field +{ + String defval; + public Password(String name, String label, String defval) + { + this.name = name; + this.label = label; + this.defval = defval; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/Radio.java b/app/src/main/java/cx/ath/matthew/cgi/Radio.java new file mode 100644 index 00000000..3a0d80e5 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/Radio.java @@ -0,0 +1,46 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class Radio extends Field +{ + boolean checked; + public Radio(String name, String label, boolean checked) + { + this.name = name; + this.label = label; + this.checked = checked; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java b/app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java new file mode 100644 index 00000000..ff97e1ce --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/SubmitButton.java @@ -0,0 +1,54 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +/* + * + * TODO To change the template for this generated file go to + * Window - Preferences - Java - Code Style - Code Templates + */ +package cx.ath.matthew.cgi; + +/** + * @author Agent + * + * TODO To change the template for this generated type comment go to + * Window - Preferences - Java - Code Style - Code Templates + */ +public class SubmitButton extends Field { + + public SubmitButton(String name, String label) { + this.name = name; + this.label = label; + } + /* (non-Javadoc) + * @see cx.ath.matthew.cgi.Field#print() + */ + protected String print() { + return ""; + } + +} diff --git a/app/src/main/java/cx/ath/matthew/cgi/TextArea.java b/app/src/main/java/cx/ath/matthew/cgi/TextArea.java new file mode 100644 index 00000000..6a924c48 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/TextArea.java @@ -0,0 +1,57 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + +public class TextArea extends Field +{ + String defval; + int cols; + int rows; + public TextArea(String name, String label, String defval) + { + this(name, label, defval, 30, 4); + } + public TextArea(String name, String label, String defval, int cols, int rows) + { + this.name = name; + this.label = label; + if (null == defval) + this.defval = ""; + else + this.defval = defval; + this.cols = cols; + this.rows = rows; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/TextField.java b/app/src/main/java/cx/ath/matthew/cgi/TextField.java new file mode 100644 index 00000000..124ce3ef --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/TextField.java @@ -0,0 +1,69 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + + +package cx.ath.matthew.cgi; + + +public class TextField extends Field +{ + String defval; + int length; + public TextField(String name, String label) + { + this.name = name; + this.label = label; + this.defval = ""; + this.length = 0; + } + public TextField(String name, String label, String defval) + { + this.name = name; + this.label = label; + if (null == defval) + this.defval = ""; + else + this.defval = defval; + this.length = 0; + } + public TextField(String name, String label, String defval, int length) + { + this.name = name; + this.label = label; + if (null == defval) + this.defval = ""; + else + this.defval = defval; + this.length = length; + } + protected String print() + { + return ""; + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/cgi/testcgi.java b/app/src/main/java/cx/ath/matthew/cgi/testcgi.java new file mode 100644 index 00000000..563447d9 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/cgi/testcgi.java @@ -0,0 +1,78 @@ +/* + * Java CGI Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.cgi; + +import java.util.Iterator; +import java.util.Map; + +class testcgi extends CGI +{ + protected void cgi(Map POST, Map GET, Map ENV, Map COOKIE, String[] params) throws Exception + { + header("Content-type", "text/plain"); + setcookie("testcgi", "You have visited us already"); + out("This is a test CGI program"); + out("These are the params:"); + for (int i=0; i < params.length; i++) + out("-- "+params[i]); + + out("These are the POST vars:"); + Iterator i = POST.keySet().iterator(); + while (i.hasNext()) { + String s = (String) i.next(); + out("-- "+s+" => "+POST.get(s)); + } + + out("These are the GET vars:"); + i = GET.keySet().iterator(); + while (i.hasNext()) { + String s = (String) i.next(); + out("-- "+s+" => "+GET.get(s)); + } + + out("These are the ENV vars:"); + i = ENV.keySet().iterator(); + while (i.hasNext()) { + String s = (String) i.next(); + out("-- "+s+" => "+ENV.get(s)); + } + + out("These are the COOKIEs:"); + i = COOKIE.keySet().iterator(); + while (i.hasNext()) { + String s = (String) i.next(); + out("-- "+s+" => "+COOKIE.get(s)); + } + } + + public static void main(String[] args) + { + CGI cgi = new testcgi(); + cgi.doCGI(args); + } +} diff --git a/app/src/main/java/cx/ath/matthew/debug/Debug.java b/app/src/main/java/cx/ath/matthew/debug/Debug.java new file mode 100644 index 00000000..22e8c8cc --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/debug/Debug.java @@ -0,0 +1,617 @@ +/* Copyright (C) 1991-2014 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ +/* This header is separate from features.h so that the compiler can + include it implicitly at the start of every compilation. It must + not itself include or any other header that includes + because the implicit include comes before any feature + test macros that may be defined in a source file before it first + explicitly includes a system header. GCC knows the name of this + header in order to preinclude it. */ +/* glibc's intent is to support the IEC 559 math functionality, real + and complex. If the GCC (4.9 and later) predefined macros + specifying compiler intent are available, use them to determine + whether the overall intent is to support these features; otherwise, + presume an older compiler has intent to support these features and + define these macros by default. */ +/* wchar_t uses ISO/IEC 10646 (2nd ed., published 2011-03-15) / + Unicode 6.0. */ +/* We do not support C11 . */ +/* + * Java Debug Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.debug; +import cx.ath.matthew.utils.Hexdump; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; +/** + Add debugging to your program, has support for large projects with multiple + classes and debug levels per class. Supports optional enabling of debug + per-level per-class and debug targets of files, Streams or stderr. + Also supports timing between debug outputs, printing of stack traces for Throwables + and files/line numbers on each message. +

+ Debug now automatically figures out which class it was called from, so all + methods passing in the calling class are deprecated. +

+

+ The defaults are to print all messages to stderr with class and method name. +

+

+ Should be called like this: +

+
+   if (Debug.debug) Debug.print(Debug.INFO, "Debug Message");
+  
+ */ +public class Debug +{ + /** + This interface can be used to provide custom printing filters + for certain classes. + */ + public static interface FilterCommand + { + /** + Called to print debug messages with a custom filter. + @param output The PrintStream to output to. + @param level The debug level of this message. + @param location The textual location of the message. + @param extra Extra information such as timing details. + @param message The debug message. + @param lines Other lines of a multiple-line debug message. + */ + public void filter(PrintStream output, int level, String location, String extra, String message, String[] lines); + } + /** Highest priority messages */ + public static final int CRIT = 1; + /** Error messages */ + public static final int ERR = 2; + /** Warnings */ + public static final int WARN = 3; + /** Information */ + public static final int INFO = 4; + /** Debug messages */ + public static final int DEBUG = 5; + /** Verbose debug messages */ + public static final int VERBOSE = 6; + /** Set this to false to disable compilation of Debug statements */ + public static final boolean debug = false; + /** The current output stream (defaults to System.err) */ + public static PrintStream debugout = System.err; + private static Properties prop = null; + private static boolean timing = false; + private static boolean ttrace = false; + private static boolean lines = false; + private static boolean hexdump = false; + private static long last = 0; + private static int balen = 36; + private static int bawidth = 80; + private static Class saveclass = null; + //TODO: 1.5 private static Map, FilterCommand> filterMap = new HashMap, FilterCommand>(); + private static Map filterMap = new HashMap(); + /** + Set properties to configure debugging. + Format of properties is class => level, e.g. +
+      cx.ath.matthew.io.TeeOutputStream = INFO
+      cx.ath.matthew.io.DOMPrinter = DEBUG
+     
+ The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + correspond to all messages up to that level. The special words YES, ALL and TRUE + cause all messages to be printed regardless of level. All other terms disable + messages for that class. CRIT and ERR messages are always printed if debugging is enabled + unless explicitly disabled. + The special class name ALL can be used to set the default level for all classes. + @param prop Properties object to use. + */ + public static void setProperties(Properties prop) + { + Debug.prop = prop; + } + /** + Read which class to debug on at which level from the given File. + Syntax the same as Java Properties files: +
+     <class> = <debuglevel>
+     
+ E.G. +
+      cx.ath.matthew.io.TeeOutputStream = INFO
+      cx.ath.matthew.io.DOMPrinter = DEBUG
+     
+ The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + correspond to all messages up to that level. The special words YES, ALL and TRUE + cause all messages to be printed regardless of level. All other terms disable + messages for that class. CRIT and ERR messages are always printed if debugging is enabled + unless explicitly disabled. + The special class name ALL can be used to set the default level for all classes. + @param f File to read from. + */ + public static void loadConfig(File f) throws IOException + { + prop = new Properties(); + prop.load(new FileInputStream(f)); + } + /** @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. */ + //TODO: 1.5 @Deprecated() + public static boolean debugging(Class c, int loglevel) + { + if (debug) { + if (null == c) return true; + return debugging(c.getName(), loglevel); + } + return false; + } + public static boolean debugging(String s, int loglevel) + { + if (debug) { + try { + if (null == s) return true; + if (null == prop) return loglevel <= VERBOSE; + String d = prop.getProperty(s); + if (null == d || "".equals(d)) d = prop.getProperty("ALL"); + if (null == d) return loglevel <= ERR; + if ("".equals(d)) return loglevel <= ERR; + d = d.toLowerCase(); + if ("true".equals(d)) return true; + if ("yes".equals(d)) return true; + if ("all".equals(d)) return true; + if ("verbose".equals(d)) return loglevel <= VERBOSE; + if ("debug".equals(d)) return loglevel <= DEBUG; + if ("info".equals(d)) return loglevel <= INFO; + if ("warn".equals(d)) return loglevel <= WARN; + if ("err".equals(d)) return loglevel <= ERR; + if ("crit".equals(d)) return loglevel <= CRIT; + int i = Integer.parseInt(d); return i >= loglevel; + } catch (Exception e) { return false; } + } + return false; + } + /** + Output to the given Stream */ + public static void setOutput(PrintStream p) throws IOException + { + debugout = p; + } + /** + Output to the given file */ + public static void setOutput(String filename) throws IOException + { + debugout = new PrintStream(new FileOutputStream(filename, true)); + } + /** + Output to the default debug.log */ + public static void setOutput() throws IOException { + setOutput("./debug.log"); + } + /** + Log at DEBUG + @param d The object to log */ + public static void print(Object d) + { + if (debug) { + if (d instanceof String) + print(DEBUG, (String) d); + else if (d instanceof Throwable) + print(DEBUG, (Throwable) d); + else if (d instanceof byte[]) + print(DEBUG, (byte[]) d); + else if (d instanceof Map) + printMap(DEBUG, (Map) d); + else print(DEBUG, d); + } + } + /** + Log at DEBUG + @param o The object doing the logging + @param d The object to log + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, Object d) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(d); + } + } + /** + Log an Object + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param d The object to log with d.toString() + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Object d) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, d); + } + } + /** + Log a String + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The log message + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, String s) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, s); + } + } + /** + Log a Throwable + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Throwable t) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, t); + } + } + /** + Log a Throwable + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Throwable t) + { + if (debug) { + saveclass = c; + print(loglevel, t); + } + } + /** + Log a Throwable + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @see #setThrowableTraces to turn on stack traces. + */ + public static void print(int loglevel, Throwable t) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + String[] lines = null; + if (ttrace) { + StackTraceElement[] ste = t.getStackTrace(); + lines = new String[ste.length]; + for (int i = 0; i < ste.length; i++) + lines[i] = "\tat "+ste[i].toString(); + } + _print(t.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, t.toString(), lines); + } + } + } + /** + Log a byte array + @param loglevel The level to log at (DEBUG, WARN, etc) + @param b The byte array to print. + @see #setHexDump to enable hex dumping. + @see #setByteArrayCount to change how many bytes are printed. + @see #setByteArrayWidth to change the formatting width of hex. */ + public static void print(int loglevel, byte[] b) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + String[] lines = null; + if (hexdump) { + if (balen >= b.length) + lines = Hexdump.format(b, bawidth).split("\n"); + else { + byte[] buf = new byte[balen]; + System.arraycopy(b, 0, buf, 0, balen); + lines = Hexdump.format(buf, bawidth).split("\n"); + } + } + _print(b.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, b.length+" bytes", lines); + } + } + } + /** + Log a String + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The string to log with d.toString() + */ + public static void print(int loglevel, String s) + { + if (debug) + print(loglevel, (Object) s); + } + /** + Log an Object + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param d The object to log with d.toString() + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Object d) + { + if (debug) { + saveclass = c; + print(loglevel, d); + } + } + /** + Log a String + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The log message + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, String s) + { + if (debug) { + saveclass = c; + print(loglevel, s); + } + } + private static String[] getTraceElements() + { + String[] data = new String[] { "", "", "" }; + try { + Method m = Thread.class.getMethod("getStackTrace", new Class[0]); + StackTraceElement[] stes = (StackTraceElement[]) m.invoke(Thread.currentThread(), new Object[0]); + for (StackTraceElement ste: stes) { + if (Debug.class.getName().equals(ste.getClassName())) continue; + if (Thread.class.getName().equals(ste.getClassName())) continue; + if (Method.class.getName().equals(ste.getClassName())) continue; + if (ste.getClassName().startsWith("sun.reflect")) continue; + data[0] = ste.getClassName(); + data[1] = ste.getMethodName(); + if (lines) + data[2] = " "+ste.getFileName()+":"+ste.getLineNumber(); + break; + } + } catch (NoSuchMethodException NSMe) { + if (null != saveclass) + data[0] = saveclass.getName(); + } catch (IllegalAccessException IAe) { + } catch (InvocationTargetException ITe) { + } + return data; + } + /** + Log an Object + @param loglevel The level to log at (DEBUG, WARN, etc) + @param o The object to log + */ + public static void print(int loglevel, Object o) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + _print(o.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, o.toString(), null); + } + } + } + /** + Log a Map + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Object o, int loglevel, Map m) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + printMap(loglevel, m); + } + } + /** + Log a Map + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Class c, int loglevel, Map m) + { + if (debug) { + saveclass = c; + printMap(loglevel, m); + } + } + /** + Log a Map at DEBUG log level + @param m The Map to print out + */ + public static void printMap(Map m) + { + printMap(DEBUG, m); + } + /** + Log a Map + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + */ + public static void printMap(int loglevel, Map m) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + Iterator i = m.keySet().iterator(); + String[] lines = new String[m.size()]; + int j = 0; + while (i.hasNext()) { + Object key = i.next(); + lines[j++] = "\t\t- "+key+" => "+m.get(key); + } + _print(m.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, "Map:", lines); + } + } + } + /** + Enable or disable stack traces in Debuging throwables. + */ + public static void setThrowableTraces(boolean ttrace) + { + Debug.ttrace = ttrace; + } + /** + Enable or disable timing in Debug messages. + */ + public static void setTiming(boolean timing) + { + Debug.timing = timing; + } + /** + Enable or disable line numbers. + */ + public static void setLineNos(boolean lines) + { + Debug.lines = lines; + } + /** + Enable or disable hexdumps. + */ + public static void setHexDump(boolean hexdump) + { + Debug.hexdump = hexdump; + } + /** + Set the size of hexdumps. + (Default: 36) + */ + public static void setByteArrayCount(int count) + { + Debug.balen = count; + } + /** + Set the formatted width of hexdumps. + (Default: 80 chars) + */ + public static void setByteArrayWidth(int width) + { + Debug.bawidth = width; + } + /** + Add a filter command for a specific type. + This command will be called with the output stream + and the text to be sent. It should perform any + changes necessary to the text and then print the + result to the output stream. + */ + public static void addFilterCommand(Class c, FilterCommand f) + //TODO 1.5: public static void addFilterCommand(Class c, FilterCommand f) + { + filterMap.put(c, f); + } + private static void _print(Class c, int level, String loc, String extra, String message, String[] lines) + { + //TODO 1.5: FilterCommand f = filterMap.get(c); + FilterCommand f = (FilterCommand) filterMap.get(c); + if (null == f) { + debugout.println("["+loc+"] " +extra + message); + if (null != lines) + for (String s: lines) + debugout.println(s); + } else + f.filter(debugout, level, loc, extra, message, lines); + } +} diff --git a/app/src/main/java/cx/ath/matthew/debug/Debug.jpp b/app/src/main/java/cx/ath/matthew/debug/Debug.jpp new file mode 100644 index 00000000..56fd4ac7 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/debug/Debug.jpp @@ -0,0 +1,597 @@ +/* + * Java Debug Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.debug; + +import cx.ath.matthew.utils.Hexdump; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.PrintStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +/** + Add debugging to your program, has support for large projects with multiple + classes and debug levels per class. Supports optional enabling of debug + per-level per-class and debug targets of files, Streams or stderr. + Also supports timing between debug outputs, printing of stack traces for Throwables + and files/line numbers on each message. +

+ Debug now automatically figures out which class it was called from, so all + methods passing in the calling class are deprecated. +

+

+ The defaults are to print all messages to stderr with class and method name. +

+

+ Should be called like this: +

+
+   if (Debug.debug) Debug.print(Debug.INFO, "Debug Message");
+  
+ */ +public class Debug +{ + /** + This interface can be used to provide custom printing filters + for certain classes. + */ + public static interface FilterCommand + { + /** + Called to print debug messages with a custom filter. + @param output The PrintStream to output to. + @param level The debug level of this message. + @param location The textual location of the message. + @param extra Extra information such as timing details. + @param message The debug message. + @param lines Other lines of a multiple-line debug message. + */ + public void filter(PrintStream output, int level, String location, String extra, String message, String[] lines); + } + /** Highest priority messages */ + public static final int CRIT = 1; + /** Error messages */ + public static final int ERR = 2; + /** Warnings */ + public static final int WARN = 3; + /** Information */ + public static final int INFO = 4; + /** Debug messages */ + public static final int DEBUG = 5; + /** Verbose debug messages */ + public static final int VERBOSE = 6; + /** Set this to false to disable compilation of Debug statements */ + public static final boolean debug = DEBUGSETTING; + /** The current output stream (defaults to System.err) */ + public static PrintStream debugout = System.err; + private static Properties prop = null; + private static boolean timing = false; + private static boolean ttrace = false; + private static boolean lines = false; + private static boolean hexdump = false; + private static long last = 0; + private static int balen = 36; + private static int bawidth = 80; + private static Class saveclass = null; + //TODO: 1.5 private static Map, FilterCommand> filterMap = new HashMap, FilterCommand>(); + private static Map filterMap = new HashMap(); + /** + Set properties to configure debugging. + Format of properties is class => level, e.g. +
+      cx.ath.matthew.io.TeeOutputStream = INFO
+      cx.ath.matthew.io.DOMPrinter = DEBUG
+     
+ The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + correspond to all messages up to that level. The special words YES, ALL and TRUE + cause all messages to be printed regardless of level. All other terms disable + messages for that class. CRIT and ERR messages are always printed if debugging is enabled + unless explicitly disabled. + The special class name ALL can be used to set the default level for all classes. + @param prop Properties object to use. + */ + public static void setProperties(Properties prop) + { + Debug.prop = prop; + } + /** + Read which class to debug on at which level from the given File. + Syntax the same as Java Properties files: +
+     <class> = <debuglevel>
+     
+ E.G. +
+      cx.ath.matthew.io.TeeOutputStream = INFO
+      cx.ath.matthew.io.DOMPrinter = DEBUG
+     
+ The debug level can be one of CRIT, ERR, WARN, INFO, DEBUG or VERBOSE which + correspond to all messages up to that level. The special words YES, ALL and TRUE + cause all messages to be printed regardless of level. All other terms disable + messages for that class. CRIT and ERR messages are always printed if debugging is enabled + unless explicitly disabled. + The special class name ALL can be used to set the default level for all classes. + @param f File to read from. + */ + public static void loadConfig(File f) throws IOException + { + prop = new Properties(); + prop.load(new FileInputStream(f)); + } + /** @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. */ + //TODO: 1.5 @Deprecated() + public static boolean debugging(Class c, int loglevel) + { + if (debug) { + if (null == c) return true; + return debugging(c.getName(), loglevel); + } + return false; + } + public static boolean debugging(String s, int loglevel) + { + if (debug) { + try { + if (null == s) return true; + if (null == prop) return loglevel <= DEBUG; + String d = prop.getProperty(s); + if (null == d || "".equals(d)) d = prop.getProperty("ALL"); + if (null == d) return loglevel <= ERR; + if ("".equals(d)) return loglevel <= ERR; + d = d.toLowerCase(); + if ("true".equals(d)) return true; + if ("yes".equals(d)) return true; + if ("all".equals(d)) return true; + if ("verbose".equals(d)) return loglevel <= VERBOSE; + if ("debug".equals(d)) return loglevel <= DEBUG; + if ("info".equals(d)) return loglevel <= INFO; + if ("warn".equals(d)) return loglevel <= WARN; + if ("err".equals(d)) return loglevel <= ERR; + if ("crit".equals(d)) return loglevel <= CRIT; + int i = Integer.parseInt(d); return i >= loglevel; + } catch (Exception e) { return false; } + } + return false; + } + + /** + Output to the given Stream */ + public static void setOutput(PrintStream p) throws IOException + { + debugout = p; + } + /** + Output to the given file */ + public static void setOutput(String filename) throws IOException + { + debugout = new PrintStream(new FileOutputStream(filename, true)); + } + + /** + Output to the default debug.log */ + public static void setOutput() throws IOException { + setOutput("./debug.log"); + } + /** + Log at DEBUG + @param d The object to log */ + public static void print(Object d) + { + if (debug) { + if (d instanceof String) + print(DEBUG, (String) d); + else if (d instanceof Throwable) + print(DEBUG, (Throwable) d); + else if (d instanceof byte[]) + print(DEBUG, (byte[]) d); + else if (d instanceof Map) + printMap(DEBUG, (Map) d); + else print(DEBUG, d); + } + } + /** + Log at DEBUG + @param o The object doing the logging + @param d The object to log + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, Object d) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(d); + } + } + + /** + Log an Object + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param d The object to log with d.toString() + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Object d) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, d); + } + } + /** + Log a String + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The log message + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, String s) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, s); + } + } + /** + Log a Throwable + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Object o, int loglevel, Throwable t) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + print(loglevel, t); + } + } + + /** + Log a Throwable + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Throwable t) + { + if (debug) { + saveclass = c; + print(loglevel, t); + } + } + /** + Log a Throwable + @param loglevel The level to log at (DEBUG, WARN, etc) + @param t The throwable to log with .toString and .printStackTrace + @see #setThrowableTraces to turn on stack traces. + */ + public static void print(int loglevel, Throwable t) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + String[] lines = null; + if (ttrace) { + StackTraceElement[] ste = t.getStackTrace(); + lines = new String[ste.length]; + for (int i = 0; i < ste.length; i++) + lines[i] = "\tat "+ste[i].toString(); + } + _print(t.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, t.toString(), lines); + } + } + } + + /** + Log a byte array + @param loglevel The level to log at (DEBUG, WARN, etc) + @param b The byte array to print. + @see #setHexDump to enable hex dumping. + @see #setByteArrayCount to change how many bytes are printed. + @see #setByteArrayWidth to change the formatting width of hex. */ + public static void print(int loglevel, byte[] b) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + String[] lines = null; + if (hexdump) { + if (balen >= b.length) + lines = Hexdump.format(b, bawidth).split("\n"); + else { + byte[] buf = new byte[balen]; + System.arraycopy(b, 0, buf, 0, balen); + lines = Hexdump.format(buf, bawidth).split("\n"); + } + } + _print(b.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, b.length+" bytes", lines); + } + } + } + /** + Log a String + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The string to log with d.toString() + */ + public static void print(int loglevel, String s) + { + if (debug) + print(loglevel, (Object) s); + } + /** + Log an Object + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param d The object to log with d.toString() + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, Object d) + { + if (debug) { + saveclass = c; + print(loglevel, d); + } + } + /** + Log a String + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param s The log message + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void print(Class c, int loglevel, String s) + { + if (debug) { + saveclass = c; + print(loglevel, s); + } + } + private static String[] getTraceElements() + { + String[] data = new String[] { "", "", "" }; + try { + Method m = Thread.class.getMethod("getStackTrace", new Class[0]); + StackTraceElement[] stes = (StackTraceElement[]) m.invoke(Thread.currentThread(), new Object[0]); + for (StackTraceElement ste: stes) { + if (Debug.class.getName().equals(ste.getClassName())) continue; + if (Thread.class.getName().equals(ste.getClassName())) continue; + if (Method.class.getName().equals(ste.getClassName())) continue; + if (ste.getClassName().startsWith("sun.reflect")) continue; + data[0] = ste.getClassName(); + data[1] = ste.getMethodName(); + if (lines) + data[2] = " "+ste.getFileName()+":"+ste.getLineNumber(); + break; + } + } catch (NoSuchMethodException NSMe) { + if (null != saveclass) + data[0] = saveclass.getName(); + } catch (IllegalAccessException IAe) { + } catch (InvocationTargetException ITe) { + } + return data; + } + /** + Log an Object + @param loglevel The level to log at (DEBUG, WARN, etc) + @param o The object to log + */ + public static void print(int loglevel, Object o) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + _print(o.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, o.toString(), null); + } + } + } + + /** + Log a Map + @param o The object doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Object o, int loglevel, Map m) + { + if (debug) { + if (o instanceof Class) + saveclass = (Class) o; + else + saveclass = o.getClass(); + printMap(loglevel, m); + } + } + /** + Log a Map + @param c The class doing the logging + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + @deprecated In Java 1.5 calling class is automatically identified, no need to pass it in. + */ + //TODO: 1.5 @Deprecated() + public static void printMap(Class c, int loglevel, Map m) + { + if (debug) { + saveclass = c; + printMap(loglevel, m); + } + } + /** + Log a Map at DEBUG log level + @param m The Map to print out + */ + public static void printMap(Map m) + { + printMap(DEBUG, m); + } + /** + Log a Map + @param loglevel The level to log at (DEBUG, WARN, etc) + @param m The Map to print out + */ + public static void printMap(int loglevel, Map m) + { + if (debug) { + String timestr = ""; + String[] data = getTraceElements(); + if (debugging(data[0], loglevel)) { + if (timing) { + long now = System.currentTimeMillis(); + timestr = "{" + (now-last) + "} "; + last = now; + } + Iterator i = m.keySet().iterator(); + String[] lines = new String[m.size()]; + int j = 0; + while (i.hasNext()) { + Object key = i.next(); + lines[j++] = "\t\t- "+key+" => "+m.get(key); + } + _print(m.getClass(), loglevel, data[0]+"."+data[1]+"()" + data[2], timestr, "Map:", lines); + } + } + } + /** + Enable or disable stack traces in Debuging throwables. + */ + public static void setThrowableTraces(boolean ttrace) + { + Debug.ttrace = ttrace; + } + /** + Enable or disable timing in Debug messages. + */ + public static void setTiming(boolean timing) + { + Debug.timing = timing; + } + /** + Enable or disable line numbers. + */ + public static void setLineNos(boolean lines) + { + Debug.lines = lines; + } + /** + Enable or disable hexdumps. + */ + public static void setHexDump(boolean hexdump) + { + Debug.hexdump = hexdump; + } + /** + Set the size of hexdumps. + (Default: 36) + */ + public static void setByteArrayCount(int count) + { + Debug.balen = count; + } + /** + Set the formatted width of hexdumps. + (Default: 80 chars) + */ + public static void setByteArrayWidth(int width) + { + Debug.bawidth = width; + } + /** + Add a filter command for a specific type. + This command will be called with the output stream + and the text to be sent. It should perform any + changes necessary to the text and then print the + result to the output stream. + */ + public static void addFilterCommand(Class c, FilterCommand f) + //TODO 1.5: public static void addFilterCommand(Class c, FilterCommand f) + { + filterMap.put(c, f); + } + private static void _print(Class c, int level, String loc, String extra, String message, String[] lines) + { + //TODO 1.5: FilterCommand f = filterMap.get(c); + FilterCommand f = (FilterCommand) filterMap.get(c); + if (null == f) { + debugout.println("["+loc+"] " +extra + message); + if (null != lines) + for (String s: lines) + debugout.println(s); + } else + f.filter(debugout, level, loc, extra, message, lines); + } +} + + diff --git a/app/src/main/java/cx/ath/matthew/io/DOMPrinter.java b/app/src/main/java/cx/ath/matthew/io/DOMPrinter.java new file mode 100644 index 00000000..e61fa779 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/DOMPrinter.java @@ -0,0 +1,114 @@ +/* + * Java DOM Printing Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; + +import org.w3c.dom.Document; +import org.w3c.dom.DocumentType; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * Print a DOM tree to the given OutputStream + */ +public class DOMPrinter +{ + /** + * Print the given node and all its children. + * @param n The Node to print. + * @param os The Stream to print to. + */ + public static void printNode(Node n, OutputStream os) + { + PrintStream p = new PrintStream(os); + printNode(n, p); + } + /** + * Print the given node and all its children. + * @param n The Node to print. + * @param p The Stream to print to. + */ + public static void printNode(Node n, PrintStream p) + { + if (null != n.getNodeValue()) p.print(n.getNodeValue()); + else { + p.print("<"+n.getNodeName()); + if (n.hasAttributes()) { + NamedNodeMap nnm = n.getAttributes(); + for (int i = 0; i < nnm.getLength(); i++) { + Node attr = nnm.item(i); + p.print(" "+attr.getNodeName()+"='"+attr.getNodeValue()+"'"); + } + } + if (n.hasChildNodes()) { + p.print(">"); + NodeList nl = n.getChildNodes(); + for (int i = 0; i < nl.getLength(); i++) + printNode(nl.item(i), p); + p.print(""); + } else { + p.print("/>"); + } + } + } + /** + * Print the given document and all its children. + * @param d The Document to print. + * @param p The Stream to print to. + */ + public static void printDOM(Document d, PrintStream p) + { + DocumentType dt = d.getDoctype(); + if (null != dt) { + p.print(""); + } + Element e = d.getDocumentElement(); + printNode(e, p); + } + /** + * Print the given document and all its children. + * @param d The Document to print. + * @param os The Stream to print to. + */ + public static void printDOM(Document d, OutputStream os) + { + PrintStream p = new PrintStream(os); + printDOM(d, p); + } +} + diff --git a/app/src/main/java/cx/ath/matthew/io/ExecInputStream.java b/app/src/main/java/cx/ath/matthew/io/ExecInputStream.java new file mode 100644 index 00000000..517cded8 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/ExecInputStream.java @@ -0,0 +1,142 @@ +/* + * Java Exec Pipe Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Class to pipe an InputStream through a command using stdin/stdout. + * E.g. + *
+ *    Reader r = new InputStreamReader(new ExecInputStream(new FileInputStream("file"), "command"));
+ * 
+ */ +public class ExecInputStream extends FilterInputStream +{ + private Process proc; + private InputStream stdout; + private OutputStream stdin; + private InOutCopier copy; + + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param p Filters data through stdin/out on this Process + */ + public ExecInputStream(InputStream is, Process p) throws IOException + { + super(is); + proc = p; + stdin = p.getOutputStream(); + stdout = p.getInputStream(); + copy = new InOutCopier(in, stdin); + copy.start(); + } + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param cmd Creates a Process from this string to filter data through stdin/out + */ + public ExecInputStream(InputStream is, String cmd) throws IOException + { this(is, Runtime.getRuntime().exec(cmd)); } + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out + */ + public ExecInputStream(InputStream is, String[] cmd) throws IOException + { this(is, Runtime.getRuntime().exec(cmd)); } + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param cmd Creates a Process from this string to filter data through stdin/out + * @param env Setup the environment for the command + */ + public ExecInputStream(InputStream is, String cmd, String[] env) throws IOException + { this(is, Runtime.getRuntime().exec(cmd, env)); } + /** + * Create a new ExecInputStream on the given InputStream + * using the process to filter the stream. + * @param is Reads from this InputStream + * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out + * @param env Setup the environment for the command + */ + public ExecInputStream(InputStream is, String[] cmd, String[] env) throws IOException + { this(is, Runtime.getRuntime().exec(cmd, env)); } + + public void close() throws IOException + { + try { + proc.waitFor(); + } catch (InterruptedException Ie) {} + //copy.close(); + try { + copy.join(); + } catch (InterruptedException Ie) {} + stdin.close(); + in.close(); + stdout.close(); + } + public void flush() throws IOException + { + copy.flush(); + } + public int available() throws IOException + { return stdout.available(); } + public int read() throws IOException + { return stdout.read(); } + public int read(byte[] b) throws IOException + { return stdout.read(b); } + public int read(byte[] b, int off, int len) throws IOException + { return stdout.read(b, off, len); } + public long skip(long n) throws IOException + { return stdout.skip(n); } + public void mark(int readlimit) + {} + public boolean markSupported() + { return false; } + public void reset() + {} + + public void finalize() + { + try { + close(); + } catch (Exception e) {} + } +} + + + diff --git a/app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java b/app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java new file mode 100644 index 00000000..9ee92d6c --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/ExecOutputStream.java @@ -0,0 +1,137 @@ +/* + * Java Exec Pipe Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.FilterOutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Class to pipe an OutputStream through a command using stdin/stdout. + * E.g. + *
+ *    Writer w = new OutputStreamWriter(new ExecOutputStream(new FileOutputStream("file"), "command"));
+ * 
+ */ +public class ExecOutputStream extends FilterOutputStream +{ + private Process proc; + private InputStream stdout; + private OutputStream stdin; + private InOutCopier copy; + + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param p Filters data through stdin/out on this Process + */ + public ExecOutputStream(OutputStream os, Process p) throws IOException + { + super(os); + proc = p; + stdin = p.getOutputStream(); + stdout = p.getInputStream(); + copy = new InOutCopier(stdout, out); + copy.start(); + } + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param cmd Creates a Process from this string to filter data through stdin/out + */ + public ExecOutputStream(OutputStream os, String cmd) throws IOException + { this(os, Runtime.getRuntime().exec(cmd)); } + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out + */ + public ExecOutputStream(OutputStream os, String[] cmd) throws IOException + { this(os, Runtime.getRuntime().exec(cmd)); } + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param cmd Creates a Process from this string to filter data through stdin/out + * @param env Setup the environment for the command + */ + public ExecOutputStream(OutputStream os, String cmd, String[] env) throws IOException + { this(os, Runtime.getRuntime().exec(cmd, env)); } + /** + * Create a new ExecOutputStream on the given OutputStream + * using the process to filter the stream. + * @param os Writes to this OutputStream + * @param cmd Creates a Process from this string array (command, arg, ...) to filter data through stdin/out + * @param env Setup the environment for the command + */ + public ExecOutputStream(OutputStream os, String[] cmd, String[] env) throws IOException + { this(os, Runtime.getRuntime().exec(cmd, env)); } + + public void close() throws IOException + { + stdin.close(); + try { + proc.waitFor(); + } catch (InterruptedException Ie) {} + //copy.close(); + try { + copy.join(); + } catch (InterruptedException Ie) {} + stdout.close(); + out.close(); + } + public void flush() throws IOException + { + stdin.flush(); + copy.flush(); + out.flush(); + } + public void write(byte[] b) throws IOException + { + stdin.write(b); + } + public void write(byte[] b, int off, int len) throws IOException + { + stdin.write(b, off, len); + } + public void write(int b) throws IOException + { + stdin.write(b); + } + public void finalize() + { + try { + close(); + } catch (Exception e) {} + } +} + diff --git a/app/src/main/java/cx/ath/matthew/io/InOutCopier.java b/app/src/main/java/cx/ath/matthew/io/InOutCopier.java new file mode 100644 index 00000000..49688539 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/InOutCopier.java @@ -0,0 +1,115 @@ +/* + * Java Exec Pipe Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Copies from an input stream to an output stream using a Thread. + * example: + * + *
+ * InputStream a = getInputStream();
+ * OutputStream b = getOutputStream();
+ * InOutCopier copier = new InOutCopier(a, b);
+ * copier.start();
+ * <do stuff that writes to the inputstream>
+ * 
+ */ +public class InOutCopier extends Thread +{ + private static final int BUFSIZE=1024; + private static final int POLLTIME=100; + private BufferedInputStream is; + private OutputStream os; + private boolean enable; + /** + * Create a copier from an inputstream to an outputstream + * @param is The stream to copy from + * @param os the stream to copy to + */ + public InOutCopier(InputStream is, OutputStream os) throws IOException + { + this.is = new BufferedInputStream(is); + this.os = os; + this.enable = true; + } + /** + * Force close the stream without waiting for EOF on the source + */ + public void close() + { + enable = false; + interrupt(); + } + /** + * Flush the outputstream + */ + public void flush() throws IOException + { + os.flush(); + } + /** Start the thread and wait to make sure its really started */ + public synchronized void start() + { + super.start(); + try { + wait(); + } catch (InterruptedException Ie) {} + } + /** + * Copies from the inputstream to the outputstream + * until EOF on the inputstream or explicitly closed + * @see #close() + */ + public void run() + { + byte[] buf = new byte[BUFSIZE]; + synchronized (this) { + notifyAll(); + } + while (enable) + try { + int n = is.read(buf); + if (0 > n) + break; + if (0 < n) { + os.write(buf, 0, (n> BUFSIZE? BUFSIZE:n)); + os.flush(); + } + } catch (IOException IOe) { + break; + } + try { os.close(); } catch (IOException IOe) {} + } +} + diff --git a/app/src/main/java/cx/ath/matthew/io/TeeInputStream.java b/app/src/main/java/cx/ath/matthew/io/TeeInputStream.java new file mode 100644 index 00000000..f769b114 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/TeeInputStream.java @@ -0,0 +1,155 @@ +/* + * Java Tee Stream Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterInputStream; +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +/** + * Class to copy a stream to a file or another stream as it is being sent through a stream pipe + * E.g. + *
+ *    Reader r = new InputStreamReader(new TeeInputStream(new FileInputStream("file"), new File("otherfile")));
+ * 
+ */ +public class TeeInputStream extends FilterInputStream +{ + private InputStream in; + private OutputStream fos; + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param tos Write to this OutputStream + */ + public TeeInputStream(InputStream is, OutputStream tos) throws IOException + { + super(is); + this.in = is; + this.fos = tos; + } + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param f Write to this File + * @param append Append to file not overwrite + */ + public TeeInputStream(InputStream is, File f, boolean append) throws IOException + { + super(is); + this.in = is; + this.fos = new FileOutputStream(f, append); + } + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param f Write to this File + */ + public TeeInputStream(InputStream is, File f) throws IOException + { + super(is); + this.in = is; + this.fos = new FileOutputStream(f); + } + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param f Write to this File + * @param append Append to file not overwrite + */ + public TeeInputStream(InputStream is, String f, boolean append) throws IOException + { + this(is, new File(f), append); + } + /** + * Create a new TeeInputStream on the given InputStream + * and copy the stream to the given File. + * @param is Reads from this InputStream + * @param f Write to this File + */ + public TeeInputStream(InputStream is, String f) throws IOException + { + this(is, new File(f)); + } + public void close() throws IOException + { + in.close(); + fos.close(); + } + public void flush() throws IOException + { + fos.flush(); + } + public int available() throws IOException + { + return in.available(); + } + public int read() throws IOException + { + int i = in.read(); + if (-1 != i) fos.write(i); + return i; + } + public int read(byte[] b) throws IOException + { + int c = in.read(b); + if (-1 != c) fos.write(b, 0, c); + return c; + } + public int read(byte[] b, int off, int len) throws IOException + { + int c = in.read(b, off, len); + if (-1 != c) fos.write(b, off, c); + return c; + } + public long skip(long n) throws IOException + { return in.skip(n); } + public void mark(int readlimit) + {} + public boolean markSupported() + { return false; } + public void reset() throws IOException + { in.reset(); } + + public void finalize() + { + try { + close(); + } catch (Exception e) {} + } +} + + + diff --git a/app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java b/app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java new file mode 100644 index 00000000..30509230 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/TeeOutputStream.java @@ -0,0 +1,141 @@ +/* + * Java Tee Stream Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.FilterOutputStream; +import java.io.OutputStream; +import java.io.IOException; + +/** + * Class to copy a stream to another stream or file as it is being sent through a stream pipe + * E.g. + *
+ *    PrintWriter r = new PrintWriter(new TeeOutputStream(new FileOutputStream("file"), new File("otherfile")));
+ * 
+ */ +public class TeeOutputStream extends FilterOutputStream +{ + private File f; + private OutputStream out; + private OutputStream fos; + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the other OuputStream. + * @param os Writes to this OutputStream + * @param tos Write to this OutputStream + */ + public TeeOutputStream(OutputStream os, OutputStream tos) throws IOException + { + super(os); + this.out = os; + this.fos = tos; + } + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the given File. + * @param os Writes to this OutputStream + * @param f Write to this File + * @param append Append to file not overwrite + */ + public TeeOutputStream(OutputStream os, File f, boolean append) throws IOException + { + super(os); + this.out = os; + this.fos = new FileOutputStream(f, append); + } + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the given File. + * @param os Writes to this OutputStream + * @param f Write to this File + */ + public TeeOutputStream(OutputStream os, File f) throws IOException + { + super(os); + this.out = os; + this.fos = new FileOutputStream(f); + } + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the given File. + * @param os Writes to this OutputStream + * @param f Write to this File + * @param append Append to file not overwrite + */ + public TeeOutputStream(OutputStream os, String f, boolean append) throws IOException + { + this(os, new File(f), append); + } + /** + * Create a new TeeOutputStream on the given OutputStream + * and copy the stream to the given File. + * @param os Writes to this OutputStream + * @param f Write to this File + */ + public TeeOutputStream(OutputStream os, String f) throws IOException + { + this(os, new File(f)); + } + public void close() throws IOException + { + out.close(); + fos.close(); + } + public void flush() throws IOException + { + fos.flush(); + out.flush(); + } + public void write(int b) throws IOException + { + fos.write(b); + out.write(b); + } + public void write(byte[] b) throws IOException + { + fos.write(b); + out.write(b); + } + public void write(byte[] b, int off, int len) throws IOException + { + fos.write(b, off, len); + out.write(b, off, len); + } + + public void finalize() + { + try { + close(); + } catch (Exception e) {} + } +} + + + diff --git a/app/src/main/java/cx/ath/matthew/io/test.java b/app/src/main/java/cx/ath/matthew/io/test.java new file mode 100644 index 00000000..0f3c9e43 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/test.java @@ -0,0 +1,48 @@ +/* + * Java IO Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; +import java.io.PrintWriter; +import java.io.OutputStreamWriter; +class test +{ + public static void main(String[] args) throws Exception + { + PrintWriter out = new PrintWriter(new OutputStreamWriter(new ExecOutputStream(System.out, "xsltproc mcr.xsl -")));///java cx.ath.matthew.io.findeof"))); + + out.println(""); + out.println(" "); + out.println(" "); + out.println(" TEST"); + out.println(" "); + out.println("hello, he is helping tie up helen's lemmings"); + out.println("we are being followed and we break out"); + out.println(" "); + out.println(" "); + out.close(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/io/test2.java b/app/src/main/java/cx/ath/matthew/io/test2.java new file mode 100644 index 00000000..bd7a047d --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/test2.java @@ -0,0 +1,39 @@ +/* + * Java IO Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; +import java.io.BufferedReader; +import java.io.InputStreamReader; +class test2 +{ + public static void main(String[] args) throws Exception + { + BufferedReader in = new BufferedReader(new InputStreamReader(new ExecInputStream(System.in, "xsltproc mcr.xsl -"))); + String s; + while (null != (s = in.readLine())) System.out.println(s); + } +} diff --git a/app/src/main/java/cx/ath/matthew/io/test3.java b/app/src/main/java/cx/ath/matthew/io/test3.java new file mode 100644 index 00000000..5d40214b --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/io/test3.java @@ -0,0 +1,45 @@ +/* + * Java IO Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.io; +import java.io.PrintWriter; +import java.io.BufferedReader; +import java.io.InputStreamReader; +class test3 +{ + public static void main(String[] args) throws Exception + { + String file = args[0]; + PrintWriter p = new PrintWriter(new TeeOutputStream(System.out, file)); + BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); + String s; + while (null != (s = r.readLine())) + p.println(s); + p.close(); + r.close(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java b/app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java new file mode 100644 index 00000000..7f5c4e75 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/NotConnectedException.java @@ -0,0 +1,37 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.net.SocketException; + +public class NotConnectedException extends SocketException +{ + public NotConnectedException() + { + super("The Socket is Not Connected"); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/USInputStream.java b/app/src/main/java/cx/ath/matthew/unix/USInputStream.java new file mode 100644 index 00000000..95a95e6d --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/USInputStream.java @@ -0,0 +1,84 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.InputStream; +import java.io.IOException; + +public class USInputStream extends InputStream +{ + public static final int MSG_DONTWAIT = 0x40; + private native int native_recv(int sock, byte[] b, int off, int len, int flags, int timeout) throws IOException; + private int sock; + boolean closed = false; + private byte[] onebuf = new byte[1]; + private UnixSocket us; + private boolean blocking = true; + private int flags = 0; + private int timeout = 0; + public USInputStream(int sock, UnixSocket us) + { + this.sock = sock; + this.us = us; + } + public void close() throws IOException + { + closed = true; + us.close(); + } + public boolean markSupported() { return false; } + public int read() throws IOException + { + int rv = 0; + while (0 >= rv) rv = read(onebuf); + if (-1 == rv) return -1; + return 0 > onebuf[0] ? -onebuf[0] : onebuf[0]; + } + public int read(byte[] b, int off, int len) throws IOException + { + if (closed) throw new NotConnectedException(); + int count = native_recv(sock, b, off, len, flags, timeout); + /* Yes, I really want to do this. Recv returns 0 for 'connection shut down'. + * read() returns -1 for 'end of stream. + * Recv returns -1 for 'EAGAIN' (all other errors cause an exception to be raised) + * whereas read() returns 0 for '0 bytes read', so yes, I really want to swap them here. + */ + if (0 == count) return -1; + else if (-1 == count) return 0; + else return count; + } + public boolean isClosed() { return closed; } + public UnixSocket getSocket() { return us; } + public void setBlocking(boolean enable) + { + flags = enable ? 0 : MSG_DONTWAIT; + } + public void setSoTimeout(int timeout) + { + this.timeout = timeout; + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/USOutputStream.java b/app/src/main/java/cx/ath/matthew/unix/USOutputStream.java new file mode 100644 index 00000000..7e06289e --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/USOutputStream.java @@ -0,0 +1,69 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.IOException; +import java.io.OutputStream; + +public class USOutputStream extends OutputStream +{ + private native int native_send(int sock, byte[] b, int off, int len) throws IOException; + private native int native_send(int sock, byte[][] b) throws IOException; + private int sock; + boolean closed = false; + private byte[] onebuf = new byte[1]; + private UnixSocket us; + public USOutputStream(int sock, UnixSocket us) + { + this.sock = sock; + this.us = us; + } + public void close() throws IOException + { + closed = true; + us.close(); + } + public void flush() {} // no-op, we do not buffer + public void write(byte[][] b) throws IOException + { + if (closed) throw new NotConnectedException(); + native_send(sock, b); + } + public void write(byte[] b, int off, int len) throws IOException + { + if (closed) throw new NotConnectedException(); + native_send(sock, b, off, len); + } + public void write(int b) throws IOException + { + onebuf[0] = (byte) (b % 0x7F); + if (1 == (b % 0x80)) onebuf[0] = (byte) -onebuf[0]; + write(onebuf); + } + public boolean isClosed() { return closed; } + public UnixSocket getSocket() { return us; } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixIOException.java b/app/src/main/java/cx/ath/matthew/unix/UnixIOException.java new file mode 100644 index 00000000..61cdc127 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/UnixIOException.java @@ -0,0 +1,44 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.IOException; + +/** + * An IO Exception which occurred during UNIX Socket IO + */ +public class UnixIOException extends IOException +{ + private int no; + private String message; + public UnixIOException(int no, String message) + { + super(message); + this.message = message; + this.no = no; + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java b/app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java new file mode 100644 index 00000000..082978c0 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/UnixServerSocket.java @@ -0,0 +1,129 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.IOException; + +/** + * Represents a listening UNIX Socket. + */ +public class UnixServerSocket +{ + static { System.loadLibrary("unix-java"); } + private native int native_bind(String address, boolean abs) throws IOException; + private native void native_close(int sock) throws IOException; + private native int native_accept(int sock) throws IOException; + private UnixSocketAddress address = null; + private boolean bound = false; + private boolean closed = false; + private int sock; + /** + * Create an un-bound server socket. + */ + public UnixServerSocket() + { + } + /** + * Create a server socket bound to the given address. + * @param address Path to the socket. + */ + public UnixServerSocket(UnixSocketAddress address) throws IOException + { + bind(address); + } + /** + * Create a server socket bound to the given address. + * @param address Path to the socket. + */ + public UnixServerSocket(String address) throws IOException + { + this(new UnixSocketAddress(address)); + } + /** + * Accepts a connection on the ServerSocket. + * @return A UnixSocket connected to the accepted connection. + */ + public UnixSocket accept() throws IOException + { + int client_sock = native_accept(sock); + return new UnixSocket(client_sock, address); + } + /** + * Closes the ServerSocket. + */ + public synchronized void close() throws IOException + { + native_close(sock); + sock = 0; + closed = true; + bound = false; + } + /** + * Binds a server socket to the given address. + * @param address Path to the socket. + */ + public void bind(UnixSocketAddress address) throws IOException + { + if (bound) close(); + sock = native_bind(address.path, address.abs); + bound = true; + closed = false; + this.address = address; + } + /** + * Binds a server socket to the given address. + * @param address Path to the socket. + */ + public void bind(String address) throws IOException + { + bind(new UnixSocketAddress(address)); + } + /** + * Return the address this socket is bound to. + * @return The UnixSocketAddress if bound or null if unbound. + */ + public UnixSocketAddress getAddress() + { + return address; + } + /** + * Check the status of the socket. + * @return True if closed. + */ + public boolean isClosed() + { + return closed; + } + /** + * Check the status of the socket. + * @return True if bound. + */ + public boolean isBound() + { + return bound; + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixSocket.java b/app/src/main/java/cx/ath/matthew/unix/UnixSocket.java new file mode 100644 index 00000000..f6526143 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/UnixSocket.java @@ -0,0 +1,320 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +import java.io.InputStream; +import java.io.IOException; +import java.io.OutputStream; + +import cx.ath.matthew.debug.Debug; + +/** + * Represents a UnixSocket. + */ +public class UnixSocket +{ + static { System.loadLibrary("unix-java"); } + private native void native_set_pass_cred(int sock, boolean passcred) throws IOException; + private native int native_connect(String address, boolean abs) throws IOException; + private native void native_close(int sock) throws IOException; + private native int native_getPID(int sock); + private native int native_getUID(int sock); + private native int native_getGID(int sock); + private native void native_send_creds(int sock, byte data) throws IOException; + private native byte native_recv_creds(int sock, int[] creds) throws IOException; + + private UnixSocketAddress address = null; + private USOutputStream os = null; + private USInputStream is = null; + private boolean closed = false; + private boolean connected = false; + private boolean passcred = false; + private int sock = 0; + private boolean blocking = true; + private int uid = -1; + private int pid = -1; + private int gid = -1; + UnixSocket(int sock, UnixSocketAddress address) + { + this.sock = sock; + this.address = address; + this.connected = true; + this.os = new USOutputStream(sock, this); + this.is = new USInputStream(sock, this); + } + /** + * Create an unconnected socket. + */ + public UnixSocket() + { + } + /** + * Create a socket connected to the given address. + * @param address The Unix Socket address to connect to + */ + public UnixSocket(UnixSocketAddress address) throws IOException + { + connect(address); + } + /** + * Create a socket connected to the given address. + * @param address The Unix Socket address to connect to + */ + public UnixSocket(String address) throws IOException + { + this(new UnixSocketAddress(address)); + } + /** + * Connect the socket to this address. + * @param address The Unix Socket address to connect to + */ + public void connect(UnixSocketAddress address) throws IOException + { + if (connected) close(); + this.sock = native_connect(address.path, address.abs); + this.os = new USOutputStream(this.sock, this); + this.is = new USInputStream(this.sock, this); + this.address = address; + this.connected = true; + this.closed = false; + this.is.setBlocking(blocking); + } + /** + * Connect the socket to this address. + * @param address The Unix Socket address to connect to + */ + public void connect(String address) throws IOException + { + connect(new UnixSocketAddress(address)); + } + public void finalize() + { + try { + close(); + } catch (IOException IOe) {} + } + /** + * Closes the connection. + */ + public synchronized void close() throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Closing socket"); + native_close(sock); + sock = 0; + this.closed = true; + this.connected = false; + os = null; + is = null; + } + /** + * Returns an InputStream for reading from the socket. + * @return An InputStream connected to this socket. + */ + public InputStream getInputStream() + { + return is; + } + /** + * Returns an OutputStream for writing to the socket. + * @return An OutputStream connected to this socket. + */ + public OutputStream getOutputStream() + { + return os; + } + /** + * Returns the address this socket is connected to. + * Returns null if the socket is unconnected. + * @return The UnixSocketAddress the socket is connected to + */ + public UnixSocketAddress getAddress() + { + return address; + } + /** + * Send a single byte of data with credentials. + * (Works on BSDs) + * @param data The byte of data to send. + */ + public void sendCredentialByte(byte data) throws IOException + { + if (!connected) throw new NotConnectedException(); + native_send_creds(sock, data); + } + /** + * Receive a single byte of data, with credentials. + * (Works on BSDs) + * @see getPeerUID + * @see getPeerPID + * @see getPeerGID + * @param data The byte of data to send. + */ + public byte recvCredentialByte() throws IOException + { + if (!connected) throw new NotConnectedException(); + int[] creds = new int[] { -1, -1, -1 }; + byte data = native_recv_creds(sock, creds); + pid = creds[0]; + uid = creds[1]; + gid = creds[2]; + return data; + } + /** + * Get the credential passing status. + * (only effective on linux) + * @return The current status of credential passing. + * @see setPassCred + */ + public boolean getPassCred() + { + return passcred; + } + /** + * Return the uid of the remote process. + * Some data must have been received on the socket to do this. + * Either setPassCred must be called on Linux first, or recvCredentialByte + * on BSD. + * @return the UID or -1 if it is not available + */ + public int getPeerUID() + { + if (-1 == uid) + uid = native_getUID(sock); + return uid; + } + /** + * Return the gid of the remote process. + * Some data must have been received on the socket to do this. + * Either setPassCred must be called on Linux first, or recvCredentialByte + * on BSD. + * @return the GID or -1 if it is not available + */ + public int getPeerGID() + { + if (-1 == gid) + gid = native_getGID(sock); + return gid; + } + /** + * Return the pid of the remote process. + * Some data must have been received on the socket to do this. + * Either setPassCred must be called on Linux first, or recvCredentialByte + * on BSD. + * @return the PID or -1 if it is not available + */ + public int getPeerPID() + { + if (-1 == pid) + pid = native_getPID(sock); + return pid; + } + /** + * Set the credential passing status. + * (Only does anything on linux, for other OS, you need + * to use send/recv credentials) + * @param enable Set to true for credentials to be passed. + */ + public void setPassCred(boolean enable) throws IOException + { + native_set_pass_cred(sock, enable); + passcred = enable; + } + /** + * Get the blocking mode. + * @return true if reads are blocking. + * @see setBlocking + */ + public boolean getBlocking() + { + return blocking; + } + /** + * Set the blocking mode. + * @param enable Set to false for non-blocking reads. + */ + public void setBlocking(boolean enable) + { + blocking = enable; + if (null != is) is.setBlocking(enable); + } + + /** + * Check the socket status. + * @return true if closed. + */ + public boolean isClosed() + { + return closed; + } + /** + * Check the socket status. + * @return true if connected. + */ + public boolean isConnected() + { + return connected; + } + /** + * Check the socket status. + * @return true if the input stream has been shutdown + */ + public boolean isInputShutdown() + { + return is.isClosed(); + } + /** + * Check the socket status. + * @return true if the output stream has been shutdown + */ + public boolean isOutputShutdown() + { + return os.isClosed(); + } + /** + * Shuts down the input stream. + * Subsequent reads on the associated InputStream will fail. + */ + public void shutdownInput() + { + is.closed = true; + } + /** + * Shuts down the output stream. + * Subsequent writes to the associated OutputStream will fail. + */ + public void shutdownOutput() + { + os.closed = true; + } + /** + * Set timeout of read requests. + */ + public void setSoTimeout(int timeout) + { + is.setSoTimeout(timeout); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java b/app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java new file mode 100644 index 00000000..319cd604 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/UnixSocketAddress.java @@ -0,0 +1,85 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2004 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ +package cx.ath.matthew.unix; + +/** + * Represents an address for a Unix Socket + */ +public class UnixSocketAddress +{ + String path; + boolean abs; + /** + * Create the address. + * @param path The path to the Unix Socket. + * @param abs True if this should be an abstract socket. + */ + public UnixSocketAddress(String path, boolean abs) + { + this.path = path; + this.abs = abs; + } + /** + * Create the address. + * @param path The path to the Unix Socket. + */ + public UnixSocketAddress(String path) + { + this.path = path; + this.abs = false; + } + /** + * Return the path. + */ + public String getPath() + { + return path; + } + /** + * Returns true if this an address for an abstract socket. + */ + public boolean isAbstract() + { + return abs; + } + /** + * Return the Address as a String. + */ + public String toString() + { + return "unix"+(abs?":abstract":"")+":path="+path; + } + public boolean equals(Object o) + { + if (!(o instanceof UnixSocketAddress)) return false; + return ((UnixSocketAddress) o).path.equals(this.path); + } + public int hashCode() + { + return path.hashCode(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/java-unix.h b/app/src/main/java/cx/ath/matthew/unix/java-unix.h new file mode 100644 index 00000000..f0d1ebe3 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/java-unix.h @@ -0,0 +1,112 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class cx_ath_matthew_unix_UnixServerSocket */ + +#ifndef _Included_cx_ath_matthew_unix_UnixServerSocket +#define _Included_cx_ath_matthew_unix_UnixServerSocket +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: cx_ath_matthew_unix_UnixServerSocket + * Method: native_bind + * Signature: (Ljava/lang/String;Z)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_UnixServerSocket_native_1bind + (JNIEnv *, jobject, jstring, jboolean); + +/* + * Class: cx_ath_matthew_unix_UnixServerSocket + * Method: native_close + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_cx_ath_matthew_unix_UnixServerSocket_native_1close + (JNIEnv *, jobject, jint); + +/* + * Class: cx_ath_matthew_unix_UnixServerSocket + * Method: native_accept + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_UnixServerSocket_native_1accept + (JNIEnv *, jobject, jint); + +#ifdef __cplusplus +} +#endif +#endif +/* Header for class cx_ath_matthew_unix_UnixSocket */ + +#ifndef _Included_cx_ath_matthew_unix_UnixSocket +#define _Included_cx_ath_matthew_unix_UnixSocket +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: cx_ath_matthew_unix_UnixSocket + * Method: native_set_pass_cred + * Signature: (IZ)V + */ +JNIEXPORT void JNICALL Java_cx_ath_matthew_unix_UnixSocket_native_1set_1pass_1cred + (JNIEnv *, jobject, jint, jboolean); + +/* + * Class: cx_ath_matthew_unix_UnixSocket + * Method: native_connect + * Signature: (Ljava/lang/String;Z)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_UnixSocket_native_1connect + (JNIEnv *, jobject, jstring, jboolean); + +/* + * Class: cx_ath_matthew_unix_UnixSocket + * Method: native_close + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_cx_ath_matthew_unix_UnixSocket_native_1close + (JNIEnv *, jobject, jint); + +#ifdef __cplusplus +} +#endif +#endif +/* Header for class cx_ath_matthew_unix_USInputStream */ + +#ifndef _Included_cx_ath_matthew_unix_USInputStream +#define _Included_cx_ath_matthew_unix_USInputStream +#ifdef __cplusplus +extern "C" { +#endif +#undef cx_ath_matthew_unix_USInputStream_SKIP_BUFFER_SIZE +#define cx_ath_matthew_unix_USInputStream_SKIP_BUFFER_SIZE 2048L +/* + * Class: cx_ath_matthew_unix_USInputStream + * Method: native_recv + * Signature: (I[BII)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_USInputStream_native_1recv + (JNIEnv *, jobject, jint, jbyteArray, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif +/* Header for class cx_ath_matthew_unix_USOutputStream */ + +#ifndef _Included_cx_ath_matthew_unix_USOutputStream +#define _Included_cx_ath_matthew_unix_USOutputStream +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: cx_ath_matthew_unix_USOutputStream + * Method: native_send + * Signature: (I[BII)I + */ +JNIEXPORT jint JNICALL Java_cx_ath_matthew_unix_USOutputStream_native_1send + (JNIEnv *, jobject, jint, jbyteArray, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/app/src/main/java/cx/ath/matthew/unix/testclient.java b/app/src/main/java/cx/ath/matthew/unix/testclient.java new file mode 100644 index 00000000..f390b4a9 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/testclient.java @@ -0,0 +1,51 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.unix; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; + +public class testclient +{ + public static void main(String args[]) throws IOException + { + UnixSocket s = new UnixSocket(new UnixSocketAddress("testsock", true)); + OutputStream os = s.getOutputStream(); + PrintWriter o = new PrintWriter(os); + BufferedReader r = new BufferedReader(new InputStreamReader(System.in)); + String l; + while (null != (l = r.readLine())) { + byte[] buf = (l+"\n").getBytes(); + os.write(buf, 0, buf.length); + } + s.close(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/unix/testserver.java b/app/src/main/java/cx/ath/matthew/unix/testserver.java new file mode 100644 index 00000000..5e1d4167 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/unix/testserver.java @@ -0,0 +1,55 @@ +/* + * Java Unix Sockets Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.unix; + +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; + +public class testserver +{ + public static void main(String args[]) throws IOException + { + UnixServerSocket ss = new UnixServerSocket(new UnixSocketAddress("testsock", true)); + UnixSocket s = ss.accept(); + BufferedReader r = new BufferedReader(new InputStreamReader(s.getInputStream())); + String l; + while (null != (l = r.readLine())) + System.out.println(l);/* + InputStream is = s.getInputStream(); + int r; + do { + r = is.read(); + System.out.print((char)r); + } while (-1 != r);*/ + s.close(); + ss.close(); + } +} diff --git a/app/src/main/java/cx/ath/matthew/utils/Hexdump.java b/app/src/main/java/cx/ath/matthew/utils/Hexdump.java new file mode 100644 index 00000000..02cf5c65 --- /dev/null +++ b/app/src/main/java/cx/ath/matthew/utils/Hexdump.java @@ -0,0 +1,149 @@ +/* + * Java Hexdump Library + * + * Copyright (c) Matthew Johnson 2005 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * To Contact the author, please email src@matthew.ath.cx + * + */ + +package cx.ath.matthew.utils; + +import java.io.PrintStream; + +public class Hexdump +{ + public static final char[] hexchars = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + public static String toHex(byte[] buf) + { + return toHex(buf, 0, buf.length); + } + public static String toHex(byte[] buf, int ofs, int len) + { + StringBuffer sb = new StringBuffer(); + int j = ofs+len; + for (int i = ofs; i < j; i++) { + if (i < buf.length) { + sb.append(hexchars[(buf[i] & 0xF0) >> 4]); + sb.append(hexchars[buf[i] & 0x0F]); + sb.append(' '); + } else { + sb.append(' '); + sb.append(' '); + sb.append(' '); + } + } + return sb.toString(); + } + + public static String toAscii(byte[] buf) + { + return toAscii(buf, 0, buf.length); + } + public static String toAscii(byte[] buf, int ofs, int len) + { + StringBuffer sb = new StringBuffer(); + int j = ofs+len; + for (int i = ofs; i < j ; i++) { + if (i < buf.length) { + if (20 <= buf[i] && 126 >= buf[i]) + sb.append((char) buf[i]); + else + sb.append('.'); + } else + sb.append(' '); + } + return sb.toString(); + } + public static String format(byte[] buf) + { + return format(buf, 80); + } + public static String format(byte[] buf, int width) + { + int bs = (width - 8) / 4; + int i = 0; + StringBuffer sb = new StringBuffer(); + do { + for (int j = 0; j < 6; j++) { + sb.append(hexchars[(i << (j*4) & 0xF00000) >> 20]); + } + sb.append('\t'); + sb.append(toHex(buf, i, bs)); + sb.append(' '); + sb.append(toAscii(buf, i, bs)); + sb.append('\n'); + i += bs; + } while (i < buf.length); + return sb.toString(); + } + public static void print(byte[] buf) + { + print(buf, System.err); + } + public static void print(byte[] buf, int width) + { + print(buf, width, System.err); + } + public static void print(byte[] buf, int width, PrintStream out) + { + out.print(format(buf, width)); + } + public static void print(byte[] buf, PrintStream out) + { + out.print(format(buf)); + } + /** + * Returns a string which can be written to a Java source file as part + * of a static initializer for a byte array. + * Returns data in the format 0xAB, 0xCD, .... + * use like: + * javafile.print("byte[] data = {") + * javafile.print(Hexdump.toByteArray(data)); + * javafile.println("};"); + */ + public static String toByteArray(byte[] buf) + { + return toByteArray(buf, 0, buf.length); + } + /** + * Returns a string which can be written to a Java source file as part + * of a static initializer for a byte array. + * Returns data in the format 0xAB, 0xCD, .... + * use like: + * javafile.print("byte[] data = {") + * javafile.print(Hexdump.toByteArray(data)); + * javafile.println("};"); + */ + public static String toByteArray(byte[] buf, int ofs, int len) + { + StringBuffer sb = new StringBuffer(); + for (int i = ofs; i < len && i < buf.length; i++) { + sb.append('0'); + sb.append('x'); + sb.append(hexchars[(buf[i] & 0xF0) >> 4]); + sb.append(hexchars[buf[i] & 0x0F]); + if ((i+1) < len && (i+1) < buf.length) + sb.append(','); + } + return sb.toString(); + } +} diff --git a/app/src/main/java/org/asteroidos/sync/MainActivity.java b/app/src/main/java/org/asteroidos/sync/MainActivity.java index e44d7b7b..09243bc3 100644 --- a/app/src/main/java/org/asteroidos/sync/MainActivity.java +++ b/app/src/main/java/org/asteroidos/sync/MainActivity.java @@ -419,16 +419,20 @@ private void btEnableAndScan() { } } + private boolean isBound = false; + @Override protected void onResume() { super.onResume(); - bindService(mSyncServiceIntent, mConnection, Context.BIND_AUTO_CREATE); + isBound = bindService(mSyncServiceIntent, mConnection, Context.BIND_AUTO_CREATE); } @Override protected void onPause() { super.onPause(); - unbindService(mConnection); + if (isBound) { + unbindService(mConnection); + } } @Override diff --git a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java index abbdb668..8dc0007d 100644 --- a/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java +++ b/app/src/main/java/org/asteroidos/sync/asteroid/AsteroidBleManager.java @@ -50,7 +50,7 @@ public class AsteroidBleManager extends BleManager { public final HashMap recvCallbacks; public HashMap sendingCharacteristics; - private int currentMtu = 244; + private int currentMtu = 247; public AsteroidBleManager(@NonNull final Context context, SynchronizationService syncService) { super(context); @@ -162,7 +162,7 @@ public final boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gat @Override protected final void initialize() { beginAtomicRequestQueue() - .add(requestMtu(517) + .add(requestMtu(currentMtu) .with((device, mtu) -> log(Log.INFO, "MTU set to " + mtu)) .fail((device, status) -> log(Log.WARN, "Requested MTU not supported: " + status))) .done(device -> log(Log.INFO, "Target initialized")) diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java b/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java index 676eee65..5eda949e 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/MediaService.java @@ -166,6 +166,8 @@ public void sync() { onActiveSessionsChanged(controllers); if (mMediaSessionManager != null) { mMediaSessionManager.addOnActiveSessionsChangedListener(this, new ComponentName(mCtx, NLService.class)); + } else { + Log.e("MediaSessionManager", "missing MediaSessionManager"); } }); } catch (SecurityException e) { diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java b/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java index de8c71ab..7d716424 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/NotificationService.java @@ -1,6 +1,6 @@ /* * AsteroidOSSync - * Copyright (c) 2023 AsteroidOS + * Copyright (c) 2024 AsteroidOS * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,13 +26,14 @@ import org.asteroidos.sync.NotificationPreferences; import org.asteroidos.sync.asteroid.IAsteroidDevice; import org.asteroidos.sync.dataobjects.Notification; +import org.asteroidos.sync.services.INotificationHandler; import org.asteroidos.sync.utils.AsteroidUUIDS; import java.util.HashMap; import java.util.Objects; import java.util.UUID; -public class NotificationService implements IConnectivityService { +public class NotificationService implements IConnectivityService, INotificationHandler { public static final String TAG = NotificationService.class.toString(); private final Context mCtx; @@ -82,56 +83,61 @@ public final UUID getServiceUUID() { return AsteroidUUIDS.NOTIFICATION_SERVICE_UUID; } + @Override + public void postNotification(Context context, Intent intent) { + String event = intent.getStringExtra("event"); + if (Objects.equals(event, "posted")) { + String packageName = intent.getStringExtra("packageName"); + NotificationPreferences.putPackageToSeen(context, packageName); + NotificationPreferences.NotificationOption notificationOption = + NotificationPreferences.getNotificationPreferenceForApp(context, packageName); + if (notificationOption == NotificationPreferences.NotificationOption.NO_NOTIFICATIONS) + return; + + int id = intent.getIntExtra("id", 0); + String appName = intent.getStringExtra("appName"); + String appIcon = intent.getStringExtra("appIcon"); + String summary = intent.getStringExtra("summary"); + String body = intent.getStringExtra("body"); + String vibration; + if (notificationOption == NotificationPreferences.NotificationOption.SILENT_NOTIFICATION) + vibration = "none"; + else if (notificationOption == null + || notificationOption == NotificationPreferences.NotificationOption.NORMAL_VIBRATION + || notificationOption == NotificationPreferences.NotificationOption.DEFAULT) + vibration = "normal"; + else if (notificationOption == NotificationPreferences.NotificationOption.STRONG_VIBRATION) + vibration = "strong"; + else if(notificationOption == NotificationPreferences.NotificationOption.RINGTONE_VIBRATION) + vibration = "ringtone"; + else + throw new IllegalArgumentException("Not all options handled"); + + if(intent.hasExtra("vibration")) + vibration = intent.getStringExtra("vibration"); + + Notification notification = new Notification( + Notification.MsgType.POSTED, + packageName, + id, + appName, + appIcon, + summary, + body, + vibration); + + mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, notification.toBytes(), NotificationService.this); + } else if (Objects.equals(event, "removed")) { + int id = intent.getIntExtra("id", 0); + + mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, new Notification(Notification.MsgType.REMOVED, id).toBytes(), NotificationService.this); + } + } + class NotificationReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - String event = intent.getStringExtra("event"); - if (Objects.equals(event, "posted")) { - String packageName = intent.getStringExtra("packageName"); - NotificationPreferences.putPackageToSeen(context, packageName); - NotificationPreferences.NotificationOption notificationOption = - NotificationPreferences.getNotificationPreferenceForApp(context, packageName); - if (notificationOption == NotificationPreferences.NotificationOption.NO_NOTIFICATIONS) - return; - - int id = intent.getIntExtra("id", 0); - String appName = intent.getStringExtra("appName"); - String appIcon = intent.getStringExtra("appIcon"); - String summary = intent.getStringExtra("summary"); - String body = intent.getStringExtra("body"); - String vibration; - if (notificationOption == NotificationPreferences.NotificationOption.SILENT_NOTIFICATION) - vibration = "none"; - else if (notificationOption == null - || notificationOption == NotificationPreferences.NotificationOption.NORMAL_VIBRATION - || notificationOption == NotificationPreferences.NotificationOption.DEFAULT) - vibration = "normal"; - else if (notificationOption == NotificationPreferences.NotificationOption.STRONG_VIBRATION) - vibration = "strong"; - else if(notificationOption == NotificationPreferences.NotificationOption.RINGTONE_VIBRATION) - vibration = "ringtone"; - else - throw new IllegalArgumentException("Not all options handled"); - - if(intent.hasExtra("vibration")) - vibration = intent.getStringExtra("vibration"); - - Notification notification = new Notification( - Notification.MsgType.POSTED, - packageName, - id, - appName, - appIcon, - summary, - body, - vibration); - - mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, notification.toBytes(), NotificationService.this); - } else if (Objects.equals(event, "removed")) { - int id = intent.getIntExtra("id", 0); - - mDevice.send(AsteroidUUIDS.NOTIFICATION_UPDATE_CHAR, new Notification(Notification.MsgType.REMOVED, id).toBytes(), NotificationService.this); - } + postNotification(context, intent); } } } diff --git a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java index 47305a15..6a3487e7 100644 --- a/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java +++ b/app/src/main/java/org/asteroidos/sync/connectivity/SlirpService.java @@ -19,20 +19,28 @@ package org.asteroidos.sync.connectivity; import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; import android.system.Os; import android.system.OsConstants; import android.system.StructPollfd; import android.util.Log; import org.asteroidos.sync.asteroid.IAsteroidDevice; +import org.asteroidos.sync.dbus.IDBusConnectionCallback; +import org.asteroidos.sync.dbus.IDBusConnectionProvider; import org.asteroidos.sync.utils.AsteroidUUIDS; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; import java.io.FileDescriptor; import java.nio.ByteBuffer; +import java.text.ParseException; import java.util.HashMap; import java.util.UUID; +import java.util.function.Consumer; -public class SlirpService implements IConnectivityService { +public class SlirpService implements IConnectivityService, IDBusConnectionProvider { private final IAsteroidDevice mDevice; @@ -40,12 +48,48 @@ public class SlirpService implements IConnectivityService { private final Thread slirpThread; + private final HandlerThread dBusHandlerThread; + + private final Handler dBusHandler; + private volatile int mtu; + private final ByteBuffer rx = ByteBuffer.allocateDirect(1500); + + private final ByteBuffer tx = ByteBuffer.allocateDirect(1500); + + private final Consumer dBusConnectionRunnable = dBusConnectionCallback -> { + DBusConnection connection = null; + try { + synchronized (DBusConnection.class) { + connection = DBusConnection.getConnection("tcp:host=127.0.0.1,bind=*,port=55556,family=ipv4"); + try { + Log.i("SlirpService", "D-Bus connection acquired: " + connection.getAddress().toString()); + } catch (ParseException e) { + Log.i("SlirpService", "D-Bus connection acquired"); + } + } + } catch (Throwable e) { + Log.e("SlirpService", "Failed to connect to D-Bus", e); + } + if (connection != null) { + try { + dBusConnectionCallback.handleConnection(connection); + } catch (Throwable e) { + Log.e("SlirpService", "An error occurred in a D-Bus callback", e); + } + } + }; + + public SlirpService(Context ctx, IAsteroidDevice device) { mDevice = device; mCtx = ctx; + dBusHandlerThread = new HandlerThread("D-Bus Connection"); + dBusHandlerThread.start(); + dBusHandler = new Handler(dBusHandlerThread.getLooper()); + slirpThread = new Thread(() -> { FileDescriptor fd = getVdeFd(); StructPollfd pollfd = new StructPollfd(); @@ -58,41 +102,59 @@ public SlirpService(Context ctx, IAsteroidDevice device) { continue; } - ByteBuffer rx = ByteBuffer.allocateDirect(mtu); - long read = vdeRecv(rx, 0, mtu); - byte[] data = new byte[(int) read]; - rx.get(data); - mDevice.send(AsteroidUUIDS.SLIRP_OUTGOING_CHAR, data, SlirpService.this); - -// resetMtu(); + synchronized (SlirpService.this) { + rx.clear(); + long read = vdeRecv(rx, 0, mtu - 3); + assert read <= (mtu - 3); + if (read > 0) { + Log.d("SlirpService", "Received " + read + " bytes"); + byte[] data = new byte[(int) read]; + rx.get(data); + mDevice.send(AsteroidUUIDS.SLIRP_OUTGOING_CHAR, data, SlirpService.this); + } else { + Log.e("SlirpService", "Read error: " + read); + } + } } catch (Exception e) { - Log.e("SlirpService", e.toString()); + Log.e("SlirpService", "Poller exception", e); } } }); mtu = mDevice.getMtu(); - initNative(mtu - 14); + startNative(mtu - 3); mDevice.registerCallback(AsteroidUUIDS.SLIRP_INCOMING_CHAR, data -> { resetMtu(); - ByteBuffer tx = ByteBuffer.allocateDirect(data.length); - tx.put(data); - vdeSend(tx, 0, data.length); + synchronized (SlirpService.this) { + tx.clear(); + tx.put(data); + vdeSend(tx, 0, data.length); + } }); slirpThread.start(); } + private void startNative(int mtu) { + initNative(mtu - 14); + + vdeAddFwd(false, "0.0.0.0", 45722, "10.0.2.3", 22); + vdeAddFwd(false, "0.0.0.0", 55555, "10.0.2.3", 55555); + vdeAddFwd(false, "0.0.0.0", 55556, "10.0.2.3", 55556); + } + private void resetMtu() { - int newMtu = mDevice.getMtu(); - if (mtu != newMtu) { - mtu = newMtu; + synchronized (SlirpService.this) { + int newMtu = mDevice.getMtu(); + if (mtu != newMtu) { + mtu = newMtu; - finalizeNative(); - initNative(mtu - 14); + finalizeNative(); + startNative(mtu - 3); + } } } @@ -127,8 +189,22 @@ public UUID getServiceUUID() { return AsteroidUUIDS.SLIRP_SERVICE_UUID; } + public void acquireDBusConnection(IDBusConnectionCallback dBusConnectionCallback) { + dBusHandler.post(() -> dBusConnectionRunnable.accept(dBusConnectionCallback)); + } + + @Override + public void acquireDBusConnectionLater(IDBusConnectionCallback dBusConnectionCallback, long delay) { + dBusHandler.postDelayed(() -> dBusConnectionRunnable.accept(dBusConnectionCallback), delay); + } + + @SuppressWarnings({"unused", "FieldMayBeFinal"}) // used internally by the JNI part private long mySlirp = 0; + private native int vdeAddUnixFwd(String path, String ip, int port); + + private native int vdeAddFwd(boolean udp, String hostip, int hostport, String ip, int port); + private native void initNative(int mtu); private native void finalizeNative(); diff --git a/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java b/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java new file mode 100644 index 00000000..5b263441 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/DBusNotificationService.java @@ -0,0 +1,169 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.util.Log; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + +import org.asteroidos.sync.NotificationPreferences; +import org.freedesktop.Notifications; +import org.asteroidos.sync.services.INotificationHandler; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.Variant; + +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class DBusNotificationService implements INotificationHandler, DBusSigHandler { + private final IDBusConnectionProvider connectionProvider; + + private static final HashFunction murmur32 = Hashing.murmur3_32_fixed(0); + + private final BiMap mapping = HashBiMap.create(); + + private Context mCtx; + + private NotificationReceiver mNReceiver; + + public DBusNotificationService(Context ctx, IDBusConnectionProvider connectionProvider) { + this.mCtx = ctx; + this.connectionProvider = connectionProvider; + + connectionProvider.acquireDBusConnectionLater(notify -> Log.w("DBusNotificationService", Arrays.toString(notify.getNames())), 1000); + } + + @Override + public void postNotification(Context context, Intent intent) { + String event = intent.getStringExtra("event"); + + if (Objects.equals(event, "posted")) { + String packageName = intent.getStringExtra("packageName"); + NotificationPreferences.putPackageToSeen(context, packageName); + NotificationPreferences.NotificationOption notificationOption = + NotificationPreferences.getNotificationPreferenceForApp(context, packageName); + if (notificationOption == NotificationPreferences.NotificationOption.NO_NOTIFICATIONS) + return; + + String key = Objects.requireNonNull(intent.getStringExtra("key")); + String appName = intent.getStringExtra("appName"); + String appIcon = intent.getStringExtra("appIcon"); + String summary = intent.getStringExtra("summary"); + String body = intent.getStringExtra("body"); + String vibration; + if (notificationOption == NotificationPreferences.NotificationOption.SILENT_NOTIFICATION) + vibration = "notif_silent"; + else if (notificationOption == null + || notificationOption == NotificationPreferences.NotificationOption.NORMAL_VIBRATION + || notificationOption == NotificationPreferences.NotificationOption.DEFAULT) + vibration = "notif_normal"; + else if (notificationOption == NotificationPreferences.NotificationOption.STRONG_VIBRATION) + vibration = "notif_strong"; + else if (notificationOption == NotificationPreferences.NotificationOption.RINGTONE_VIBRATION) + vibration = "ringtone"; + else + throw new IllegalArgumentException("Not all options handled"); + + connectionProvider.acquireDBusConnectionLater(notify -> { + synchronized (mapping) { + mapping.put(key, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class) + .Notify(appName, mapping.getOrDefault(key, new UInt32(0)), appIcon, summary, body, Collections.emptyList(), + Map.of( + "x-nemo-feedback", new Variant<>(vibration), + "x-nemo-preview-body", new Variant<>(body), + "x-nemo-preview-summary", new Variant<>(summary), + "urgency", new Variant<>((byte) 3)), 0)); + } + }, 500); + } else if (Objects.equals(event, "removed")) { + String key = Objects.requireNonNull(intent.getStringExtra("key")); + // Avoid an infinite loop when the user dismisses the notification on the watch + if (mapping.containsKey(key)) { + UInt32 id; + synchronized (mapping) { + id = mapping.get(key); + mapping.remove(key); + } + connectionProvider.acquireDBusConnectionLater(notify -> notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class) + .CloseNotification(id), 500); + } + } + } + + @Override + public void handle(Notifications.NotificationClosed s) { + synchronized (mapping) { + if (s.reason == Notifications.NotificationClosed.REASON_DISMISSED_BY_USER + && mapping.containsValue(s.id)) { + Intent dismiss = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE"); + dismiss.putExtra("command", "dismiss"); + dismiss.putExtra("key", mapping.inverse().get(s.id)); + + mapping.inverse().remove(s.id); + mCtx.sendBroadcast(dismiss); + } + } + } + + @Override + public void sync() { + if (mNReceiver == null) { + IntentFilter filter = new IntentFilter(); + filter.addAction("org.asteroidos.sync.NOTIFICATION_LISTENER"); + mNReceiver = new NotificationReceiver(); + mCtx.registerReceiver(mNReceiver, filter); + + Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER_SERVICE"); + i.putExtra("command", "refresh"); + mCtx.sendBroadcast(i); + } + connectionProvider.acquireDBusConnection(notify -> notify.addSigHandler(Notifications.NotificationClosed.class, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class), DBusNotificationService.this)); + } + + @Override + public void unsync() { + connectionProvider.acquireDBusConnection(notify -> notify.removeSigHandler(Notifications.NotificationClosed.class, notify.getRemoteObject("org.freedesktop.Notifications", "/org/freedesktop/Notifications", Notifications.class), DBusNotificationService.this)); + if (mNReceiver != null) { + try { + mCtx.unregisterReceiver(mNReceiver); + } catch (IllegalArgumentException ignored) { + } + mNReceiver = null; + } + } + + class NotificationReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + postNotification(context, intent); + } + } +} diff --git a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java new file mode 100644 index 00000000..146b60a7 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionCallback.java @@ -0,0 +1,27 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus; + +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusException; + +public interface IDBusConnectionCallback { + + public void handleConnection(DBusConnection connection) throws DBusException; +} diff --git a/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java new file mode 100644 index 00000000..88e144a7 --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/dbus/IDBusConnectionProvider.java @@ -0,0 +1,25 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.dbus; + +public interface IDBusConnectionProvider { + public void acquireDBusConnection(IDBusConnectionCallback dBusConnectionConsumer); + + public void acquireDBusConnectionLater(IDBusConnectionCallback dBusConnectionConsumer, long delay); +} diff --git a/app/src/main/java/org/asteroidos/sync/services/INotificationHandler.java b/app/src/main/java/org/asteroidos/sync/services/INotificationHandler.java new file mode 100644 index 00000000..57f3d48c --- /dev/null +++ b/app/src/main/java/org/asteroidos/sync/services/INotificationHandler.java @@ -0,0 +1,28 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.asteroidos.sync.services; + +import android.content.Context; +import android.content.Intent; + +import org.asteroidos.sync.connectivity.IService; + +public interface INotificationHandler extends IService { + void postNotification(Context context, Intent intent); +} diff --git a/app/src/main/java/org/asteroidos/sync/services/NLService.java b/app/src/main/java/org/asteroidos/sync/services/NLService.java index d8626c73..a1104861 100644 --- a/app/src/main/java/org/asteroidos/sync/services/NLService.java +++ b/app/src/main/java/org/asteroidos/sync/services/NLService.java @@ -33,11 +33,17 @@ import androidx.core.app.NotificationCompat; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; + import org.asteroidos.sync.utils.NotificationParser; +import java.nio.charset.Charset; import java.util.Arrays; +import java.util.HashMap; import java.util.Hashtable; import java.util.Map; +import java.util.Objects; import java.util.concurrent.TimeUnit; public class NLService extends NotificationListenerService { @@ -157,7 +163,7 @@ public void onDestroy() { @Override public void onNotificationPosted(StatusBarNotification sbn) { - Notification notification = sbn.getNotification(); + final Notification notification = sbn.getNotification(); String packageName = sbn.getPackageName(); String[] allowedOngoingApps = {"com.google.android.apps.maps", "org.thoughtcrime.securesms"}; @@ -171,7 +177,7 @@ public void onNotificationPosted(StatusBarNotification sbn) { NotificationParser notifParser = new NotificationParser(notification); String summary = notifParser.summary; String body = notifParser.body; - int id = sbn.getId(); + String key = sbn.getKey(); String appIcon = iconFromPackage.get(packageName); String appName = ""; @@ -192,7 +198,7 @@ public void onNotificationPosted(StatusBarNotification sbn) { Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); i.putExtra("event", "posted"); i.putExtra("packageName", packageName); - i.putExtra("id", id); + i.putExtra("key", key); i.putExtra("appName", appName); i.putExtra("appIcon", appIcon); i.putExtra("summary", summary); @@ -205,7 +211,7 @@ public void onNotificationPosted(StatusBarNotification sbn) { public void onNotificationRemoved(StatusBarNotification sbn) { Intent i = new Intent("org.asteroidos.sync.NOTIFICATION_LISTENER"); i.putExtra("event", "removed"); - i.putExtra("id", sbn.getId()); + i.putExtra("key", sbn.getKey()); sendBroadcast(i); } @@ -225,7 +231,7 @@ public void onListenerConnected() { class NLServiceReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - if (intent.getStringExtra("command").equals("refresh")) { + if (Objects.equals(intent.getStringExtra("command"), "refresh")) { Handler handler = new Handler(); handler.postDelayed(() -> { while (!listenerConnected) { @@ -240,6 +246,11 @@ public void onReceive(Context context, Intent intent) { for (StatusBarNotification notif : notifs) onNotificationPosted(notif); }, 500); + } else if (Objects.equals(intent.getStringExtra("command"), "dismiss")) { + String key = intent.getStringExtra("key"); + if (key != null) { + new Handler().post(() -> cancelNotification(key)); + } } } } diff --git a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java index 9a3f43c7..da8e9d1f 100644 --- a/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java +++ b/app/src/main/java/org/asteroidos/sync/services/SynchronizationService.java @@ -48,12 +48,12 @@ import org.asteroidos.sync.connectivity.IService; import org.asteroidos.sync.connectivity.IServiceCallback; import org.asteroidos.sync.connectivity.MediaService; -import org.asteroidos.sync.connectivity.NotificationService; import org.asteroidos.sync.connectivity.ScreenshotService; import org.asteroidos.sync.connectivity.SilentModeService; import org.asteroidos.sync.connectivity.SlirpService; import org.asteroidos.sync.connectivity.TimeService; import org.asteroidos.sync.connectivity.WeatherService; +import org.asteroidos.sync.dbus.DBusNotificationService; import java.util.ArrayList; import java.util.Arrays; @@ -281,17 +281,20 @@ public void onCreate() { mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(defaultDevMacAddr); } - if (nonBleServices.isEmpty()) - nonBleServices.add(new SilentModeService(getApplicationContext())); + SlirpService slirpService = new SlirpService(getApplicationContext(), this); if (bleServices.isEmpty()) { // Register Services registerBleService(new MediaService(getApplicationContext(), this)); - registerBleService(new NotificationService(getApplicationContext(), this)); registerBleService(new WeatherService(getApplicationContext(), this)); registerBleService(new ScreenshotService(getApplicationContext(), this)); registerBleService(new TimeService(getApplicationContext(), this)); - registerBleService(new SlirpService(getApplicationContext(), this)); + registerBleService(slirpService); + } + + if (nonBleServices.isEmpty()) { + nonBleServices.add(new SilentModeService(getApplicationContext())); + nonBleServices.add(new DBusNotificationService(getApplicationContext(), slirpService)); } handleConnect(); diff --git a/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java b/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java index aeccd41e..58cbc1c4 100644 --- a/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java +++ b/app/src/main/java/org/asteroidos/sync/utils/AppInfoHelper.java @@ -67,10 +67,14 @@ public static ArrayList getPackageInfo(Context context) } } else { - icon = Bitmap.createBitmap(apkIcon.getIntrinsicWidth(), apkIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(icon); - apkIcon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - apkIcon.draw(canvas); + if (apkIcon.getIntrinsicWidth() <= 0 || apkIcon.getIntrinsicHeight() <= 0) + icon = Bitmap.createBitmap(144, 144, Bitmap.Config.ARGB_8888); + else { + icon = Bitmap.createBitmap(apkIcon.getIntrinsicWidth(), apkIcon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(icon); + apkIcon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + apkIcon.draw(canvas); + } } } catch(ClassCastException ignored) {} AppInfo appInfo = new AppInfo(pinfo.packageName, diff --git a/app/src/main/java/org/freedesktop/DBus.java b/app/src/main/java/org/freedesktop/DBus.java new file mode 100644 index 00000000..5d99a648 --- /dev/null +++ b/app/src/main/java/org/freedesktop/DBus.java @@ -0,0 +1,490 @@ +/* + 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import java.util.Map; +import java.util.List; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.Tuple; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.UInt64; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +public interface DBus extends DBusInterface +{ + public static final int DBUS_NAME_FLAG_ALLOW_REPLACEMENT = 0x01; + public static final int DBUS_NAME_FLAG_REPLACE_EXISTING = 0x02; + public static final int DBUS_NAME_FLAG_DO_NOT_QUEUE = 0x04; + public static final int DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER = 1; + public static final int DBUS_REQUEST_NAME_REPLY_IN_QUEUE = 2; + public static final int DBUS_REQUEST_NAME_REPLY_EXISTS = 3; + public static final int DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER = 4; + public static final int DBUS_RELEASE_NAME_REPLY_RELEASED = 1; + public static final int DBUS_RELEASE_NAME_REPLY_NON_EXISTANT = 2; + public static final int DBUS_RELEASE_NAME_REPLY_NOT_OWNER = 3; + public static final int DBUS_START_REPLY_SUCCESS = 1; + public static final int DBUS_START_REPLY_ALREADY_RUNNING = 2; + /** + * All DBus Applications should respond to the Ping method on this interface + */ + public interface Peer extends DBusInterface + { + public void Ping(); + } + /** + * Objects can provide introspection data via this interface and method. + * See the Introspection Format. + */ + public interface Introspectable extends DBusInterface + { + /** + * @return The XML introspection data for this object + */ + public String Introspect(); + } + /** + * A standard properties interface. + */ + public interface Properties extends DBusInterface + { + /** + * Get the value for the given property. + * @param interface_name The interface this property is associated with. + * @param property_name The name of the property. + * @return The value of the property (may be any valid DBus type). + */ + public A Get (String interface_name, String property_name); + /** + * Set the value for the given property. + * @param interface_name The interface this property is associated with. + * @param property_name The name of the property. + * @param value The new value of the property (may be any valid DBus type). + */ + public void Set (String interface_name, String property_name, A value); + /** + * Get all properties and values. + * @param interface_name The interface the properties is associated with. + * @return The properties mapped to their values. + */ + public Map GetAll (String interface_name); + } + /** + * Messages generated locally in the application. + */ + public interface Local extends DBusInterface + { + public class Disconnected extends DBusSignal + { + public Disconnected(String path) throws DBusException + { + super(path); + } + } + } + + /** + * Initial message to register ourselves on the Bus. + * @return The unique name of this connection to the Bus. + */ + public String Hello(); + /** + * Lists all connected names on the Bus. + * @return An array of all connected names. + */ + public String[] ListNames(); + /** + * Determine if a name has an owner. + * @param name The name to query. + * @return true if the name has an owner. + */ + public boolean NameHasOwner(String name); + /** + * Get the connection unique name that owns the given name. + * @param name The name to query. + * @return The connection which owns the name. + */ + public String GetNameOwner(String name); + /** + * Get the Unix UID that owns a connection name. + * @param connection_name The connection name. + * @return The Unix UID that owns it. + */ + public UInt32 GetConnectionUnixUser(String connection_name); + /** + * Start a service. If the given service is not provided + * by any application, it will be started according to the .service file + * for that service. + * @param name The service name to start. + * @param flags Unused. + * @return DBUS_START_REPLY constants. + */ + public UInt32 StartServiceByName(String name, UInt32 flags); + /** + * Request a name on the bus. + * @param name The name to request. + * @param flags DBUS_NAME flags. + * @return DBUS_REQUEST_NAME_REPLY constants. + */ + public UInt32 RequestName(String name, UInt32 flags); + /** + * Release a name on the bus. + * @param name The name to release. + * @return DBUS_RELEASE_NAME_REPLY constants. + */ + public UInt32 ReleaseName(String name); + + /** + * Add a match rule. + * Will cause you to receive messages that aren't directed to you which + * match this rule. + * @param matchrule The Match rule as a string. Format Undocumented. + */ + public void AddMatch(String matchrule) throws Error.MatchRuleInvalid; + + /** + * Remove a match rule. + * Will cause you to stop receiving messages that aren't directed to you which + * match this rule. + * @param matchrule The Match rule as a string. Format Undocumented. + */ + public void RemoveMatch(String matchrule) throws Error.MatchRuleInvalid; + + /** + * List the connections currently queued for a name. + * @param name The name to query + * @return A list of unique connection IDs. + */ + public String[] ListQueuedOwners(String name); + + /** + * Returns the proccess ID associated with a connection. + * @param connection_name The name of the connection + * @return The PID of the connection. + */ + public UInt32 GetConnectionUnixProcessID(String connection_name); + + /** + * Does something undocumented. + */ + public Byte[] GetConnectionSELinuxSecurityContext(String a); + + /** + * Does something undocumented. + */ + public void ReloadConfig(); + + /** + * Signal sent when the owner of a name changes + */ + public class NameOwnerChanged extends DBusSignal + { + public final String name; + public final String old_owner; + public final String new_owner; + public NameOwnerChanged(String path, String name, String old_owner, String new_owner) throws DBusException + { + super(path, new Object[] { name, old_owner, new_owner }); + this.name = name; + this.old_owner = old_owner; + this.new_owner = new_owner; + } + } + /** + * Signal sent to a connection when it loses a name + */ + public class NameLost extends DBusSignal + { + public final String name; + public NameLost(String path, String name) throws DBusException + { + super(path, name); + this.name = name; + } + } + /** + * Signal sent to a connection when it aquires a name + */ + public class NameAcquired extends DBusSignal + { + public final String name; + public NameAcquired(String path, String name) throws DBusException + { + super(path, name); + this.name = name; + } + } + /** + * Contains standard errors that can be thrown from methods. + */ + public interface Error + { + /** + * Thrown if the method called was unknown on the remote object + */ + @SuppressWarnings("serial") + public class UnknownMethod extends DBusExecutionException + { + public UnknownMethod(String message) + { + super(message); + } + } + /** + * Thrown if the object was unknown on a remote connection + */ + @SuppressWarnings("serial") + public class UnknownObject extends DBusExecutionException + { + public UnknownObject(String message) + { + super(message); + } + } + /** + * Thrown if the requested service was not available + */ + @SuppressWarnings("serial") + public class ServiceUnknown extends DBusExecutionException + { + public ServiceUnknown(String message) + { + super(message); + } + } + /** + * Thrown if the match rule is invalid + */ + @SuppressWarnings("serial") + public class MatchRuleInvalid extends DBusExecutionException + { + public MatchRuleInvalid(String message) + { + super(message); + } + } + /** + * Thrown if there is no reply to a method call + */ + @SuppressWarnings("serial") + public class NoReply extends DBusExecutionException + { + public NoReply(String message) + { + super(message); + } + } + /** + * Thrown if a message is denied due to a security policy + */ + @SuppressWarnings("serial") + public class AccessDenied extends DBusExecutionException + { + public AccessDenied(String message) + { + super(message); + } + } + } + /** + * Description of the interface or method, returned in the introspection data + */ + @Retention(RetentionPolicy.RUNTIME) + public @interface Description + { + String value(); + } + /** + * Indicates that a DBus interface or method is deprecated + */ + @Retention(RetentionPolicy.RUNTIME) + public @interface Deprecated {} + /** + * Contains method-specific annotations + */ + public interface Method + { + /** + * Methods annotated with this do not send a reply + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface NoReply {} + /** + * Give an error that the method can return + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface Error + { + String value(); + } + } + /** + * Contains GLib-specific annotations + */ + public interface GLib + { + /** + * Define a C symbol to map to this method. Used by GLib only + */ + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface CSymbol + { + String value(); + } + } + /** + * Contains Binding-test interfaces + */ + public interface Binding + { + public interface SingleTests extends DBusInterface + { + @Description("Returns the sum of the values in the input list") + public UInt32 Sum(byte[] a); + } + public interface TestClient extends DBusInterface + { + @Description("when the trigger signal is received, this method should be called on the sending process/object.") + public void Response(UInt16 a, double b); + @Description("Causes a callback") + public static class Trigger extends DBusSignal + { + public final UInt16 a; + public final double b; + public Trigger(String path, UInt16 a, double b) throws DBusException + { + super(path, a, b); + this.a = a; + this.b = b; + } + } + + } + public interface Tests extends DBusInterface + { + @Description("Returns whatever it is passed") + public Variant Identity(Variant input); + @Description("Returns whatever it is passed") + public byte IdentityByte(byte input); + @Description("Returns whatever it is passed") + public boolean IdentityBool(boolean input); + @Description("Returns whatever it is passed") + public short IdentityInt16(short input); + @Description("Returns whatever it is passed") + public UInt16 IdentityUInt16(UInt16 input); + @Description("Returns whatever it is passed") + public int IdentityInt32(int input); + @Description("Returns whatever it is passed") + public UInt32 IdentityUInt32(UInt32 input); + @Description("Returns whatever it is passed") + public long IdentityInt64(long input); + @Description("Returns whatever it is passed") + public UInt64 IdentityUInt64(UInt64 input); + @Description("Returns whatever it is passed") + public double IdentityDouble(double input); + @Description("Returns whatever it is passed") + public String IdentityString(String input); + @Description("Returns whatever it is passed") + public Variant[] IdentityArray(Variant[] input); + @Description("Returns whatever it is passed") + public byte[] IdentityByteArray(byte[] input); + @Description("Returns whatever it is passed") + public boolean[] IdentityBoolArray(boolean[] input); + @Description("Returns whatever it is passed") + public short[] IdentityInt16Array(short[] input); + @Description("Returns whatever it is passed") + public UInt16[] IdentityUInt16Array(UInt16[] input); + @Description("Returns whatever it is passed") + public int[] IdentityInt32Array(int[] input); + @Description("Returns whatever it is passed") + public UInt32[] IdentityUInt32Array(UInt32[] input); + @Description("Returns whatever it is passed") + public long[] IdentityInt64Array(long[] input); + @Description("Returns whatever it is passed") + public UInt64[] IdentityUInt64Array(UInt64[] input); + @Description("Returns whatever it is passed") + public double[] IdentityDoubleArray(double[] input); + @Description("Returns whatever it is passed") + public String[] IdentityStringArray(String[] input); + @Description("Returns the sum of the values in the input list") + public long Sum(int[] a); + @Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B") + public Map> InvertMapping(Map a); + @Description("This method returns the contents of a struct as separate values") + public Triplet DeStruct(TestStruct a); + @Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants") + public List> Primitize(Variant a); + @Description("inverts it's input") + public boolean Invert(boolean a); + @Description("triggers sending of a signal from the supplied object with the given parameter") + public void Trigger(String a, UInt64 b); + @Description("Causes the server to exit") + public void Exit(); + } + public interface TestSignals extends DBusInterface + { + @Description("Sent in response to a method call") + public static class Triggered extends DBusSignal + { + public final UInt64 a; + public Triggered(String path, UInt64 a) throws DBusException + { + super(path, a); + this.a = a; + } + } + } + public final class Triplet extends Tuple + { + @Position(0) + public final A a; + @Position(1) + public final B b; + @Position(2) + public final C c; + public Triplet(A a, B b, C c) + { + this.a = a; + this.b = b; + this.c = c; + } + } + public final class TestStruct extends Struct + { + @Position(0) + public final String a; + @Position(1) + public final UInt32 b; + @Position(2) + public final Short c; + public TestStruct(String a, UInt32 b, Short c) + { + this.a = a; + this.b = b; + this.c = c; + } + } + } +} diff --git a/app/src/main/java/org/freedesktop/Notifications.java b/app/src/main/java/org/freedesktop/Notifications.java new file mode 100644 index 00000000..e873a5b2 --- /dev/null +++ b/app/src/main/java/org/freedesktop/Notifications.java @@ -0,0 +1,52 @@ +/* + * AsteroidOSSync + * Copyright (c) 2024 AsteroidOS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.freedesktop; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; +import java.util.Map; + +public interface Notifications extends DBusInterface { + + public static class NotificationClosed extends DBusSignal { + public static final int REASON_EXPIRED = 1; + public static final int REASON_DISMISSED_BY_USER = 2; + public static final int REASON_CLOSE_NOTIFICATION_CALLED = 3; + public static final int REASON_RESERVED = 4; + + public final UInt32 id; + public final int reason; + public NotificationClosed(String path, UInt32 id, UInt32 reason) throws DBusException { + super(path, id, reason); + this.id = id; + this.reason = reason.intValue(); + } + } + + public List GetCapabilities(); + + public UInt32 Notify(String app_name, UInt32 replaces_id, String app_icon, String summary, String body, List actions, Map hints, int expire_timeout); + + public void CloseNotification(UInt32 id); +} diff --git a/app/src/main/java/org/freedesktop/dbus/AbstractConnection.java b/app/src/main/java/org/freedesktop/dbus/AbstractConnection.java new file mode 100644 index 00000000..2b289cc4 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/AbstractConnection.java @@ -0,0 +1,1030 @@ +/* + 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.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; + +import java.io.File; +import java.io.IOException; + +import java.text.MessageFormat; +import java.text.ParseException; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Properties; +import java.util.Vector; + +import java.util.regex.Pattern; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.NotConnected; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.FatalDBusException; +import org.freedesktop.dbus.exceptions.FatalException; + +import cx.ath.matthew.debug.Debug; + + +/** Handles a connection to DBus. + */ +public abstract class AbstractConnection +{ + protected class FallbackContainer + { + private Map fallbacks = new HashMap(); + public synchronized void add(String path, ExportedObject eo) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Adding fallback on "+path+" of "+eo); + fallbacks.put(path.split("/"), eo); + } + public synchronized void remove(String path) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Removing fallback on "+path); + fallbacks.remove(path.split("/")); + } + public synchronized ExportedObject get(String path) + { + int best = 0; + int i = 0; + ExportedObject bestobject = null; + String[] pathel = path.split("/"); + for (String[] fbpath: fallbacks.keySet()) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Trying fallback path "+Arrays.deepToString(fbpath)+" to match "+Arrays.deepToString(pathel)); + for (i = 0; i < pathel.length && i < fbpath.length; i++) + if (!pathel[i].equals(fbpath[i])) break; + if (i > 0 && i == fbpath.length && i > best) + bestobject = fallbacks.get(fbpath); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Matches "+i+" bestobject now "+bestobject); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "Found fallback for "+path+" of "+bestobject); + return bestobject; + } + } + protected class _thread extends Thread + { + public _thread() + { + setName("DBusConnection"); + } + public void run() + { + try { + Message m = null; + while (_run) { + m = null; + + // read from the wire + try { + // this blocks on outgoing being non-empty or a message being available. + m = readIncoming(); + if (m != null) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Got Incoming Message: "+m); + synchronized (this) { notifyAll(); } + + if (m instanceof DBusSignal) + handleMessage((DBusSignal) m); + else if (m instanceof MethodCall) + handleMessage((MethodCall) m); + else if (m instanceof MethodReturn) + handleMessage((MethodReturn) m); + else if (m instanceof Error) + handleMessage((Error) m); + + m = null; + } + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + if (e instanceof FatalException) { + disconnect(); + } + } + + } + synchronized (this) { notifyAll(); } + } catch (Exception e) { + if (Debug.debug && EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); + } + } + } + private class _globalhandler implements org.freedesktop.DBus.Peer, org.freedesktop.DBus.Introspectable + { + private String objectpath; + public _globalhandler() + { + this.objectpath = null; + } + public _globalhandler(String objectpath) + { + this.objectpath = objectpath; + } + public boolean isRemote() { return false; } + public void Ping() { return; } + public String Introspect() + { + String intro = objectTree.Introspect(objectpath); + if (null == intro) { + ExportedObject eo = fallbackcontainer.get(objectpath); + if (null != eo) intro = eo.introspectiondata; + } + if (null == intro) + throw new DBus.Error.UnknownObject("Introspecting on non-existant object"); + else return + "\n"+intro; + } + } + protected class _workerthread extends Thread + { + private boolean _run = true; + public void halt() + { + _run = false; + } + public void run() + { + while (_run) { + Runnable r = null; + synchronized (runnables) { + while (runnables.size() == 0 && _run) + try { runnables.wait(); } catch (InterruptedException Ie) {} + if (runnables.size() > 0) + r = runnables.removeFirst(); + } + if (null != r) r.run(); + } + } + } + private class _sender extends Thread + { + public _sender() + { + setName("Sender"); + } + public void run() + { + Message m = null; + + if (Debug.debug) Debug.print(Debug.INFO, "Monitoring outbound queue"); + // block on the outbound queue and send from it + while (_run) { + if (null != outgoing) synchronized (outgoing) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking"); + while (outgoing.size() == 0 && _run) + try { outgoing.wait(); } catch (InterruptedException Ie) {} + if (Debug.debug) Debug.print(Debug.VERBOSE, "Notified"); + if (outgoing.size() > 0) + m = outgoing.remove(); + if (Debug.debug) Debug.print(Debug.DEBUG, "Got message: "+m); + } + if (null != m) + sendMessage(m); + m = null; + } + + if (Debug.debug) Debug.print(Debug.INFO, "Flushing outbound queue and quitting"); + // flush the outbound queue before disconnect. + if (null != outgoing) do { + EfficientQueue ogq = outgoing; + synchronized (ogq) { + outgoing = null; + } + if (!ogq.isEmpty()) + m = ogq.remove(); + else m = null; + sendMessage(m); + } while (null != m); + + // close the underlying streams + } + } + /** + * Timeout in us on checking the BUS for incoming messages and sending outgoing messages + */ + protected static final int TIMEOUT = 100000; + /** Initial size of the pending calls map */ + private static final int PENDING_MAP_INITIAL_SIZE = 10; + static final String BUSNAME_REGEX = "^[-_a-zA-Z][-_a-zA-Z0-9]*(\\.[-_a-zA-Z][-_a-zA-Z0-9]*)*$"; + static final String CONNID_REGEX = "^:[0-9]*\\.[0-9]*$"; + static final String OBJECT_REGEX = "^/([-_a-zA-Z0-9]+(/[-_a-zA-Z0-9]+)*)?$"; + static final byte THREADCOUNT = 4; + static final int MAX_ARRAY_LENGTH = 67108864; + static final int MAX_NAME_LENGTH = 255; + protected Map exportedObjects; + private ObjectTree objectTree; + private _globalhandler _globalhandlerreference; + protected Map importedObjects; + protected Map>> handledSignals; + protected EfficientMap pendingCalls; + protected Map> pendingCallbacks; + protected Map> pendingCallbackReplys; + protected LinkedList runnables; + protected LinkedList<_workerthread> workers; + protected FallbackContainer fallbackcontainer; + protected boolean _run; + EfficientQueue outgoing; + LinkedList pendingErrors; + private static final Map infomap = new HashMap(); + protected _thread thread; + protected _sender sender; + protected Transport transport; + protected String addr; + protected boolean weakreferences = false; + static final Pattern dollar_pattern = Pattern.compile("[$]"); + public static final boolean EXCEPTION_DEBUG; + static final boolean FLOAT_SUPPORT; + protected boolean connected = false; + static { + FLOAT_SUPPORT = (null != System.getenv("DBUS_JAVA_FLOATS")); + EXCEPTION_DEBUG = (null != System.getenv("DBUS_JAVA_EXCEPTION_DEBUG")); + if (EXCEPTION_DEBUG) { + Debug.print("Debugging of internal exceptions enabled"); + Debug.setThrowableTraces(true); + } + if (Debug.debug) { + File f = new File("debug.conf"); + if (f.exists()) { + Debug.print("Loading debug config file: "+f); + try { + Debug.loadConfig(f); + } catch (IOException IOe) {} + } else { + Properties p = new Properties(); + p.setProperty("ALL", "INFO"); + Debug.print("debug config file "+f+" does not exist, not loading."); + } + Debug.setHexDump(true); + } + } + + protected AbstractConnection(String address) throws DBusException + { + exportedObjects = new HashMap(); + importedObjects = new HashMap(); + _globalhandlerreference = new _globalhandler(); + synchronized (exportedObjects) { + exportedObjects.put(null, new ExportedObject(_globalhandlerreference, weakreferences)); + } + handledSignals = new HashMap>>(); + pendingCalls = new EfficientMap(PENDING_MAP_INITIAL_SIZE); + outgoing = new EfficientQueue(PENDING_MAP_INITIAL_SIZE); + pendingCallbacks = new HashMap>(); + pendingCallbackReplys = new HashMap>(); + pendingErrors = new LinkedList(); + runnables = new LinkedList(); + workers = new LinkedList<_workerthread>(); + objectTree = new ObjectTree(); + fallbackcontainer = new FallbackContainer(); + synchronized (workers) { + for (int i = 0; i < THREADCOUNT; i++) { + _workerthread t = new _workerthread(); + t.start(); + workers.add(t); + } + } + _run = true; + addr = address; + } + + protected void listen() + { + // start listening + thread = new _thread(); + thread.start(); + sender = new _sender(); + sender.start(); + } + + /** + * Change the number of worker threads to receive method calls and handle signals. + * Default is 4 threads + * @param newcount The new number of worker Threads to use. + */ + public void changeThreadCount(byte newcount) + { + synchronized (workers) { + if (workers.size() > newcount) { + int n = workers.size() - newcount; + for (int i = 0; i < n; i++) { + _workerthread t = workers.removeFirst(); + t.halt(); + } + } else if (workers.size() < newcount) { + int n = newcount-workers.size(); + for (int i = 0; i < n; i++) { + _workerthread t = new _workerthread(); + t.start(); + workers.add(t); + } + } + } + } + private void addRunnable(Runnable r) + { + synchronized(runnables) { + runnables.add(r); + runnables.notifyAll(); + } + } + + String getExportedObject(DBusInterface i) throws DBusException + { + synchronized (exportedObjects) { + for (String s: exportedObjects.keySet()) + if (i.equals(exportedObjects.get(s).object.get())) + return s; + } + + String s = importedObjects.get(i).objectpath; + if (null != s) return s; + + throw new DBusException("Not an object exported or imported by this connection"); + } + + abstract DBusInterface getExportedObject(String source, String path) throws DBusException; + + /** + * Returns a structure with information on the current method call. + * @return the DBusCallInfo for this method call, or null if we are not in a method call. + */ + public static DBusCallInfo getCallInfo() + { + DBusCallInfo info; + synchronized (infomap) { + info = infomap.get(Thread.currentThread()); + } + return info; + } + + /** + * If set to true the bus will not hold a strong reference to exported objects. + * If they go out of scope they will automatically be unexported from the bus. + * The default is to hold a strong reference, which means objects must be + * explicitly unexported before they will be garbage collected. + */ + public void setWeakReferences(boolean weakreferences) + { + this.weakreferences = weakreferences; + } + + /** + * Export an object so that its methods can be called on DBus. + * @param objectpath The path to the object we are exposing. MUST be in slash-notation, like "/org/freedesktop/Local", + * and SHOULD end with a capitalised term. Only one object may be exposed on each path at any one time, but an object + * may be exposed on several paths at once. + * @param object The object to export. + * @throws DBusException If the objectpath is already exporting an object. + * or if objectpath is incorrectly formatted, + */ + public void exportObject(String objectpath, DBusInterface object) throws DBusException + { + if (null == objectpath || "".equals(objectpath)) + throw new DBusException($_("Must Specify an Object Path")); + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + synchronized (exportedObjects) { + if (null != exportedObjects.get(objectpath)) + throw new DBusException($_("Object already exported")); + ExportedObject eo = new ExportedObject(object, weakreferences); + exportedObjects.put(objectpath, eo); + objectTree.add(objectpath, eo, eo.introspectiondata); + } + } + /** + * Export an object as a fallback object. + * This object will have it's methods invoked for all paths starting + * with this object path. + * @param objectprefix The path below which the fallback handles calls. + * MUST be in slash-notation, like "/org/freedesktop/Local", + * @param object The object to export. + * @throws DBusException If the objectpath is incorrectly formatted, + */ + public void addFallback(String objectprefix, DBusInterface object) throws DBusException + { + if (null == objectprefix || "".equals(objectprefix)) + throw new DBusException($_("Must Specify an Object Path")); + if (!objectprefix.matches(OBJECT_REGEX)||objectprefix.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectprefix); + ExportedObject eo = new ExportedObject(object, weakreferences); + fallbackcontainer.add(objectprefix, eo); + } + /** + * Remove a fallback + * @param objectprefix The prefix to remove the fallback for. + */ + public void removeFallback(String objectprefix) + { + fallbackcontainer.remove(objectprefix); + } + /** + * Stop Exporting an object + * @param objectpath The objectpath to stop exporting. + */ + public void unExportObject(String objectpath) + { + synchronized (exportedObjects) { + exportedObjects.remove(objectpath); + objectTree.remove(objectpath); + } + } + /** + * Return a reference to a remote object. + * This method will resolve the well known name (if given) to a unique bus name when you call it. + * This means that if a well known name is released by one process and acquired by another calls to + * objects gained from this method will continue to operate on the original process. + * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @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. + */ + /** + * Send a signal. + * @param signal The signal to send. + */ + public void sendSignal(DBusSignal signal) + { + queueOutgoing(signal); + } + void queueOutgoing(Message m) + { + synchronized (outgoing) { + if (null == outgoing) return; + outgoing.add(m); + if (Debug.debug) Debug.print(Debug.DEBUG, "Notifying outgoing thread"); + outgoing.notifyAll(); + } + } + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * @param type The signal to watch for. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + removeSigHandler(new DBusMatchRule(type), handler); + } + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * @param type The signal to watch for. + * @param object The object emitting the signal. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, DBusInterface object, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + removeSigHandler(new DBusMatchRule(type, null, objectpath), handler); + } + + protected abstract void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException; + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type and name. + * @param type The signal to watch for. + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + addSigHandler(new DBusMatchRule(type), (DBusSigHandler) handler); + } + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type, name and object. + * @param type The signal to watch for. + * @param object The object from which the signal will be emitted + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, DBusInterface object, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + addSigHandler(new DBusMatchRule(type, null, objectpath), (DBusSigHandler) handler); + } + + protected abstract void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException; + + protected void addSigHandlerWithoutMatch(Class signal, DBusSigHandler handler) throws DBusException + { + DBusMatchRule rule = new DBusMatchRule(signal); + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null == v) { + v = new Vector>(); + v.add(handler); + handledSignals.put(key, v); + } else + v.add(handler); + } + } + + /** + * Disconnect from the Bus. + */ + public void disconnect() + { + connected = false; + if (Debug.debug) Debug.print(Debug.INFO, "Sending disconnected signal"); + try { + handleMessage(new org.freedesktop.DBus.Local.Disconnected("/")); + } catch (Exception ee) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ee); + } + + if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Abstract Connection"); + // run all pending tasks. + while (runnables.size() > 0) + synchronized (runnables) { + runnables.notifyAll(); + } + + // stop the main thread + _run = false; + + // unblock the sending thread. + synchronized (outgoing) { + outgoing.notifyAll(); + } + + // disconnect from the trasport layer + try { + if (null != transport) { + transport.disconnect(); + transport = null; + } + } catch (IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + } + + // stop all the workers + synchronized(workers) { + for (_workerthread t: workers) + t.halt(); + } + + // make sure none are blocking on the runnables queue still + synchronized (runnables) { + runnables.notifyAll(); + } + } + + public void finalize() + { + disconnect(); + } + /** + * Return any DBus error which has been received. + * @return A DBusExecutionException, or null if no error is pending. + */ + public DBusExecutionException getError() + { + synchronized (pendingErrors) { + if (pendingErrors.size() == 0) return null; + else + return pendingErrors.removeFirst().getException(); + } + } + + /** + * Call a method asynchronously and set a callback. + * This handler will be called in a separate thread. + * @param object The remote object on which to call the method. + * @param m The name of the method on the interface to call. + * @param callback The callback handler. + * @param parameters The parameters to call the method with. + */ + @SuppressWarnings("unchecked") + public void callWithCallback(DBusInterface object, String m, CallbackHandler callback, Object... parameters) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "callWithCallback("+object+","+m+", "+callback); + Class[] types = new Class[parameters.length]; + for (int i = 0; i < parameters.length; i++) + types[i] = parameters[i].getClass(); + RemoteObject ro = importedObjects.get(object); + + try { + Method me; + if (null == ro.iface) + me = object.getClass().getMethod(m, types); + else + me = ro.iface.getMethod(m, types); + RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_CALLBACK, callback, parameters); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw DBEe; + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(e.getMessage()); + } + } + + /** + * Call a method asynchronously and get a handle with which to get the reply. + * @param object The remote object on which to call the method. + * @param m The name of the method on the interface to call. + * @param parameters The parameters to call the method with. + * @return A handle to the call. + */ + @SuppressWarnings("unchecked") + public DBusAsyncReply callMethodAsync(DBusInterface object, String m, Object... parameters) + { + Class[] types = new Class[parameters.length]; + for (int i = 0; i < parameters.length; i++) + types[i] = parameters[i].getClass(); + RemoteObject ro = importedObjects.get(object); + + try { + Method me; + if (null == ro.iface) + me = object.getClass().getMethod(m, types); + else + me = ro.iface.getMethod(m, types); + return (DBusAsyncReply) RemoteInvocationHandler.executeRemoteMethod(ro, me, this, RemoteInvocationHandler.CALL_TYPE_ASYNC, null, parameters); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw DBEe; + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(e.getMessage()); + } + } + + private void handleMessage(final MethodCall m) throws DBusException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method call: "+m); + + ExportedObject eo = null; + Method meth = null; + Object o = null; + + if (null == m.getInterface() || + m.getInterface().equals("org.freedesktop.DBus.Peer") || + m.getInterface().equals("org.freedesktop.DBus.Introspectable")) { + synchronized (exportedObjects) { + eo = exportedObjects.get(null); + } + if (null != eo && null == eo.object.get()) { + unExportObject(null); + eo = null; + } + if (null != eo) { + meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig())); + } + if (null != meth) + o = new _globalhandler(m.getPath()); + else + eo = null; + } + if (null == o) { + // now check for specific exported functions + + synchronized (exportedObjects) { + eo = exportedObjects.get(m.getPath()); + } + if (null != eo && null == eo.object.get()) { + if (Debug.debug) Debug.print(Debug.INFO, "Unexporting "+m.getPath()+" implicitly"); + unExportObject(m.getPath()); + eo = null; + } + + if (null == eo) { + eo = fallbackcontainer.get(m.getPath()); + } + + if (null == eo) { + try { + queueOutgoing(new Error(m, new DBus.Error.UnknownObject(m.getPath()+ $_(" is not an object provided by this process.")))); + } catch (DBusException DBe) {} + return; + } + if (Debug.debug) { + Debug.print(Debug.VERBOSE, "Searching for method "+m.getName()+" with signature "+m.getSig()); + Debug.print(Debug.VERBOSE, "List of methods on "+eo+":"); + for (MethodTuple mt: eo.methods.keySet()) + Debug.print(Debug.VERBOSE, " "+mt+" => "+eo.methods.get(mt)); + } + meth = eo.methods.get(new MethodTuple(m.getName(), m.getSig())); + if (null == meth) { + try { + queueOutgoing(new Error(m, new DBus.Error.UnknownMethod(MessageFormat.format($_("The method `{0}.{1}' does not exist on this object."), new Object[] { m.getInterface(), m.getName() })))); + } catch (DBusException DBe) {} + return; + } + o = eo.object.get(); + } + + // now execute it + final Method me = meth; + final Object ob = o; + final boolean noreply = (1 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED)); + final DBusCallInfo info = new DBusCallInfo(m); + final AbstractConnection conn = this; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for method "+meth); + addRunnable(new Runnable() + { + private boolean run = false; + public synchronized void run() + { + if (run) return; + run = true; + if (Debug.debug) Debug.print(Debug.DEBUG, "Running method "+me+" for remote call"); + try { + Type[] ts = me.getGenericParameterTypes(); + m.setArgs(Marshalling.deSerializeParameters(m.getParameters(), ts, conn)); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserialised "+Arrays.deepToString(m.getParameters())+" to types "+Arrays.deepToString(ts)); + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + try { + conn.queueOutgoing(new Error(m, new DBus.Error.UnknownMethod($_("Failure in de-serializing message: ")+e))); + } catch (DBusException DBe) {} + return; + } + + try { + synchronized (infomap) { + infomap.put(Thread.currentThread(), info); + } + Object result; + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Invoking Method: "+me+" on "+ob+" with parameters "+Arrays.deepToString(m.getParameters())); + result = me.invoke(ob, m.getParameters()); + } catch (InvocationTargetException ITe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, ITe.getCause()); + throw ITe.getCause(); + } + synchronized (infomap) { + infomap.remove(Thread.currentThread()); + } + if (!noreply) { + MethodReturn reply; + if (Void.TYPE.equals(me.getReturnType())) + reply = new MethodReturn(m, null); + else { + StringBuffer sb = new StringBuffer(); + for (String s: Marshalling.getDBusType(me.getGenericReturnType())) + sb.append(s); + Object[] nr = Marshalling.convertParameters(new Object[] { result }, new Type[] {me.getGenericReturnType()}, conn); + + reply = new MethodReturn(m, sb.toString(),nr); + } + conn.queueOutgoing(reply); + } + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + try { + conn.queueOutgoing(new Error(m, DBEe)); + } catch (DBusException DBe) {} + } catch (Throwable e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + try { + conn.queueOutgoing(new Error(m, new DBusExecutionException(MessageFormat.format($_("Error Executing Method {0}.{1}: {2}"), new Object[] { m.getInterface(), m.getName(), e.getMessage() })))); + } catch (DBusException DBe) {} + } + } + }); + } + @SuppressWarnings({"unchecked","deprecation"}) + private void handleMessage(final DBusSignal s) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming signal: "+s); + Vector> v = new Vector>(); + synchronized(handledSignals) { + Vector> t; + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, null)); + if (null != t) v.addAll(t); + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), null)); + if (null != t) v.addAll(t); + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), null, s.getSource())); + if (null != t) v.addAll(t); + t = handledSignals.get(new SignalTuple(s.getInterface(), s.getName(), s.getPath(), s.getSource())); + if (null != t) v.addAll(t); + } + if (0 == v.size()) return; + final AbstractConnection conn = this; + for (final DBusSigHandler h: v) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for signal "+s+" with handler "+h); + addRunnable(new Runnable() { + private boolean run = false; + public synchronized void run() + { + if (run) return; + run = true; + try { + DBusSignal rs; + if (s instanceof DBusSignal.internalsig || s.getClass().equals(DBusSignal.class)) + rs = s.createReal(conn); + else + rs = s; + ((DBusSigHandler)h).handle(rs); + } catch (DBusException DBe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + try { + conn.queueOutgoing(new Error(s, new DBusExecutionException("Error handling signal "+s.getInterface()+"."+s.getName()+": "+DBe.getMessage()))); + } catch (DBusException DBe2) {} + } + } + }); + } + } + private void handleMessage(final Error err) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming error: "+err); + MethodCall m = null; + if (null == pendingCalls) return; + synchronized (pendingCalls) { + if (pendingCalls.contains(err.getReplySerial())) + m = pendingCalls.remove(err.getReplySerial()); + } + if (null != m) { + m.setReply(err); + CallbackHandler cbh = null; + DBusAsyncReply asr = null; + synchronized (pendingCallbacks) { + cbh = pendingCallbacks.remove(m); + if (Debug.debug) Debug.print(Debug.VERBOSE, cbh+" = pendingCallbacks.remove("+m+")"); + asr = pendingCallbackReplys.remove(m); + } + // queue callback for execution + if (null != cbh) { + final CallbackHandler fcbh = cbh; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Error Runnable with callback handler "+fcbh); + addRunnable(new Runnable() { + private boolean run = false; + public synchronized void run() + { + if (run) return; + run = true; + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Error Callback for "+err); + DBusCallInfo info = new DBusCallInfo(err); + synchronized (infomap) { + infomap.put(Thread.currentThread(), info); + } + + fcbh.handleError(err.getException()); + synchronized (infomap) { + infomap.remove(Thread.currentThread()); + } + + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + } + } + }); + } + + } + else + synchronized (pendingErrors) { + pendingErrors.addLast(err); } + } + @SuppressWarnings("unchecked") + private void handleMessage(final MethodReturn mr) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Handling incoming method return: "+mr); + MethodCall m = null; + if (null == pendingCalls) return; + synchronized (pendingCalls) { + if (pendingCalls.contains(mr.getReplySerial())) + m = pendingCalls.remove(mr.getReplySerial()); + } + if (null != m) { + m.setReply(mr); + mr.setCall(m); + CallbackHandler cbh = null; + DBusAsyncReply asr = null; + synchronized (pendingCallbacks) { + cbh = pendingCallbacks.remove(m); + if (Debug.debug) Debug.print(Debug.VERBOSE, cbh+" = pendingCallbacks.remove("+m+")"); + asr = pendingCallbackReplys.remove(m); + } + // queue callback for execution + if (null != cbh) { + final CallbackHandler fcbh = cbh; + final DBusAsyncReply fasr = asr; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding Runnable for method "+fasr.getMethod()+" with callback handler "+fcbh); + addRunnable(new Runnable() { + private boolean run = false; + public synchronized void run() + { + if (run) return; + run = true; + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Running Callback for "+mr); + DBusCallInfo info = new DBusCallInfo(mr); + synchronized (infomap) { + infomap.put(Thread.currentThread(), info); + } + + fcbh.handle(RemoteInvocationHandler.convertRV(mr.getSig(), mr.getParameters(), fasr.getMethod(), fasr.getConnection())); + synchronized (infomap) { + infomap.remove(Thread.currentThread()); + } + + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + } + } + }); + } + + } else + try { + queueOutgoing(new Error(mr, new DBusExecutionException($_("Spurious reply. No message with the given serial id was awaiting a reply.")))); + } catch (DBusException DBe) {} + } + protected void sendMessage(Message m) + { + try { + if (!connected) throw new NotConnected($_("Disconnected")); + if (m instanceof DBusSignal) + ((DBusSignal) m).appendbody(this); + + if (m instanceof MethodCall) { + if (0 == (m.getFlags() & Message.Flags.NO_REPLY_EXPECTED)) + if (null == pendingCalls) + ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") })); + else synchronized (pendingCalls) { + pendingCalls.put(m.getSerial(),(MethodCall) m); + } + } + + transport.mout.writeMessage(m); + + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + if (m instanceof MethodCall && e instanceof NotConnected) + try { + ((MethodCall) m).setReply(new Error("org.freedesktop.DBus.Local", "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") })); + } catch (DBusException DBe) {} + if (m instanceof MethodCall && e instanceof DBusExecutionException) + try { + ((MethodCall)m).setReply(new Error(m, e)); + } catch (DBusException DBe) {} + else if (m instanceof MethodCall) + try { + if (Debug.debug) Debug.print(Debug.INFO, "Setting reply to "+m+" as an error"); + ((MethodCall)m).setReply(new Error(m, new DBusExecutionException($_("Message Failed to Send: ")+e.getMessage()))); + } catch (DBusException DBe) {} + else if (m instanceof MethodReturn) + try { + transport.mout.writeMessage(new Error(m, e)); + } catch(IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + } catch(DBusException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + } + if (e instanceof IOException) disconnect(); + } + } + private Message readIncoming() throws DBusException + { + if (!connected) throw new NotConnected($_("No transport present")); + Message m = null; + try { + m = transport.min.readMessage(); + } catch (IOException IOe) { + throw new FatalDBusException(IOe.getMessage()); + } + return m; + } + /** + * Returns the address this connection is connected to. + */ + public BusAddress getAddress() throws ParseException { return new BusAddress(addr); } +} diff --git a/app/src/main/java/org/freedesktop/dbus/ArrayFrob.java b/app/src/main/java/org/freedesktop/dbus/ArrayFrob.java new file mode 100644 index 00000000..df4f46bf --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/ArrayFrob.java @@ -0,0 +1,173 @@ +/* + 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.Array; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Hashtable; +import java.util.List; + +import cx.ath.matthew.debug.Debug; + +class ArrayFrob +{ + static Hashtable, Class> primitiveToWrapper = new Hashtable, Class>(); + static Hashtable, Class> wrapperToPrimitive = new Hashtable, Class>(); + static { + primitiveToWrapper.put( Boolean.TYPE, Boolean.class ); + primitiveToWrapper.put( Byte.TYPE, Byte.class ); + primitiveToWrapper.put( Short.TYPE, Short.class ); + primitiveToWrapper.put( Character.TYPE, Character.class ); + primitiveToWrapper.put( Integer.TYPE, Integer.class ); + primitiveToWrapper.put( Long.TYPE, Long.class ); + primitiveToWrapper.put( Float.TYPE, Float.class ); + primitiveToWrapper.put( Double.TYPE, Double.class ); + wrapperToPrimitive.put( Boolean.class, Boolean.TYPE ); + wrapperToPrimitive.put( Byte.class, Byte.TYPE ); + wrapperToPrimitive.put( Short.class, Short.TYPE ); + wrapperToPrimitive.put( Character.class, Character.TYPE ); + wrapperToPrimitive.put( Integer.class, Integer.TYPE ); + wrapperToPrimitive.put( Long.class, Long.TYPE ); + wrapperToPrimitive.put( Float.class, Float.TYPE ); + wrapperToPrimitive.put( Double.class, Double.TYPE ); + + } + @SuppressWarnings("unchecked") + public static T[] wrap(Object o) throws IllegalArgumentException + { + Class ac = o.getClass(); + if (!ac.isArray()) throw new IllegalArgumentException($_("Not an array")); + Class cc = ac.getComponentType(); + Class ncc = primitiveToWrapper.get(cc); + if (null == ncc) throw new IllegalArgumentException($_("Not a primitive type")); + T[] ns = (T[]) Array.newInstance(ncc, Array.getLength(o)); + for (int i = 0; i < ns.length; i++) + ns[i] = (T) Array.get(o, i); + return ns; + } + @SuppressWarnings("unchecked") + public static Object unwrap(T[] ns) throws IllegalArgumentException + { + Class ac = (Class) ns.getClass(); + Class cc = (Class) ac.getComponentType(); + Class ncc = wrapperToPrimitive.get(cc); + if (null == ncc) throw new IllegalArgumentException($_("Not a wrapper type")); + Object o = Array.newInstance(ncc, ns.length); + for (int i = 0; i < ns.length; i++) + Array.set(o, i, ns[i]); + return o; + } + public static List listify(T[] ns) throws IllegalArgumentException + { + return Arrays.asList(ns); + } + @SuppressWarnings("unchecked") + public static List listify(Object o) throws IllegalArgumentException + { + if (o instanceof Object[]) return listify((T[]) o); + if (!o.getClass().isArray()) throw new IllegalArgumentException($_("Not an array")); + List l = new ArrayList(Array.getLength(o)); + for (int i = 0; i < Array.getLength(o); i++) + l.add((T)Array.get(o, i)); + return l; + } + @SuppressWarnings("unchecked") + public static T[] delist(List l, Class c) throws IllegalArgumentException + { + return l.toArray((T[]) Array.newInstance(c, 0)); + } + public static Object delistprimitive(List l, Class c) throws IllegalArgumentException + { + Object o = Array.newInstance(c, l.size()); + for (int i = 0; i < l.size(); i++) + Array.set(o, i, l.get(i)); + return o; + } + @SuppressWarnings("unchecked") + public static Object convert(Object o, Class c) throws IllegalArgumentException + { + /* Possible Conversions: + * + ** List -> List + ** List -> int[] + ** List -> Integer[] + ** int[] -> int[] + ** int[] -> List + ** int[] -> Integer[] + ** Integer[] -> Integer[] + ** Integer[] -> int[] + ** Integer[] -> List + */ + try { + // List -> List + if (List.class.equals(c) + && o instanceof List) + return o; + + // int[] -> List + // Integer[] -> List + if (List.class.equals(c) + && o.getClass().isArray()) + return listify(o); + + // int[] -> int[] + // Integer[] -> Integer[] + if (o.getClass().isArray() + && c.isArray() + && o.getClass().getComponentType().equals(c.getComponentType())) + return o; + + // int[] -> Integer[] + if (o.getClass().isArray() + && c.isArray() + && o.getClass().getComponentType().isPrimitive()) + return wrap(o); + + // Integer[] -> int[] + if (o.getClass().isArray() + && c.isArray() + && c.getComponentType().isPrimitive()) + return unwrap((Object[]) o); + + // List -> int[] + if (o instanceof List + && c.isArray() + && c.getComponentType().isPrimitive()) + return delistprimitive((List) o, (Class) c.getComponentType()); + + // List -> Integer[] + if (o instanceof List + && c.isArray()) + return delist((List) o, (Class) c.getComponentType()); + + if (o.getClass().isArray() + && c.isArray()) + return type((Object[]) o, (Class) c.getComponentType()); + + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new IllegalArgumentException(e); + } + + throw new IllegalArgumentException(MessageFormat.format($_("Not An Expected Convertion type from {0} to {1}"), new Object[] { o.getClass(), c})); + } + public static Object[] type(Object[] old, Class c) + { + Object[] ns = (Object[]) Array.newInstance(c, old.length); + for (int i = 0; i < ns.length; i++) + ns[i] = old[i]; + return ns; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/BusAddress.java b/app/src/main/java/org/freedesktop/dbus/BusAddress.java new file mode 100644 index 00000000..79d29865 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/BusAddress.java @@ -0,0 +1,41 @@ +/* + 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.text.ParseException; +import java.util.Map; +import java.util.HashMap; +import cx.ath.matthew.debug.Debug; + +public class BusAddress +{ + private String type; + private Map parameters; + public BusAddress(String address) throws ParseException + { + if (null == address || "".equals(address)) throw new ParseException($_("Bus address is blank"), 0); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing bus address: "+address); + String[] ss = address.split(":", 2); + if (ss.length < 2) throw new ParseException($_("Bus address is invalid: ")+address, 0); + type = ss[0]; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport type: "+type); + String[] ps = ss[1].split(","); + parameters = new HashMap(); + for (String p: ps) { + String[] kv = p.split("=", 2); + parameters.put(kv[0], kv[1]); + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Transport options: "+parameters); + } + public String getType() { return type; } + public String getParameter(String key) { return parameters.get(key); } + public String toString() { return type+": "+parameters; } +} diff --git a/app/src/main/java/org/freedesktop/dbus/CallbackHandler.java b/app/src/main/java/org/freedesktop/dbus/CallbackHandler.java new file mode 100644 index 00000000..b05b5001 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/CallbackHandler.java @@ -0,0 +1,22 @@ +/* + 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 org.freedesktop.dbus.exceptions.DBusExecutionException; + +/** + * Interface for callbacks in async mode + */ +public interface CallbackHandler +{ + public void handle(ReturnType r); + public void handleError(DBusExecutionException e); +} diff --git a/app/src/main/java/org/freedesktop/dbus/Container.java b/app/src/main/java/org/freedesktop/dbus/Container.java new file mode 100644 index 00000000..d2efb677 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Container.java @@ -0,0 +1,88 @@ +/* + 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 java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.lang.reflect.Field; +import java.lang.reflect.Type; + +/** + * This class is the super class of both Structs and Tuples + * and holds common methods. + */ +abstract class Container +{ + private static Map typecache = new HashMap(); + static void putTypeCache(Type k, Type[] v) + { + typecache.put(k, v); + } + static Type[] getTypeCache(Type k) + { + return typecache.get(k); + } + private Object[] parameters = null; + public Container() {} + private void setup() + { + Field[] fs = getClass().getDeclaredFields(); + Object[] args = new Object[fs.length]; + + int diff = 0; + for (Field f : fs) { + Position p = f.getAnnotation(Position.class); + if (null == p) { + diff++; + continue; + } + try { + args[p.value()] = f.get(this); + } catch (IllegalAccessException IAe) {} + } + + this.parameters = new Object[args.length - diff]; + System.arraycopy(args, 0, parameters, 0, parameters.length); + } + /** + * Returns the struct contents in order. + * @throws DBusException If there is a problem doing this. + */ + public final Object[] getParameters() + { + if (null != parameters) return parameters; + setup(); + return parameters; + } + /** Returns this struct as a string. */ + public final String toString() + { + String s = getClass().getName()+"<"; + if (null == parameters) + setup(); + if (0 == parameters.length) + return s+">"; + for (Object o: parameters) + s += o+", "; + return s.replaceAll(", $", ">"); + } + public final boolean equals(Object other) + { + if (other instanceof Container) { + Container that = (Container) other; + if (this.getClass().equals(that.getClass())) + return Arrays.equals(this.getParameters(), that.getParameters()); + else return false; + } + else return false; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java b/app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java new file mode 100644 index 00000000..c3843812 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusAsyncReply.java @@ -0,0 +1,111 @@ +/* + 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.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import org.freedesktop.DBus.Error.NoReply; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +import cx.ath.matthew.debug.Debug; + +/** + * A handle to an asynchronous method call. + */ +public class DBusAsyncReply +{ + /** + * Check if any of a set of asynchronous calls have had a reply. + * @param replies A Collection of handles to replies to check. + * @return A Collection only containing those calls which have had replies. + */ + public static Collection> hasReply(Collection> replies) + { + Collection> c = new ArrayList>(replies); + Iterator> i = c.iterator(); + while (i.hasNext()) + if (!i.next().hasReply()) i.remove(); + return c; + } + + private ReturnType rval = null; + private DBusExecutionException error = null; + private MethodCall mc; + private Method me; + private AbstractConnection conn; + DBusAsyncReply(MethodCall mc, Method me, AbstractConnection conn) + { + this.mc = mc; + this.me = me; + this.conn = conn; + } + @SuppressWarnings("unchecked") + private synchronized void checkReply() + { + if (mc.hasReply()) { + Message m = mc.getReply(); + if (m instanceof Error) + error = ((Error) m).getException(); + else if (m instanceof MethodReturn) { + try { + rval = (ReturnType) RemoteInvocationHandler.convertRV(m.getSig(), m.getParameters(), me, conn); + } catch (DBusExecutionException DBEe) { + error = DBEe; + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + error = new DBusExecutionException(DBe.getMessage()); + } + } + } + } + + /** + * Check if we've had a reply. + * @return True if we have a reply + */ + public boolean hasReply() + { + if (null != rval || null != error) return true; + checkReply(); + return null != rval || null != error; + } + + /** + * Get the reply. + * @return The return value from the method. + * @throws DBusExecutionException if the reply to the method was an error. + * @throws NoReply if the method hasn't had a reply yet + */ + public ReturnType getReply() throws DBusExecutionException + { + if (null != rval) return rval; + else if (null != error) throw error; + checkReply(); + if (null != rval) return rval; + else if (null != error) throw error; + else throw new NoReply($_("Async call has not had a reply")); + } + + public String toString() + { + return $_("Waiting for: ")+mc; + } + Method getMethod() { return me; } + AbstractConnection getConnection() { return conn; } + MethodCall getCall() { return mc; } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java b/app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java new file mode 100644 index 00000000..d34ef4ef --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusCallInfo.java @@ -0,0 +1,51 @@ +/* + 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; + +/** + * Holds information on a method call + */ +public class DBusCallInfo +{ + /** + * Indicates the caller won't wait for a reply (and we won't send one). + */ + public static final int NO_REPLY = Message.Flags.NO_REPLY_EXPECTED; + public static final int ASYNC = 0x100; + private String source; + private String destination; + private String objectpath; + private String iface; + private String method; + private int flags; + DBusCallInfo(Message m) + { + this.source = m.getSource(); + this.destination = m.getDestination(); + this.objectpath = m.getPath(); + this.iface = m.getInterface(); + this.method = m.getName(); + this.flags = m.getFlags(); + } + + /** Returns the BusID which called the method */ + public String getSource() { return source; } + /** Returns the name with which we were addressed on the Bus */ + public String getDestination() { return destination; } + /** Returns the object path used to call this method */ + public String getObjectPath() { return objectpath; } + /** Returns the interface this method was called with */ + public String getInterface() { return iface; } + /** Returns the method name used to call this method */ + public String getMethod() { return method; } + /** Returns any flags set on this method call */ + public int getFlags() { return flags; } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusConnection.java b/app/src/main/java/org/freedesktop/dbus/DBusConnection.java new file mode 100644 index 00000000..b07a764b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusConnection.java @@ -0,0 +1,780 @@ +/* + 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.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; + +import java.text.MessageFormat; +import java.text.ParseException; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import cx.ath.matthew.debug.Debug; + +/** Handles a connection to DBus. + *

+ * This is a Singleton class, only 1 connection to the SYSTEM or SESSION busses can be made. + * Repeated calls to getConnection will return the same reference. + *

+ *

+ * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues. + *

+ */ +public class DBusConnection extends AbstractConnection +{ + /** + * Add addresses of peers to a set which will watch for them to + * disappear and automatically remove them from the set. + */ + public class PeerSet implements Set, DBusSigHandler + { + private Set addresses; + public PeerSet() + { + addresses = new TreeSet(); + try { + addSigHandler(new DBusMatchRule(DBus.NameOwnerChanged.class, null, null), this); + } catch (DBusException DBe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + } + } + public void handle(DBus.NameOwnerChanged noc) + { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Received NameOwnerChanged("+noc.name+","+noc.old_owner+","+noc.new_owner+")"); + if ("".equals(noc.new_owner) && addresses.contains(noc.name)) + remove(noc.name); + } + public boolean add(String address) + { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Adding "+address); + synchronized (addresses) { + return addresses.add(address); + } + } + public boolean addAll(Collection addresses) + { + synchronized (this.addresses) { + return this.addresses.addAll(addresses); + } + } + public void clear() + { + synchronized (addresses) { + addresses.clear(); + } + } + public boolean contains(Object o) + { + return addresses.contains(o); + } + public boolean containsAll(Collection os) + { + return addresses.containsAll(os); + } + public boolean equals(Object o) + { + if (o instanceof PeerSet) + return ((PeerSet) o).addresses.equals(addresses); + else return false; + } + public int hashCode() + { + return addresses.hashCode(); + } + public boolean isEmpty() + { + return addresses.isEmpty(); + } + public Iterator iterator() + { + return addresses.iterator(); + } + public boolean remove(Object o) + { + if (Debug.debug) + Debug.print(Debug.DEBUG, "Removing "+o); + synchronized(addresses) { + return addresses.remove(o); + } + } + public boolean removeAll(Collection os) + { + synchronized(addresses) { + return addresses.removeAll(os); + } + } + public boolean retainAll(Collection os) + { + synchronized(addresses) { + return addresses.retainAll(os); + } + } + public int size() + { + return addresses.size(); + } + public Object[] toArray() + { + synchronized(addresses) { + return addresses.toArray(); + } + } + public T[] toArray(T[] a) + { + synchronized(addresses) { + return addresses.toArray(a); + } + } + } + private class _sighandler implements DBusSigHandler + { + public void handle(DBusSignal s) + { + if (s instanceof org.freedesktop.DBus.Local.Disconnected) { + if (Debug.debug) Debug.print(Debug.WARN, "Handling Disconnected signal from bus"); + try { + Error err = new Error( + "org.freedesktop.DBus.Local" , "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") }); + if (null != pendingCalls) synchronized (pendingCalls) { + long[] set = pendingCalls.getKeys(); + for (long l: set) if (-1 != l) { + MethodCall m = pendingCalls.remove(l); + if (null != m) + m.setReply(err); + } + } + synchronized (pendingErrors) { + pendingErrors.add(err); + } + } catch (DBusException DBe) {} + } else if (s instanceof org.freedesktop.DBus.NameAcquired) { + busnames.add(((org.freedesktop.DBus.NameAcquired) s).name); + } + } + } + + /** + * System Bus + */ + public static final int SYSTEM = 0; + /** + * Session Bus + */ + public static final int SESSION = 1; + + public static final String DEFAULT_SYSTEM_BUS_ADDRESS = "unix:path=/var/run/dbus/system_bus_socket"; + + private List busnames; + + private static final Map conn = new HashMap(); + private int _refcount = 0; + private Object _reflock = new Object(); + private DBus _dbus; + + /** + * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned. + * @param address The address of the bus to connect to + * @throws DBusException If there is a problem connecting to the Bus. + */ + public static DBusConnection getConnection(String address) throws DBusException + { + synchronized (conn) { + DBusConnection c = conn.get(address); + if (null != c) { + synchronized (c._reflock) { c._refcount++; } + return c; + } + else { + c = new DBusConnection(address); + conn.put(address, c); + return c; + } + } + } + /** + * Connect to the BUS. If a connection already exists to the specified Bus, a reference to it is returned. + * @param bustype The Bus to connect to. + * @see #SYSTEM + * @see #SESSION + * @throws DBusException If there is a problem connecting to the Bus. + */ + public static DBusConnection getConnection(int bustype) throws DBusException + { + synchronized (conn) { + String s = null; + switch (bustype) { + case SYSTEM: + s = System.getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (null == s) s = DEFAULT_SYSTEM_BUS_ADDRESS; + break; + case SESSION: + s = System.getenv("DBUS_SESSION_BUS_ADDRESS"); + if (null == s) { + // address gets stashed in $HOME/.dbus/session-bus/`dbus-uuidgen --get`-`sed 's/:\(.\)\..*/\1/' <<< $DISPLAY` + String display = System.getenv("DISPLAY"); + if (null == display) throw new DBusException($_("Cannot Resolve Session Bus Address")); + File uuidfile = new File("/var/lib/dbus/machine-id"); + if (!uuidfile.exists()) throw new DBusException($_("Cannot Resolve Session Bus Address")); + try { + BufferedReader r = new BufferedReader(new FileReader(uuidfile)); + String uuid = r.readLine(); + String homedir = System.getProperty("user.home"); + File addressfile = new File(homedir + "/.dbus/session-bus", + uuid + "-" + display.replaceAll(":([0-9]*)\\..*", "$1")); + if (!addressfile.exists()) throw new DBusException($_("Cannot Resolve Session Bus Address")); + r = new BufferedReader(new FileReader(addressfile)); + String l; + while (null != (l = r.readLine())) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading D-Bus session data: "+l); + if (l.matches("DBUS_SESSION_BUS_ADDRESS.*")) { + s = l.replaceAll("^[^=]*=", ""); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Parsing "+l+" to "+s); + } + } + if (null == s || "".equals(s)) throw new DBusException($_("Cannot Resolve Session Bus Address")); + if (Debug.debug) Debug.print(Debug.INFO, "Read bus address "+s+" from file "+addressfile.toString()); + } catch (Exception e) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException($_("Cannot Resolve Session Bus Address")); + } + } + break; + default: + throw new DBusException($_("Invalid Bus Type: ")+bustype); + } + DBusConnection c = conn.get(s); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Getting bus connection for "+s+": "+c); + if (null != c) { + synchronized (c._reflock) { c._refcount++; } + return c; + } + else { + if (Debug.debug) Debug.print(Debug.DEBUG, "Creating new bus connection to: "+s); + c = new DBusConnection(s); + conn.put(s, c); + return c; + } + } + } + @SuppressWarnings("unchecked") + private DBusConnection(String address) throws DBusException + { + super(address); + busnames = new Vector(); + + synchronized (_reflock) { + _refcount = 1; + } + + try { + transport = new Transport(addr, AbstractConnection.TIMEOUT); + connected = true; + } catch (IOException IOe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOe); + disconnect(); + throw new DBusException($_("Failed to connect to bus ")+IOe.getMessage()); + } catch (ParseException Pe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Pe); + disconnect(); + throw new DBusException($_("Failed to connect to bus ")+Pe.getMessage()); + } + + // start listening for calls + listen(); + + // register disconnect handlers + DBusSigHandler h = new _sighandler(); + addSigHandlerWithoutMatch(org.freedesktop.DBus.Local.Disconnected.class, h); + addSigHandlerWithoutMatch(org.freedesktop.DBus.NameAcquired.class, h); + + // register ourselves + _dbus = getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); + try { + busnames.add(_dbus.Hello()); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + } + + @SuppressWarnings("unchecked") + DBusInterface dynamicProxy(String source, String path) throws DBusException + { + if (Debug.debug) Debug.print(Debug.INFO, "Introspecting "+path+" on "+source+" for dynamic proxy creation"); + try { + DBus.Introspectable intro = getRemoteObject(source, path, DBus.Introspectable.class); + String data = intro.Introspect(); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Got introspection data: "+data); + String[] tags = data.split("[<>]"); + Vector ifaces = new Vector(); + for (String tag: tags) { + if (tag.startsWith("interface")) { + ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1")); + } + } + Vector> ifcs = new Vector>(); + for(String iface: ifaces) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Trying interface "+iface); + int j = 0; + while (j >= 0) { + try { + Class ifclass = Class.forName(iface); + if (!ifcs.contains(ifclass)) + ifcs.add(ifclass); + 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(source, 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} exported by {1}. Reason: {2}"), new Object[] { path, source, e.getMessage() })); + } + } + + DBusInterface getExportedObject(String source, 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(); + if (null == source) throw new DBusException($_("Not an object exported by this connection and no remote specified")); + return dynamicProxy(source, path); + } + + /** + * Release a bus name. + * Releases the name so that other people can use it + * @param busname The name to release. MUST be in dot-notation like "org.freedesktop.local" + * @throws DBusException If the busname is incorrectly formatted. + */ + public void releaseBusName(String busname) throws DBusException + { + if (!busname.matches(BUSNAME_REGEX)||busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name")); + synchronized (this.busnames) { + UInt32 rv; + try { + rv = _dbus.ReleaseName(busname); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + this.busnames.remove(busname); + } + } + /** + * Request a bus name. + * Request the well known name that this should respond to on the Bus. + * @param busname The name to respond to. MUST be in dot-notation like "org.freedesktop.local" + * @throws DBusException If the register name failed, or our name already exists on the bus. + * or if busname is incorrectly formatted. + */ + public void requestBusName(String busname) throws DBusException + { + if (!busname.matches(BUSNAME_REGEX)||busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name")); + synchronized (this.busnames) { + UInt32 rv; + try { + rv = _dbus.RequestName(busname, + new UInt32(DBus.DBUS_NAME_FLAG_REPLACE_EXISTING | + DBus.DBUS_NAME_FLAG_DO_NOT_QUEUE)); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + switch (rv.intValue()) { + case DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER: break; + case DBus.DBUS_REQUEST_NAME_REPLY_IN_QUEUE: throw new DBusException($_("Failed to register bus name")); + case DBus.DBUS_REQUEST_NAME_REPLY_EXISTS: throw new DBusException($_("Failed to register bus name")); + case DBus.DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER: break; + default: break; + } + this.busnames.add(busname); + } + } + + /** + * Returns the unique name of this connection. + */ + public String getUniqueName() + { + return busnames.get(0); + } + /** + * Returns all the names owned by this connection. + */ + public String[] getNames() + { + Set names = new TreeSet(); + names.addAll(busnames); + return names.toArray(new String[0]); + } + public I getPeerRemoteObject(String busname, String objectpath, Class type) throws DBusException + { + return getPeerRemoteObject(busname, objectpath, type, true); + } + /** + * Return a reference to a remote object. + * This method will resolve the well known name (if given) to a unique bus name when you call it. + * This means that if a well known name is released by one process and acquired by another calls to + * objects gained from this method will continue to operate on the original process. + * + * This method will use bus introspection to determine the interfaces on a remote object and so + * may block and may fail. 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 busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @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 getPeerRemoteObject(String busname, String objectpath) throws DBusException + { + if (null == busname) throw new DBusException($_("Invalid bus name: null")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+busname); + + String unique = _dbus.GetNameOwner(busname); + + return dynamicProxy(unique, 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. + * + * This method will use bus introspection to determine the interfaces on a remote object and so + * may block and may fail. 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 busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @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 busname, String objectpath) throws DBusException + { + if (null == busname) throw new DBusException($_("Invalid bus name: null")); + if (null == objectpath) throw new DBusException($_("Invalid object path: null")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+busname); + + if (!objectpath.matches(OBJECT_REGEX) || objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + + return dynamicProxy(busname, objectpath); + } + + /** + * Return a reference to a remote object. + * This method will resolve the well known name (if given) to a unique bus name when you call it. + * This means that if a well known name is released by one process and acquired by another calls to + * objects gained from this method will continue to operate on the original process. + * @param busname The bus name to connect to. Usually a well known bus name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @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. + * @param autostart Disable/Enable auto-starting of services in response to calls on this object. + * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name + * and is not owned the bus will attempt to start a process to take the name. When disabled an error is + * returned immediately. + * @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 I getPeerRemoteObject(String busname, String objectpath, Class type, boolean autostart) throws DBusException + { + if (null == busname) throw new DBusException($_("Invalid bus name: null")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+busname); + + String unique = _dbus.GetNameOwner(busname); + + return getRemoteObject(unique, objectpath, type, autostart); + } + /** + * 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 busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @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 I getRemoteObject(String busname, String objectpath, Class type) throws DBusException + { + return getRemoteObject(busname, objectpath, type, true); + } + /** + * 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 busname The bus name to connect to. Usually a well known bus name name in dot-notation (such as "org.freedesktop.local") + * or may be a DBus address such as ":1-16". + * @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. + * @param autostart Disable/Enable auto-starting of services in response to calls on this object. + * Default is enabled; when calling a method with auto-start enabled, if the destination is a well-known name + * and is not owned the bus will attempt to start a process to take the name. When disabled an error is + * returned immediately. + * @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. + */ + @SuppressWarnings("unchecked") + public I getRemoteObject(String busname, String objectpath, Class type, boolean autostart) throws DBusException + { + if (null == busname) throw new DBusException($_("Invalid bus name: null")); + if (null == objectpath) throw new DBusException($_("Invalid object path: null")); + if (null == type) throw new ClassCastException($_("Not A DBus Interface")); + + if ((!busname.matches(BUSNAME_REGEX) && !busname.matches(CONNID_REGEX)) + || busname.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+busname); + + 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(busname, objectpath, type, autostart); + I i = (I) Proxy.newProxyInstance(type.getClassLoader(), + new Class[] { type }, new RemoteInvocationHandler(this, ro)); + importedObjects.put(i, ro); + return i; + } + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * @param type The signal to watch for. + * @param source The source of the signal. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, String source, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); + if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+source); + removeSigHandler(new DBusMatchRule(type, source, null), handler); + } + /** + * Remove a Signal Handler. + * Stops listening for this signal. + * @param type The signal to watch for. + * @param source The source of the signal. + * @param object The object emitting the signal. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + public void removeSigHandler(Class type, String source, DBusInterface object, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); + if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+source); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + removeSigHandler(new DBusMatchRule(type, source, objectpath), handler); + } + protected void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException + { + + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null != v) { + v.remove(handler); + if (0 == v.size()) { + handledSignals.remove(key); + try { + _dbus.RemoveMatch(rule.toString()); + } catch (NotConnected NC) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, NC); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + } + } + } + } + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type, name and source. + * @param type The signal to watch for. + * @param source The process which will send the signal. This MUST be a unique bus name and not a well known name. + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, String source, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); + if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+source); + addSigHandler(new DBusMatchRule(type, source, null), (DBusSigHandler) handler); + } + /** + * Add a Signal Handler. + * Adds a signal handler to call when a signal is received which matches the specified type, name, source and object. + * @param type The signal to watch for. + * @param source The process which will send the signal. This MUST be a unique bus name and not a well known name. + * @param object The object from which the signal will be emitted + * @param handler The handler to call when a signal is received. + * @throws DBusException If listening for the signal on the bus failed. + * @throws ClassCastException If type is not a sub-type of DBusSignal. + */ + @SuppressWarnings("unchecked") + public void addSigHandler(Class type, String source, DBusInterface object, DBusSigHandler handler) throws DBusException + { + if (!DBusSignal.class.isAssignableFrom(type)) throw new ClassCastException($_("Not A DBus Signal")); + if (source.matches(BUSNAME_REGEX)) throw new DBusException($_("Cannot watch for signals based on well known bus name as source, only unique names.")); + if (!source.matches(CONNID_REGEX)||source.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid bus name: ")+source); + String objectpath = importedObjects.get(object).objectpath; + if (!objectpath.matches(OBJECT_REGEX)||objectpath.length() > MAX_NAME_LENGTH) + throw new DBusException($_("Invalid object path: ")+objectpath); + addSigHandler(new DBusMatchRule(type, source, objectpath), (DBusSigHandler) handler); + } + protected void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException + { + try { + _dbus.AddMatch(rule.toString()); + } catch (DBusExecutionException DBEe) { + if (EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBEe); + throw new DBusException(DBEe.getMessage()); + } + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null == v) { + v = new Vector>(); + v.add(handler); + handledSignals.put(key, v); + } else + v.add(handler); + } + } + /** + * Disconnect from the Bus. + * This only disconnects when the last reference to the bus has disconnect called on it + * or has been destroyed. + */ + public void disconnect() + { + synchronized (conn) { + synchronized (_reflock) { + if (0 == --_refcount) { + if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting DBusConnection"); + // Set all pending messages to have an error. + try { + Error err = new Error( + "org.freedesktop.DBus.Local" , "org.freedesktop.DBus.Local.Disconnected", 0, "s", new Object[] { $_("Disconnected") }); + synchronized (pendingCalls) { + long[] set = pendingCalls.getKeys(); + for (long l: set) if (-1 != l) { + MethodCall m = pendingCalls.remove(l); + if (null != m) + m.setReply(err); + } + pendingCalls = null; + } + synchronized (pendingErrors) { + pendingErrors.add(err); + } + } catch (DBusException DBe) {} + + conn.remove(addr); + super.disconnect(); + } + } + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusInterface.java b/app/src/main/java/org/freedesktop/dbus/DBusInterface.java new file mode 100644 index 00000000..7b0d30af --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusInterface.java @@ -0,0 +1,31 @@ +/* + 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; +/** + * Denotes a class as exportable or a remote interface which can be called. + *

+ * Any interface which should be exported or imported should extend this + * interface. All public methods from that interface are exported/imported + * with the given method signatures. + *

+ *

+ * All method calls on exported objects are run in their own threads. + * Application writers are responsible for any concurrency issues. + *

+ */ +public interface DBusInterface +{ + /** + * Returns true on remote objects. + * Local objects implementing this interface MUST return false. + */ + public boolean isRemote(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java b/app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java new file mode 100644 index 00000000..5400a929 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusInterfaceName.java @@ -0,0 +1,27 @@ +/* + 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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Force the interface name to be different to the Java class name. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface DBusInterfaceName +{ + /** The replacement interface name. */ + String value(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusMap.java b/app/src/main/java/org/freedesktop/dbus/DBusMap.java new file mode 100644 index 00000000..b9d06d72 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusMap.java @@ -0,0 +1,152 @@ +/* + 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 java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +class DBusMap implements Map +{ + Object[][] entries; + public DBusMap(Object[][] entries) + { + this.entries=entries; + } + class Entry implements Map.Entry, Comparable + { + private int entry; + public Entry(int i) + { + this.entry = i; + } + public boolean equals(Object o) + { + if (null == o) return false; + if (!(o instanceof DBusMap.Entry)) return false; + return this.entry == ((Entry) o).entry; + } + @SuppressWarnings("unchecked") + public K getKey() + { + return (K) entries[entry][0]; + } + @SuppressWarnings("unchecked") + public V getValue() + { + return (V) entries[entry][1]; + } + public int hashCode() + { + return entries[entry][0].hashCode(); + } + public V setValue(V value) + { + throw new UnsupportedOperationException(); + } + public int compareTo(Entry e) + { + return entry - e.entry; + } + } + + public void clear() + { + throw new UnsupportedOperationException(); + } + public boolean containsKey(Object key) + { + for (int i = 0; i < entries.length; i++) + if (key == entries[i][0] || (key != null && key.equals(entries[i][0]))) + return true; + return false; + } + public boolean containsValue(Object value) + { + for (int i = 0; i < entries.length; i++) + if (value == entries[i][1] || (value != null && value.equals(entries[i][1]))) + return true; + return false; + } + public Set> entrySet() + { + Set> s = new TreeSet>(); + for (int i = 0; i < entries.length; i++) + s.add(new Entry(i)); + return s; + } + @SuppressWarnings("unchecked") + public V get(Object key) + { + for (int i = 0; i < entries.length; i++) + if (key == entries[i][0] || (key != null && key.equals(entries[i][0]))) + return (V) entries[i][1]; + return null; + } + public boolean isEmpty() + { + return entries.length == 0; + } + @SuppressWarnings("unchecked") + public Set keySet() + { + Set s = new TreeSet(); + for (Object[] entry: entries) + s.add((K) entry[0]); + return s; + } + public V put(K key, V value) + { + throw new UnsupportedOperationException(); + } + public void putAll(Map t) + { + throw new UnsupportedOperationException(); + } + public V remove(Object key) + { + throw new UnsupportedOperationException(); + } + public int size() + { + return entries.length; + } + @SuppressWarnings("unchecked") + public Collection values() + { + List l = new Vector(); + for (Object[] entry: entries) + l.add((V) entry[1]); + return l; + } + public int hashCode() + { + return Arrays.deepHashCode(entries); + } + @SuppressWarnings("unchecked") + public boolean equals(Object o) + { + if (null == o) return false; + if (!(o instanceof Map)) return false; + return ((Map) o).entrySet().equals(entrySet()); + } + public String toString() + { + String s = "{ "; + for (int i = 0; i < entries.length; i++) + s += entries[i][0] + " => " + entries[i][1] + ","; + return s.replaceAll(".$", " }"); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java b/app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java new file mode 100644 index 00000000..d75f5b66 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusMatchRule.java @@ -0,0 +1,143 @@ +/* + 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 org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +import java.util.HashMap; + +public class DBusMatchRule +{ + /* signal, error, method_call, method_reply */ + private String type; + private String iface; + private String member; + private String object; + private String source; + private static HashMap> signalTypeMap = + new HashMap>(); + static Class getCachedSignalType(String type) + { + return signalTypeMap.get(type); + } + public DBusMatchRule(String type, String iface, String member) + { + this.type = type; + this.iface = iface; + this.member = member; + } + public DBusMatchRule(DBusExecutionException e) throws DBusException + { + this(e.getClass()); + member = null; + type = "error"; + } + public DBusMatchRule(Message m) + { + iface = m.getInterface(); + member = m.getName(); + if (m instanceof DBusSignal) + type = "signal"; + else if (m instanceof Error) { + type = "error"; + member = null; + } + else if (m instanceof MethodCall) + type = "method_call"; + else if (m instanceof MethodReturn) + type = "method_reply"; + } + public DBusMatchRule(Class c, String method) throws DBusException + { + this(c); + member = method; + type = "method_call"; + } + public DBusMatchRule(Class c, String source, String object) throws DBusException + { + this(c); + this.source = source; + this.object = object; + } + @SuppressWarnings("unchecked") + public DBusMatchRule(Class c) throws DBusException + { + if (DBusInterface.class.isAssignableFrom(c)) { + if (null != c.getAnnotation(DBusInterfaceName.class)) + iface = c.getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll("."); + if (!iface.matches(".*\\..*")) + throw new DBusException($_("DBusInterfaces must be defined in a package.")); + member = null; + type = null; + } + else if (DBusSignal.class.isAssignableFrom(c)) { + if (null == c.getEnclosingClass()) + throw new DBusException($_("Signals must be declared as a member of a class implementing DBusInterface which is the member of a package.")); + else + if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class)) + iface = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll("."); + // Don't export things which are invalid D-Bus interfaces + if (!iface.matches(".*\\..*")) + throw new DBusException($_("DBusInterfaces must be defined in a package.")); + if (c.isAnnotationPresent(DBusMemberName.class)) + member = c.getAnnotation(DBusMemberName.class).value(); + else + member = c.getSimpleName(); + signalTypeMap.put(iface+'$'+member, (Class) c); + type = "signal"; + } + else if (Error.class.isAssignableFrom(c)) { + if (null != c.getAnnotation(DBusInterfaceName.class)) + iface = c.getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getName()).replaceAll("."); + if (!iface.matches(".*\\..*")) + throw new DBusException($_("DBusInterfaces must be defined in a package.")); + member = null; + type = "error"; + } + else if (DBusExecutionException.class.isAssignableFrom(c)) { + if (null != c.getClass().getAnnotation(DBusInterfaceName.class)) + iface = c.getClass().getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(c.getClass().getName()).replaceAll("."); + if (!iface.matches(".*\\..*")) + throw new DBusException($_("DBusInterfaces must be defined in a package.")); + member = null; + type = "error"; + } + else + throw new DBusException($_("Invalid type for match rule: ")+c); + } + public String toString() + { + String s = null; + if (null != type) s = null == s ? "type='"+type+"'" : s + ",type='"+type+"'"; + if (null != member) s = null == s ? "member='"+member+"'" : s + ",member='"+member+"'"; + if (null != iface) s = null == s ? "interface='"+iface+"'" : s + ",interface='"+iface+"'"; + if (null != source) s = null == s ? "sender='"+source+"'" : s + ",sender='"+source+"'"; + if (null != object) s = null == s ? "path='"+object+"'" : s + ",path='"+object+"'"; + return s; + } + public String getType() { return type; } + public String getInterface() { return iface; } + public String getMember() { return member; } + public String getSource() { return source; } + public String getObject() { return object; } + +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusMemberName.java b/app/src/main/java/org/freedesktop/dbus/DBusMemberName.java new file mode 100644 index 00000000..da7f8fd2 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusMemberName.java @@ -0,0 +1,27 @@ +/* + 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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Force the member (method/signal) name on the bus to be different to the Java name. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE,ElementType.METHOD}) +public @interface DBusMemberName +{ + /** The replacement member name. */ + String value(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusSerializable.java b/app/src/main/java/org/freedesktop/dbus/DBusSerializable.java new file mode 100644 index 00000000..8e311375 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusSerializable.java @@ -0,0 +1,38 @@ +/* + 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 org.freedesktop.dbus.exceptions.DBusException; +/** + * Custom classes may be sent over DBus if they implement this interface. + *

+ * In addition to the serialize method, classes MUST implement + * a deserialize method which returns null and takes as it's arguments + * all the DBus types the class will be serialied to in order and + * with type parameterisation. They MUST also provide a + * zero-argument constructor. + *

+ *

+ * The serialize method should return the class properties you wish to + * serialize, correctly formatted for the wire + * (DBusConnection.convertParameters() can help with this), in order in an + * Object array. + *

+ *

+ * The deserialize method will be called once after the zero-argument + * constructor. This should contain all the code to initialise the object + * from the types. + *

+ */ +public interface DBusSerializable +{ + public Object[] serialize() throws DBusException; +} + diff --git a/app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java b/app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java new file mode 100644 index 00000000..a56d8451 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusSigHandler.java @@ -0,0 +1,25 @@ +/* + 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; +/** Handle a signal on DBus. + * All Signal handlers are run in their own Thread. + * Application writers are responsible for managing any concurrency issues. + */ +public interface DBusSigHandler +{ + /** + * Handle a signal. + * @param s The signal to handle. If such a class exists, the + * signal will be an instance of the class with the correct type signature. + * Otherwise it will be an instance of DBusSignal + */ + public void handle(T s); +} diff --git a/app/src/main/java/org/freedesktop/dbus/DBusSignal.java b/app/src/main/java/org/freedesktop/dbus/DBusSignal.java new file mode 100644 index 00000000..424d5bdd --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DBusSignal.java @@ -0,0 +1,259 @@ +/* + 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.Constructor; +import java.lang.reflect.GenericDeclaration; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MessageFormatException; + +import cx.ath.matthew.debug.Debug; + +public class DBusSignal extends Message +{ + DBusSignal() { } + public DBusSignal(String source, String path, String iface, String member, String sig, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0); + + if (null == path || null == member || null == iface) + throw new MessageFormatException($_("Must specify object path, interface and signal name to Signals.")); + headers.put(Message.HeaderField.PATH,path); + headers.put(Message.HeaderField.MEMBER,member); + headers.put(Message.HeaderField.INTERFACE,iface); + + Vector hargs = new Vector(); + hargs.add(new Object[] { Message.HeaderField.PATH, new Object[] { ArgumentType.OBJECT_PATH_STRING, path } }); + hargs.add(new Object[] { Message.HeaderField.INTERFACE, new Object[] { ArgumentType.STRING_STRING, iface } }); + hargs.add(new Object[] { Message.HeaderField.MEMBER, new Object[] { ArgumentType.STRING_STRING, member } }); + + if (null != source) { + headers.put(Message.HeaderField.SENDER,source); + hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); + } + + if (null != sig) { + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } + + blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", ++serial, hargs.toArray()); + pad((byte)8); + + long c = bytecounter; + if (null != sig) append(sig, args); + marshallint(bytecounter-c, blen, 0, 4); + bodydone = true; + } + static class internalsig extends DBusSignal + { + public internalsig(String source, String objectpath, String type, String name, String sig, Object[] parameters, long serial) throws DBusException + { + super(source, objectpath, type, name, sig, parameters, serial); + } + } + private static Map, Type[]> typeCache = new HashMap, Type[]>(); + private static Map> classCache = new HashMap>(); + private static Map, Constructor> conCache = new HashMap, Constructor>(); + private static Map signames = new HashMap(); + private static Map intnames = new HashMap(); + private Class c; + private boolean bodydone = false; + private byte[] blen; + + static void addInterfaceMap(String java, String dbus) + { + intnames.put(dbus, java); + } + static void addSignalMap(String java, String dbus) + { + signames.put(dbus, java); + } + + static DBusSignal createSignal(Class c, String source, String objectpath, String sig, long serial, Object... parameters) throws DBusException + { + String type = ""; + if (null != c.getEnclosingClass()) { + if (null != c.getEnclosingClass().getAnnotation(DBusInterfaceName.class)) + type = c.getEnclosingClass().getAnnotation(DBusInterfaceName.class).value(); + else + type = AbstractConnection.dollar_pattern.matcher(c.getEnclosingClass().getName()).replaceAll("."); + + } else + throw new DBusException($_("Signals must be declared as a member of a class implementing DBusInterface which is the member of a package.")); + DBusSignal s = new internalsig(source, objectpath, type, c.getSimpleName(), sig, parameters, serial); + s.c = c; + return s; + } + @SuppressWarnings("unchecked") + private static Class createSignalClass(String intname, String signame) throws DBusException + { + String name = intname+'$'+signame; + Class c = classCache.get(name); + if (null == c) c = DBusMatchRule.getCachedSignalType(name); + if (null != c) return c; + do { + try { + c = (Class) Class.forName(name); + } catch (ClassNotFoundException CNFe) {} + name = name.replaceAll("\\.([^\\.]*)$", "\\$$1"); + } while (null == c && name.matches(".*\\..*")); + if (null == c) + throw new DBusException($_("Could not create class from signal ")+intname+'.'+signame); + classCache.put(name, c); + return c; + } + @SuppressWarnings("unchecked") + DBusSignal createReal(AbstractConnection conn) throws DBusException + { + String intname = intnames.get(getInterface()); + String signame = signames.get(getName()); + if (null == intname) intname = getInterface(); + if (null == signame) signame = getName(); + if (null == c) + c = createSignalClass(intname,signame); + if (Debug.debug) Debug.print(Debug.DEBUG, "Converting signal to type: "+c); + Type[] types = typeCache.get(c); + Constructor con = conCache.get(c); + if (null == types) { + con = (Constructor) c.getDeclaredConstructors()[0]; + conCache.put(c, con); + Type[] ts = con.getGenericParameterTypes(); + types = new Type[ts.length-1]; + for (int i = 1; i < ts.length; i++) + if (ts[i] instanceof TypeVariable) + for (Type b: ((TypeVariable) ts[i]).getBounds()) + types[i-1] = b; + else + types[i-1] = ts[i]; + typeCache.put(c, types); + } + + try { + DBusSignal s; + Object[] args = Marshalling.deSerializeParameters(getParameters(), types, conn); + if (null == args) s = (DBusSignal) con.newInstance(getPath()); + else { + Object[] params = new Object[args.length + 1]; + params[0] = getPath(); + System.arraycopy(args, 0, params, 1, args.length); + + if (Debug.debug) Debug.print(Debug.DEBUG, "Creating signal of type "+c+" with parameters "+Arrays.deepToString(params)); + s = (DBusSignal) con.newInstance(params); + } + s.headers = headers; + s.wiredata = wiredata; + s.bytecounter = wiredata.length; + return s; + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(e.getMessage()); + } + } + /** + * Create a new signal. + * This contructor MUST be called by all sub classes. + * @param objectpath The path to the object this is emitted from. + * @param args The parameters of the signal. + * @throws DBusException This is thrown if the subclass is incorrectly defined. + */ + @SuppressWarnings("unchecked") + protected DBusSignal(String objectpath, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.SIGNAL, (byte) 0); + + if (!objectpath.matches(AbstractConnection.OBJECT_REGEX)) throw new DBusException($_("Invalid object path: ")+objectpath); + + Class tc = getClass(); + String member; + if (tc.isAnnotationPresent(DBusMemberName.class)) + member = tc.getAnnotation(DBusMemberName.class).value(); + else + member = tc.getSimpleName(); + String iface = null; + Class enc = tc.getEnclosingClass(); + if (null == enc || + !DBusInterface.class.isAssignableFrom(enc) || + enc.getName().equals(enc.getSimpleName())) + throw new DBusException($_("Signals must be declared as a member of a class implementing DBusInterface which is the member of a package.")); + else + if (null != enc.getAnnotation(DBusInterfaceName.class)) + iface = enc.getAnnotation(DBusInterfaceName.class).value(); + else + iface = AbstractConnection.dollar_pattern.matcher(enc.getName()).replaceAll("."); + + headers.put(Message.HeaderField.PATH,objectpath); + headers.put(Message.HeaderField.MEMBER,member); + headers.put(Message.HeaderField.INTERFACE,iface); + + Vector hargs = new Vector(); + hargs.add(new Object[] { Message.HeaderField.PATH, new Object[] { ArgumentType.OBJECT_PATH_STRING, objectpath } }); + hargs.add(new Object[] { Message.HeaderField.INTERFACE, new Object[] { ArgumentType.STRING_STRING, iface } }); + hargs.add(new Object[] { Message.HeaderField.MEMBER, new Object[] { ArgumentType.STRING_STRING, member } }); + + String sig = null; + if (0 < args.length) { + try { + Type[] types = typeCache.get(tc); + if (null == types) { + Constructor con = (Constructor) tc.getDeclaredConstructors()[0]; + conCache.put(tc, con); + Type[] ts = con.getGenericParameterTypes(); + types = new Type[ts.length-1]; + for (int i = 1; i <= types.length; i++) + if (ts[i] instanceof TypeVariable) + types[i-1] = ((TypeVariable) ts[i]).getBounds()[0]; + else + types[i-1] = ts[i]; + typeCache.put(tc, types); + } + sig = Marshalling.getDBusType(types); + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException($_("Failed to add signal parameters: ")+e.getMessage()); + } + } + + blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", ++serial, hargs.toArray()); + pad((byte)8); + } + void appendbody(AbstractConnection conn) throws DBusException + { + if (bodydone) return; + + Type[] types = typeCache.get(getClass()); + Object[] args = Marshalling.convertParameters(getParameters(), types, conn); + setArgs(args); + String sig = getSig(); + + long c = bytecounter; + if (null != args && 0 < args.length) append(sig, args); + marshallint(bytecounter-c, blen, 0, 4); + bodydone = true; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/DirectConnection.java b/app/src/main/java/org/freedesktop/dbus/DirectConnection.java new file mode 100644 index 00000000..96bce7b7 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/DirectConnection.java @@ -0,0 +1,251 @@ +/* + 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. + *

+ * Signal Handlers and method calls from remote objects are run in their own threads, you MUST handle the concurrency issues. + *

+ */ +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); + connected = true; + } 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 ifaces = new Vector(); + for (String tag: tags) { + if (tag.startsWith("interface")) { + ifaces.add(tag.replaceAll("^interface *name *= *['\"]([^'\"]*)['\"].*$", "$1")); + } + } + Vector> ifcs = new Vector>(); + 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 + * may block and may fail. 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 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 void removeSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException + { + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null != v) { + v.remove(handler); + if (0 == v.size()) { + handledSignals.remove(key); + } + } + } + } + protected void addSigHandler(DBusMatchRule rule, DBusSigHandler handler) throws DBusException + { + SignalTuple key = new SignalTuple(rule.getInterface(), rule.getMember(), rule.getObject(), rule.getSource()); + synchronized (handledSignals) { + Vector> v = handledSignals.get(key); + if (null == v) { + v = new Vector>(); + v.add(handler); + handledSignals.put(key, v); + } else + v.add(handler); + } + } + DBusInterface getExportedObject(String source, String path) throws DBusException + { + return getExportedObject(path); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/EfficientMap.java b/app/src/main/java/org/freedesktop/dbus/EfficientMap.java new file mode 100644 index 00000000..67a24848 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/EfficientMap.java @@ -0,0 +1,116 @@ +/* + 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; + +/** + * Provides a long => MethodCall map which doesn't allocate objects + * on insertion/removal. Keys must be inserted in ascending order. */ +class EfficientMap +{ + private long[] kv; + private MethodCall[] vv; + private int start; + private int end; + private int init_size; + public EfficientMap(int initial_size) + { + init_size = initial_size; + shrink(); + } + private void grow() + { + // create new vectors twice as long + long[] oldkv = kv; + kv = new long[oldkv.length*2]; + MethodCall[] oldvv = vv; + vv = new MethodCall[oldvv.length*2]; + + // copy start->length to the start of the new vector + System.arraycopy(oldkv,start,kv,0,oldkv.length-start); + System.arraycopy(oldvv,start,vv,0,oldvv.length-start); + // copy 0->end to the next part of the new vector + if (end != (oldkv.length-1)) { + System.arraycopy(oldkv,0,kv,oldkv.length-start,end+1); + System.arraycopy(oldvv,0,vv,oldvv.length-start,end+1); + } + // reposition pointers + start = 0; + end = oldkv.length; + } + // create a new vector with just the valid keys in and return it + public long[] getKeys() + { + int size; + if (start < end) size = end-start; + else size = kv.length-(start-end); + long[] lv = new long[size]; + int copya; + if (size > kv.length-start) copya = kv.length-start; + else copya = size; + System.arraycopy(kv,start,lv,0,copya); + if (copya < size) { + System.arraycopy(kv,0,lv,copya,size-copya); + } + return lv; + } + private void shrink() + { + if (null != kv && kv.length == init_size) return; + // reset to original size + kv = new long[init_size]; + vv = new MethodCall[init_size]; + start = 0; + end = 0; + } + public void put(long l, MethodCall m) + { + // put this at the end + kv[end] = l; + vv[end] = m; + // move the end + if (end == (kv.length-1)) end = 0; else end++; + // if we are out of space, grow. + if (end == start) grow(); + } + public MethodCall remove(long l) + { + // find the item + int pos = find(l); + // if we don't have it return null + if (-1 == pos) return null; + // get the value + MethodCall m = vv[pos]; + // set it as unused + vv[pos] = null; + kv[pos] = -1; + // move the pointer to the first full element + while (-1 == kv[start]) { + if (start == (kv.length-1)) start = 0; else start++; + // if we have emptied the list, shrink it + if (start == end) { shrink(); break; } + } + return m; + } + public boolean contains(long l) + { + // check if find succeeds + return -1 != find(l); + } + /* could binary search, but it's probably the first one */ + private int find(long l) + { + int i = start; + while (i != end && kv[i] != l) + if (i == (kv.length-1)) i = 0; else i++; + if (i == end) return -1; + return i; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/EfficientQueue.java b/app/src/main/java/org/freedesktop/dbus/EfficientQueue.java new file mode 100644 index 00000000..5724730f --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/EfficientQueue.java @@ -0,0 +1,107 @@ +/* + 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 cx.ath.matthew.debug.Debug; + +/** + * Provides a Message queue which doesn't allocate objects + * on insertion/removal. */ +class EfficientQueue +{ + private Message[] mv; + private int start; + private int end; + private int init_size; + public EfficientQueue(int initial_size) + { + init_size = initial_size; + shrink(); + } + private void grow() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Growing"); + // create new vectors twice as long + Message[] oldmv = mv; + mv = new Message[oldmv.length*2]; + + // copy start->length to the start of the new vector + System.arraycopy(oldmv,start,mv,0,oldmv.length-start); + // copy 0->end to the next part of the new vector + if (end != (oldmv.length-1)) { + System.arraycopy(oldmv,0,mv,oldmv.length-start,end+1); + } + // reposition pointers + start = 0; + end = oldmv.length; + } + // create a new vector with just the valid keys in and return it + public Message[] getKeys() + { + if (start == end) return new Message[0]; + Message[] lv; + if (start < end) { + int size = end-start; + lv = new Message[size]; + System.arraycopy(mv, start, lv, 0, size); + } else { + int size = mv.length-start+end; + lv = new Message[size]; + System.arraycopy(mv, start, lv, 0, mv.length-start); + System.arraycopy(mv, 0, lv, mv.length-start, end); + } + return lv; + } + private void shrink() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Shrinking"); + if (null != mv && mv.length == init_size) return; + // reset to original size + mv = new Message[init_size]; + start = 0; + end = 0; + } + public void add(Message m) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Enqueueing Message "+m); + // put this at the end + mv[end] = m; + // move the end + if (end == (mv.length-1)) end = 0; else end++; + // if we are out of space, grow. + if (end == start) grow(); + } + public Message remove() + { + if (start == end) return null; + // find the item + int pos = start; + // get the value + Message m = mv[pos]; + // set it as unused + mv[pos] = null; + if (start == (mv.length-1)) start = 0; else start++; + if (Debug.debug) Debug.print(Debug.DEBUG, "Dequeueing "+m); + return m; + } + public boolean isEmpty() + { + // check if find succeeds + return start == end; + } + public int size() + { + if (end >= start) + return end-start; + else + return mv.length-start+end; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Error.java b/app/src/main/java/org/freedesktop/dbus/Error.java new file mode 100644 index 00000000..93f14af6 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Error.java @@ -0,0 +1,142 @@ +/* + 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.Constructor; +import java.util.Vector; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.MessageFormatException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import cx.ath.matthew.debug.Debug; + +/** + * Error messages which can be sent over the bus. + */ +public class Error extends Message +{ + Error() { } + public Error(String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException + { + this(null, dest, errorName, replyserial, sig, args); + } + public Error(String source, String dest, String errorName, long replyserial, String sig, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.ERROR, (byte) 0); + + if (null == errorName) + throw new MessageFormatException($_("Must specify error name to Errors.")); + headers.put(Message.HeaderField.REPLY_SERIAL,replyserial); + headers.put(Message.HeaderField.ERROR_NAME,errorName); + + Vector hargs = new Vector(); + hargs.add(new Object[] { Message.HeaderField.ERROR_NAME, new Object[] { ArgumentType.STRING_STRING, errorName } }); + hargs.add(new Object[] { Message.HeaderField.REPLY_SERIAL, new Object[] { ArgumentType.UINT32_STRING, replyserial } }); + + if (null != source) { + headers.put(Message.HeaderField.SENDER,source); + hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); + } + + if (null != dest) { + headers.put(Message.HeaderField.DESTINATION,dest); + hargs.add(new Object[] { Message.HeaderField.DESTINATION, new Object[] { ArgumentType.STRING_STRING, dest } }); + } + + if (null != sig) { + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } + + byte[] blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", serial, hargs.toArray()); + pad((byte)8); + + long c = bytecounter; + if (null != sig) append(sig, args); + marshallint(bytecounter-c, blen, 0, 4); + } + public Error(String source, Message m, Throwable e) throws DBusException + { + this(source, m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage()); + } + public Error(Message m, Throwable e) throws DBusException + { + this(m.getSource(), AbstractConnection.dollar_pattern.matcher(e.getClass().getName()).replaceAll("."), m.getSerial(), "s", e.getMessage()); + } + @SuppressWarnings("unchecked") + private static Class createExceptionClass(String name) + { + if (name == "org.freedesktop.DBus.Local.Disconnected") return NotConnected.class; + Class c = null; + do { + try { + c = (Class) Class.forName(name); + } catch (ClassNotFoundException CNFe) {} + name = name.replaceAll("\\.([^\\.]*)$", "\\$$1"); + } while (null == c && name.matches(".*\\..*")); + return c; + } + /** + * Turns this into an exception of the correct type + */ + public DBusExecutionException getException() + { + try { + Class c = createExceptionClass(getName()); + if (null == c || !DBusExecutionException.class.isAssignableFrom(c)) c = DBusExecutionException.class; + Constructor con = c.getConstructor(String.class); + DBusExecutionException ex; + Object[] args = getParameters(); + if (null == args || 0 == args.length) + ex = con.newInstance(""); + else { + String s = ""; + for (Object o: args) + s += o + " "; + ex = con.newInstance(s.trim()); + } + ex.setType(getName()); + return ex; + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug && null != e.getCause()) + Debug.print(Debug.ERR, e.getCause()); + DBusExecutionException ex; + Object[] args = null; + try { + args = getParameters(); + } catch (Exception ee) {} + if (null == args || 0 == args.length) + ex = new DBusExecutionException(""); + else { + String s = ""; + for (Object o: args) + s += o + " "; + ex = new DBusExecutionException(s.trim()); + } + ex.setType(getName()); + return ex; + } + } + /** + * Throw this as an exception of the correct type + */ + public void throwException() throws DBusExecutionException + { + throw getException(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/ExportedObject.java b/app/src/main/java/org/freedesktop/dbus/ExportedObject.java new file mode 100644 index 00000000..ed84add0 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/ExportedObject.java @@ -0,0 +1,166 @@ +/* + 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.ref.Reference; +import java.lang.ref.WeakReference; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +class ExportedObject +{ + @SuppressWarnings("unchecked") + private String getAnnotations(AnnotatedElement c) + { + String ans = ""; + for (Annotation a: c.getDeclaredAnnotations()) { + Class t = a.annotationType(); + String value = ""; + try { + Method m = t.getMethod("value"); + value = m.invoke(a).toString(); + } catch (NoSuchMethodException NSMe) { + } catch (InvocationTargetException ITe) { + } catch (IllegalAccessException IAe) {} + + ans += " \n"; + } + return ans; + } + @SuppressWarnings("unchecked") + private Map getExportedMethods(Class c) throws DBusException + { + if (DBusInterface.class.equals(c)) return new HashMap(); + Map m = new HashMap(); + for (Class i: c.getInterfaces()) + if (DBusInterface.class.equals(i)) { + // add this class's public methods + if (null != c.getAnnotation(DBusInterfaceName.class)) { + String name = ((DBusInterfaceName) c.getAnnotation(DBusInterfaceName.class)).value(); + introspectiondata += " \n"; + DBusSignal.addInterfaceMap(c.getName(), name); + } else { + // don't let people export things which don't have a + // valid D-Bus interface name + if (c.getName().equals(c.getSimpleName())) + throw new DBusException($_("DBusInterfaces cannot be declared outside a package")); + if (c.getName().length() > DBusConnection.MAX_NAME_LENGTH) + throw new DBusException($_("Introspected interface name exceeds 255 characters. Cannot export objects of type ")+c.getName()); + else + introspectiondata += " \n"; + } + introspectiondata += getAnnotations(c); + for (Method meth: c.getDeclaredMethods()) + if (Modifier.isPublic(meth.getModifiers())) { + String ms = ""; + String name; + if (meth.isAnnotationPresent(DBusMemberName.class)) + name = meth.getAnnotation(DBusMemberName.class).value(); + else + name = meth.getName(); + if (name.length() > DBusConnection.MAX_NAME_LENGTH) + throw new DBusException($_("Introspected method name exceeds 255 characters. Cannot export objects with method ")+name); + introspectiondata += " \n"; + introspectiondata += getAnnotations(meth); + for (Class ex: meth.getExceptionTypes()) + if (DBusExecutionException.class.isAssignableFrom(ex)) + introspectiondata += + " \n"; + for (Type pt: meth.getGenericParameterTypes()) + for (String s: Marshalling.getDBusType(pt)) { + introspectiondata += " \n"; + ms += s; + } + if (!Void.TYPE.equals(meth.getGenericReturnType())) { + if (Tuple.class.isAssignableFrom((Class) meth.getReturnType())) { + ParameterizedType tc = (ParameterizedType) meth.getGenericReturnType(); + Type[] ts = tc.getActualTypeArguments(); + + for (Type t: ts) + if (t != null) + for (String s: Marshalling.getDBusType(t)) + introspectiondata += " \n"; + } else if (Object[].class.equals(meth.getGenericReturnType())) { + throw new DBusException($_("Return type of Object[] cannot be introspected properly")); + } else + for (String s: Marshalling.getDBusType(meth.getGenericReturnType())) + introspectiondata += " \n"; + } + introspectiondata += " \n"; + m.put(new MethodTuple(name, ms), meth); + } + for (Class sig: c.getDeclaredClasses()) + if (DBusSignal.class.isAssignableFrom(sig)) { + String name; + if (sig.isAnnotationPresent(DBusMemberName.class)) { + name = ((DBusMemberName) sig.getAnnotation(DBusMemberName.class)).value(); + DBusSignal.addSignalMap(sig.getSimpleName(), name); + } else + name = sig.getSimpleName(); + if (name.length() > DBusConnection.MAX_NAME_LENGTH) + throw new DBusException($_("Introspected signal name exceeds 255 characters. Cannot export objects with signals of type ")+name); + introspectiondata += " \n"; + Constructor con = sig.getConstructors()[0]; + Type[] ts = con.getGenericParameterTypes(); + for (int j = 1; j < ts.length; j++) + for (String s: Marshalling.getDBusType(ts[j])) + introspectiondata += " \n"; + introspectiondata += getAnnotations(sig); + introspectiondata += " \n"; + + } + introspectiondata += " \n"; + } else { + // recurse + m.putAll(getExportedMethods(i)); + } + return m; + } + Map methods; + Reference object; + String introspectiondata; + public ExportedObject(DBusInterface object, boolean weakreferences) throws DBusException + { + if (weakreferences) + this.object = new WeakReference(object); + else + this.object = new StrongReference(object); + introspectiondata = ""; + methods = getExportedMethods(object.getClass()); + introspectiondata += + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"; + introspectiondata += + " \n"+ + " \n"+ + " \n"+ + " \n"; + } +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/Gettext.java b/app/src/main/java/org/freedesktop/dbus/Gettext.java new file mode 100644 index 00000000..5f2d31be --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Gettext.java @@ -0,0 +1,33 @@ +/* + * Pescetti Pseudo-Duplimate Generator + * + * Copyright (C) 2007 Matthew Johnson + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License Version 2 as published by + * the Free Software Foundation. This program is distributed in the hope that + * it will be useful, but WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. You should have received a + * copy of the GNU Lesser General Public License along with this program; if not, + * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * To Contact me, please email src@matthew.ath.cx + * + */ +package org.freedesktop.dbus; + +import java.util.ResourceBundle; + +public class Gettext +{ +// private static ResourceBundle myResources = +// ResourceBundle.getBundle("dbusjava_localized"); +// public static String $_(String s) { +// return myResources.getString(s); +// } + public static String $_(String s) { + return s; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/InternalSignal.java b/app/src/main/java/org/freedesktop/dbus/InternalSignal.java new file mode 100644 index 00000000..55954d57 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/InternalSignal.java @@ -0,0 +1,20 @@ +/* + 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 org.freedesktop.dbus.exceptions.DBusException; +class InternalSignal extends DBusSignal +{ + public InternalSignal(String source, String objectpath, String name, String iface, String sig, long serial, Object... parameters) throws DBusException + { + super(objectpath, iface, name, sig, parameters); + this.serial = serial; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Marshalling.java b/app/src/main/java/org/freedesktop/dbus/Marshalling.java new file mode 100644 index 00000000..e0857e9a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Marshalling.java @@ -0,0 +1,626 @@ +/* + 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.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.types.DBusListType; +import org.freedesktop.dbus.types.DBusMapType; +import org.freedesktop.dbus.types.DBusStructType; + +import cx.ath.matthew.debug.Debug; + +/** + * Contains static methods for marshalling values. + */ +public class Marshalling +{ + private static Map typeCache = new HashMap(); + /** + * Will return the DBus type corresponding to the given Java type. + * Note, container type should have their ParameterizedType not their + * Class passed in here. + * @param c The Java types. + * @return The DBus types. + * @throws DBusException If the given type cannot be converted to a DBus type. + */ + public static String getDBusType(Type[] c) throws DBusException + { + StringBuffer sb = new StringBuffer(); + for (Type t: c) + for (String s: getDBusType(t)) + sb.append(s); + return sb.toString(); + } + /** + * Will return the DBus type corresponding to the given Java type. + * Note, container type should have their ParameterizedType not their + * Class passed in here. + * @param c The Java type. + * @return The DBus type. + * @throws DBusException If the given type cannot be converted to a DBus type. + */ + public static String[] getDBusType(Type c) throws DBusException + { + String[] cached = typeCache.get(c); + if (null != cached) return cached; + cached = getDBusType(c, false); + typeCache.put(c, cached); + return cached; + } + /** + * Will return the DBus type corresponding to the given Java type. + * Note, container type should have their ParameterizedType not their + * Class passed in here. + * @param c The Java type. + * @param basic If true enforces this to be a non-compound type. (compound types are Maps, Structs and Lists/arrays). + * @return The DBus type. + * @throws DBusException If the given type cannot be converted to a DBus type. + */ + public static String[] getDBusType(Type c, boolean basic) throws DBusException + { + return recursiveGetDBusType(c, basic, 0); + } + private static StringBuffer[] out = new StringBuffer[10]; + @SuppressWarnings("unchecked") + public static String[] recursiveGetDBusType(Type c, boolean basic, int level) throws DBusException + { + if (out.length <= level) { + StringBuffer[] newout = new StringBuffer[out.length]; + System.arraycopy(out, 0, newout, 0, out.length); + out = newout; + } + if (null == out[level]) out[level] = new StringBuffer(); + else out[level].delete(0, out[level].length()); + + if (basic && !(c instanceof Class)) + throw new DBusException(c+ $_(" is not a basic type")); + + if (c instanceof TypeVariable) out[level].append((char) Message.ArgumentType.VARIANT); + else if (c instanceof GenericArrayType) { + out[level].append((char) Message.ArgumentType.ARRAY); + String[] s = recursiveGetDBusType(((GenericArrayType) c).getGenericComponentType(), false, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append(s[0]); + } else if ((c instanceof Class && + DBusSerializable.class.isAssignableFrom((Class) c)) || + (c instanceof ParameterizedType && + DBusSerializable.class.isAssignableFrom((Class) ((ParameterizedType) c).getRawType()))) { + // it's a custom serializable type + Type[] newtypes = null; + if (c instanceof Class) { + for (Method m: ((Class) c).getDeclaredMethods()) + if (m.getName().equals("deserialize")) + newtypes = m.getGenericParameterTypes(); + } + else + for (Method m: ((Class) ((ParameterizedType) c).getRawType()).getDeclaredMethods()) + if (m.getName().equals("deserialize")) + newtypes = m.getGenericParameterTypes(); + + if (null == newtypes) throw new DBusException($_("Serializable classes must implement a deserialize method")); + + String[] sigs = new String[newtypes.length]; + for (int j = 0; j < sigs.length; j++) { + String[] ss = recursiveGetDBusType(newtypes[j], false, level+1); + if (1 != ss.length) throw new DBusException($_("Serializable classes must serialize to native DBus types")); + sigs[j] = ss[0]; + } + return sigs; + } + else if (c instanceof ParameterizedType) { + ParameterizedType p = (ParameterizedType) c; + if (p.getRawType().equals(Map.class)) { + out[level].append("a{"); + Type[] t = p.getActualTypeArguments(); + try { + String[] s = recursiveGetDBusType(t[0], true, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append(s[0]); + s = recursiveGetDBusType(t[1], false, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append(s[0]); + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); + throw new DBusException($_("Map must have 2 parameters")); + } + out[level].append('}'); + } + else if (List.class.isAssignableFrom((Class) p.getRawType())) { + for (Type t: p.getActualTypeArguments()) { + if (Type.class.equals(t)) + out[level].append((char) Message.ArgumentType.SIGNATURE); + else { + String[] s = recursiveGetDBusType(t, false, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append((char) Message.ArgumentType.ARRAY); + out[level].append(s[0]); + } + } + } + else if (p.getRawType().equals(Variant.class)) { + out[level].append((char) Message.ArgumentType.VARIANT); + } + else if (DBusInterface.class.isAssignableFrom((Class) p.getRawType())) { + out[level].append((char) Message.ArgumentType.OBJECT_PATH); + } + else if (Tuple.class.isAssignableFrom((Class) p.getRawType())) { + Type[] ts = p.getActualTypeArguments(); + Vector vs = new Vector(); + for (Type t: ts) + for (String s: recursiveGetDBusType(t, false, level+1)) + vs.add(s); + return vs.toArray(new String[0]); + } + else + throw new DBusException($_("Exporting non-exportable parameterized type ")+c); + } + + else if (c.equals(Byte.class)) out[level].append((char) Message.ArgumentType.BYTE); + else if (c.equals(Byte.TYPE)) out[level].append((char) Message.ArgumentType.BYTE); + else if (c.equals(Boolean.class)) out[level].append((char) Message.ArgumentType.BOOLEAN); + else if (c.equals(Boolean.TYPE)) out[level].append((char) Message.ArgumentType.BOOLEAN); + else if (c.equals(Short.class)) out[level].append((char) Message.ArgumentType.INT16); + else if (c.equals(Short.TYPE)) out[level].append((char) Message.ArgumentType.INT16); + else if (c.equals(UInt16.class)) out[level].append((char) Message.ArgumentType.UINT16); + else if (c.equals(Integer.class)) out[level].append((char) Message.ArgumentType.INT32); + else if (c.equals(Integer.TYPE)) out[level].append((char) Message.ArgumentType.INT32); + else if (c.equals(UInt32.class)) out[level].append((char) Message.ArgumentType.UINT32); + else if (c.equals(Long.class)) out[level].append((char) Message.ArgumentType.INT64); + else if (c.equals(Long.TYPE)) out[level].append((char) Message.ArgumentType.INT64); + else if (c.equals(UInt64.class)) out[level].append((char) Message.ArgumentType.UINT64); + else if (c.equals(Double.class)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(Double.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(Float.class) && AbstractConnection.FLOAT_SUPPORT) out[level].append((char) Message.ArgumentType.FLOAT); + else if (c.equals(Float.class)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(Float.TYPE) && AbstractConnection.FLOAT_SUPPORT) out[level].append((char) Message.ArgumentType.FLOAT); + else if (c.equals(Float.TYPE)) out[level].append((char) Message.ArgumentType.DOUBLE); + else if (c.equals(String.class)) out[level].append((char) Message.ArgumentType.STRING); + else if (c.equals(Variant.class)) out[level].append((char) Message.ArgumentType.VARIANT); + else if (c instanceof Class && + DBusInterface.class.isAssignableFrom((Class) c)) out[level].append((char) Message.ArgumentType.OBJECT_PATH); + else if (c instanceof Class && + Path.class.equals((Class) c)) out[level].append((char) Message.ArgumentType.OBJECT_PATH); + else if (c instanceof Class && + ObjectPath.class.equals((Class) c)) out[level].append((char) Message.ArgumentType.OBJECT_PATH); + else if (c instanceof Class && + ((Class) c).isArray()) { + if (Type.class.equals(((Class) c).getComponentType())) + out[level].append((char) Message.ArgumentType.SIGNATURE); + else { + out[level].append((char) Message.ArgumentType.ARRAY); + String[] s = recursiveGetDBusType(((Class) c).getComponentType(), false, level+1); + if (s.length != 1) throw new DBusException($_("Multi-valued array types not permitted")); + out[level].append(s[0]); + } + } else if (c instanceof Class && + Struct.class.isAssignableFrom((Class) c)) { + out[level].append((char) Message.ArgumentType.STRUCT1); + Type[] ts = Container.getTypeCache(c); + if (null == ts) { + Field[] fs = ((Class) c).getDeclaredFields(); + ts = new Type[fs.length]; + for (Field f : fs) { + Position p = f.getAnnotation(Position.class); + if (null == p) continue; + ts[p.value()] = f.getGenericType(); + } + Container.putTypeCache(c, ts); + } + + for (Type t: ts) + if (t != null) + for (String s: recursiveGetDBusType(t, false, level+1)) + out[level].append(s); + out[level].append(')'); + } else { + throw new DBusException($_("Exporting non-exportable type ")+c); + } + + if (Debug.debug) Debug.print(Debug.VERBOSE, "Converted Java type: "+c+" to D-Bus Type: "+out[level]); + + return new String[] { out[level].toString() }; + } + + /** + * Converts a dbus type string into Java Type objects, + * @param dbus The DBus type or types. + * @param rv Vector to return the types in. + * @param limit Maximum number of types to parse (-1 == nolimit). + * @return number of characters parsed from the type string. + */ + public static int getJavaType(String dbus, List rv, int limit) throws DBusException + { + if (null == dbus || "".equals(dbus) || 0 == limit) return 0; + + try { + int i = 0; + for (; i < dbus.length() && (-1 == limit || limit > rv.size()); i++) + switch(dbus.charAt(i)) { + case Message.ArgumentType.STRUCT1: + int j = i+1; + for (int c = 1; c > 0; j++) { + if (')' == dbus.charAt(j)) c--; + else if (Message.ArgumentType.STRUCT1 == dbus.charAt(j)) c++; + } + + Vector contained = new Vector(); + int c = getJavaType(dbus.substring(i+1, j-1), contained, -1); + rv.add(new DBusStructType(contained.toArray(new Type[0]))); + i = j; + break; + case Message.ArgumentType.ARRAY: + if (Message.ArgumentType.DICT_ENTRY1 == dbus.charAt(i+1)) { + contained = new Vector(); + c = getJavaType(dbus.substring(i+2), contained, 2); + rv.add(new DBusMapType(contained.get(0), contained.get(1))); + i += (c+2); + } else { + contained = new Vector(); + c = getJavaType(dbus.substring(i+1), contained, 1); + rv.add(new DBusListType(contained.get(0))); + i += c; + } + break; + case Message.ArgumentType.VARIANT: + rv.add(Variant.class); + break; + case Message.ArgumentType.BOOLEAN: + rv.add(Boolean.class); + break; + case Message.ArgumentType.INT16: + rv.add(Short.class); + break; + case Message.ArgumentType.BYTE: + rv.add(Byte.class); + break; + case Message.ArgumentType.OBJECT_PATH: + rv.add(DBusInterface.class); + break; + case Message.ArgumentType.UINT16: + rv.add(UInt16.class); + break; + case Message.ArgumentType.INT32: + rv.add(Integer.class); + break; + case Message.ArgumentType.UINT32: + rv.add(UInt32.class); + break; + case Message.ArgumentType.INT64: + rv.add(Long.class); + break; + case Message.ArgumentType.UINT64: + rv.add(UInt64.class); + break; + case Message.ArgumentType.DOUBLE: + rv.add(Double.class); + break; + case Message.ArgumentType.FLOAT: + rv.add(Float.class); + break; + case Message.ArgumentType.STRING: + rv.add(String.class); + break; + case Message.ArgumentType.SIGNATURE: + rv.add(Type[].class); + break; + case Message.ArgumentType.DICT_ENTRY1: + rv.add(Map.Entry.class); + contained = new Vector(); + c = getJavaType(dbus.substring(i+1), contained, 2); + i+=c+1; + break; + default: + throw new DBusException(MessageFormat.format($_("Failed to parse DBus type signature: {0} ({1})."), new Object[] { dbus, dbus.charAt(i) })); + } + return i; + } catch (IndexOutOfBoundsException IOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, IOOBe); + throw new DBusException($_("Failed to parse DBus type signature: ")+dbus); + } + } + /** + * Recursively converts types for serialization onto DBus. + * @param parameters The parameters to convert. + * @param types The (possibly generic) types of the parameters. + * @return The converted parameters. + * @throws DBusException Thrown if there is an error in converting the objects. + */ + @SuppressWarnings("unchecked") + public static Object[] convertParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws DBusException + { + if (null == parameters) return null; + for (int i = 0; i < parameters.length; i++) { + if (Debug.debug) Debug.print(Debug.VERBOSE,"Converting "+i+" from "+parameters[i]+" to "+types[i]); + if (null == parameters[i]) continue; + + if (parameters[i] instanceof DBusSerializable) { + for (Method m: parameters[i].getClass().getDeclaredMethods()) + if (m.getName().equals("deserialize")) { + Type[] newtypes = m.getParameterTypes(); + Type[] expand = new Type[types.length + newtypes.length - 1]; + System.arraycopy(types, 0, expand, 0, i); + System.arraycopy(newtypes, 0, expand, i, newtypes.length); + System.arraycopy(types, i+1, expand, i+newtypes.length, types.length-i-1); + types = expand; + Object[] newparams = ((DBusSerializable) parameters[i]).serialize(); + Object[] exparams = new Object[parameters.length + newparams.length - 1]; + System.arraycopy(parameters, 0, exparams, 0, i); + System.arraycopy(newparams, 0, exparams, i, newparams.length); + System.arraycopy(parameters, i+1, exparams, i+newparams.length, parameters.length-i-1); + parameters = exparams; + } + i--; + } else if (parameters[i] instanceof Tuple) { + Type[] newtypes = ((ParameterizedType) types[i]).getActualTypeArguments(); + Type[] expand = new Type[types.length + newtypes.length - 1]; + System.arraycopy(types, 0, expand, 0, i); + System.arraycopy(newtypes, 0, expand, i, newtypes.length); + System.arraycopy(types, i+1, expand, i+newtypes.length, types. length-i-1); + types = expand; + Object[] newparams = ((Tuple) parameters[i]).getParameters(); + Object[] exparams = new Object[parameters.length + newparams.length - 1]; + System.arraycopy(parameters, 0, exparams, 0, i); + System.arraycopy(newparams, 0, exparams, i, newparams.length); + System.arraycopy(parameters, i+1, exparams, i+newparams.length, parameters.length-i-1); + parameters = exparams; + if (Debug.debug) Debug.print(Debug.VERBOSE, "New params: "+Arrays.deepToString(parameters)+" new types: "+Arrays.deepToString(types)); + i--; + } else if (types[i] instanceof TypeVariable && + !(parameters[i] instanceof Variant)) + // its an unwrapped variant, wrap it + parameters[i] = new Variant(parameters[i]); + else if (parameters[i] instanceof DBusInterface) + parameters[i] = conn.getExportedObject((DBusInterface) parameters[i]); + } + return parameters; + } + @SuppressWarnings("unchecked") + static Object deSerializeParameter(Object parameter, Type type, AbstractConnection conn) throws Exception + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from "+parameter.getClass()+" to "+type.getClass()); + if (null == parameter) + return null; + + // its a wrapped variant, unwrap it + if (type instanceof TypeVariable + && parameter instanceof Variant) { + parameter = ((Variant)parameter).getValue(); + } + + // Turn a signature into a Type[] + if (type instanceof Class + && ((Class) type).isArray() + && ((Class) type).getComponentType().equals(Type.class) + && parameter instanceof String) { + Vector rv = new Vector(); + getJavaType((String) parameter, rv, -1); + parameter = rv.toArray(new Type[0]); + } + + // its an object path, get/create the proxy + if (parameter instanceof ObjectPath) { + if (type instanceof Class && DBusInterface.class.isAssignableFrom((Class) type)) + parameter = conn.getExportedObject( + ((ObjectPath) parameter).source, + ((ObjectPath) parameter).path); + else + parameter = new Path(((ObjectPath) parameter).path); + } + + // it should be a struct. create it + if (parameter instanceof Object[] && + type instanceof Class && + Struct.class.isAssignableFrom((Class) type)) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating Struct "+type+" from "+parameter); + Type[] ts = Container.getTypeCache(type); + if (null == ts) { + Field[] fs = ((Class) type).getDeclaredFields(); + ts = new Type[fs.length]; + for (Field f : fs) { + Position p = f.getAnnotation(Position.class); + if (null == p) continue; + ts[p.value()] = f.getGenericType(); + } + Container.putTypeCache(type, ts); + } + + // recurse over struct contents + parameter = deSerializeParameters((Object[]) parameter, ts, conn); + for (Constructor con: ((Class) type).getDeclaredConstructors()) { + try { + parameter = con.newInstance((Object[]) parameter); + break; + } catch (IllegalArgumentException IAe) {} + } + } + + // recurse over arrays + if (parameter instanceof Object[]) { + Type[] ts = new Type[((Object[]) parameter).length]; + Arrays.fill(ts, parameter.getClass().getComponentType()); + parameter = deSerializeParameters((Object[]) parameter, + ts, conn); + } + if (parameter instanceof List) { + Type type2; + if (type instanceof ParameterizedType) + type2 = ((ParameterizedType) type).getActualTypeArguments()[0]; + else if (type instanceof GenericArrayType) + type2 = ((GenericArrayType) type).getGenericComponentType(); + else if (type instanceof Class && ((Class) type).isArray()) + type2 = ((Class) type).getComponentType(); + else + type2 = null; + if (null != type2) + parameter = deSerializeParameters((List) parameter, type2, conn); + } + + // correct floats if appropriate + if (type.equals(Float.class) || type.equals(Float.TYPE)) + if (!(parameter instanceof Float)) + parameter = ((Number) parameter).floatValue(); + + // make sure arrays are in the correct format + if (parameter instanceof Object[] || + parameter instanceof List || + parameter.getClass().isArray()) { + if (type instanceof ParameterizedType) + parameter = ArrayFrob.convert(parameter, + (Class) ((ParameterizedType) type).getRawType()); + else if (type instanceof GenericArrayType) { + Type ct = ((GenericArrayType) type).getGenericComponentType(); + Class cc = null; + if (ct instanceof Class) + cc = (Class) ct; + if (ct instanceof ParameterizedType) + cc = (Class) ((ParameterizedType) ct).getRawType(); + Object o = Array.newInstance(cc, 0); + parameter = ArrayFrob.convert(parameter, + o.getClass()); + } else if (type instanceof Class && + ((Class) type).isArray()) { + Class cc = ((Class) type).getComponentType(); + if ((cc.equals(Float.class) || cc.equals(Float.TYPE)) + && (parameter instanceof double[])) { + double[] tmp1 = (double[]) parameter; + float[] tmp2 = new float[tmp1.length]; + for (int i = 0; i < tmp1.length; i++) + tmp2[i] = (float) tmp1[i]; + parameter = tmp2; + } + Object o = Array.newInstance(cc, 0); + parameter = ArrayFrob.convert(parameter, + o.getClass()); + } + } + if (parameter instanceof DBusMap) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing a Map"); + DBusMap dmap = (DBusMap) parameter; + Type[] maptypes = ((ParameterizedType) type).getActualTypeArguments(); + for (int i = 0; i < dmap.entries.length; i++) { + dmap.entries[i][0] = deSerializeParameter(dmap.entries[i][0], maptypes[0], conn); + dmap.entries[i][1] = deSerializeParameter(dmap.entries[i][1], maptypes[1], conn); + } + } + return parameter; + } + static List deSerializeParameters(List parameters, Type type, AbstractConnection conn) throws Exception + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from "+parameters+" to "+type); + if (null == parameters) return null; + for (int i = 0; i < parameters.size(); i++) { + if (null == parameters.get(i)) continue; + + /* DO NOT DO THIS! IT'S REALLY NOT SUPPORTED! + * if (type instanceof Class && + DBusSerializable.class.isAssignableFrom((Class) types[i])) { + for (Method m: ((Class) types[i]).getDeclaredMethods()) + if (m.getName().equals("deserialize")) { + Type[] newtypes = m.getGenericParameterTypes(); + try { + Object[] sub = new Object[newtypes.length]; + System.arraycopy(parameters, i, sub, 0, newtypes.length); + sub = deSerializeParameters(sub, newtypes, conn); + DBusSerializable sz = (DBusSerializable) ((Class) types[i]).newInstance(); + m.invoke(sz, sub); + Object[] compress = new Object[parameters.length - newtypes.length + 1]; + System.arraycopy(parameters, 0, compress, 0, i); + compress[i] = sz; + System.arraycopy(parameters, i + newtypes.length, compress, i+1, parameters.length - i - newtypes.length); + parameters = compress; + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); + throw new DBusException("Not enough elements to create custom object from serialized data ("+(parameters.size()-i)+" < "+(newtypes.length)+")"); + } + } + } else*/ + parameters.set(i, deSerializeParameter(parameters.get(i), type, conn)); + } + return parameters; + } + + @SuppressWarnings("unchecked") + static Object[] deSerializeParameters(Object[] parameters, Type[] types, AbstractConnection conn) throws Exception + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Deserializing from "+Arrays.deepToString(parameters)+" to "+Arrays.deepToString(types)); + if (null == parameters) return null; + + if (types.length == 1 && types[0] instanceof ParameterizedType + && Tuple.class.isAssignableFrom((Class) ((ParameterizedType) types[0]).getRawType())) { + types = ((ParameterizedType) types[0]).getActualTypeArguments(); + } + + for (int i = 0; i < parameters.length; i++) { + // CHECK IF ARRAYS HAVE THE SAME LENGTH <-- has to happen after expanding parameters + if (i >= types.length) { + if (Debug.debug) { + for (int j = 0; j < parameters.length; j++) { + Debug.print(Debug.ERR, String.format("Error, Parameters difference (%1d, '%2s')", j, parameters[j].toString())); + } + } + throw new DBusException($_("Error deserializing message: number of parameters didn't match receiving signature")); + } + if (null == parameters[i]) continue; + + if ((types[i] instanceof Class && + DBusSerializable.class.isAssignableFrom((Class) types[i])) || + (types[i] instanceof ParameterizedType && + DBusSerializable.class.isAssignableFrom((Class) ((ParameterizedType) types[i]).getRawType()))) { + Class dsc; + if (types[i] instanceof Class) + dsc = (Class) types[i]; + else + dsc = (Class) ((ParameterizedType) types[i]).getRawType(); + for (Method m: dsc.getDeclaredMethods()) + if (m.getName().equals("deserialize")) { + Type[] newtypes = m.getGenericParameterTypes(); + try { + Object[] sub = new Object[newtypes.length]; + System.arraycopy(parameters, i, sub, 0, newtypes.length); + sub = deSerializeParameters(sub, newtypes, conn); + DBusSerializable sz = dsc.newInstance(); + m.invoke(sz, sub); + Object[] compress = new Object[parameters.length - newtypes.length + 1]; + System.arraycopy(parameters, 0, compress, 0, i); + compress[i] = sz; + System.arraycopy(parameters, i + newtypes.length, compress, i+1, parameters.length - i - newtypes.length); + parameters = compress; + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, AIOOBe); + throw new DBusException(MessageFormat.format($_("Not enough elements to create custom object from serialized data ({0} < {1})."), + new Object[] { parameters.length-i, newtypes.length })); + } + } + } else + parameters[i] = deSerializeParameter(parameters[i], types[i], conn); + } + return parameters; + } +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/Message.java b/app/src/main/java/org/freedesktop/dbus/Message.java new file mode 100644 index 00000000..c7762330 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Message.java @@ -0,0 +1,1132 @@ +/* + 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.Array; +import java.lang.reflect.Type; +import java.io.UnsupportedEncodingException; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.utils.Hexdump; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MarshallingException; +import org.freedesktop.dbus.exceptions.UnknownTypeCodeException; + +/** + * Superclass of all messages which are sent over the Bus. + * This class deals with all the marshalling to/from the wire format. + */ +public class Message +{ + /** Defines constants representing the endianness of the message. */ + public static interface Endian { + public static final byte BIG = 'B'; + public static final byte LITTLE = 'l'; + } + /** Defines constants representing the flags which can be set on a message. */ + public static interface Flags { + public static final byte NO_REPLY_EXPECTED = 0x01; + public static final byte NO_AUTO_START = 0x02; + public static final byte ASYNC = 0x40; + } + /** Defines constants for each message type. */ + public static interface MessageType { + public static final byte METHOD_CALL = 1; + public static final byte METHOD_RETURN = 2; + public static final byte ERROR = 3; + public static final byte SIGNAL = 4; + } + /** The current protocol major version. */ + public static final byte PROTOCOL = 1; + /** Defines constants for each valid header field type. */ + public static interface HeaderField { + public static final byte PATH = 1; + public static final byte INTERFACE = 2; + public static final byte MEMBER = 3; + public static final byte ERROR_NAME = 4; + public static final byte REPLY_SERIAL = 5; + public static final byte DESTINATION = 6; + public static final byte SENDER = 7; + public static final byte SIGNATURE = 8; + } + /** Defines constants for each argument type. + * There are two constants for each argument type, + * as a byte or as a String (the _STRING version) */ + public static interface ArgumentType { + public static final String BYTE_STRING="y"; + public static final String BOOLEAN_STRING="b"; + public static final String INT16_STRING="n"; + public static final String UINT16_STRING="q"; + public static final String INT32_STRING="i"; + public static final String UINT32_STRING="u"; + public static final String INT64_STRING="x"; + public static final String UINT64_STRING="t"; + public static final String DOUBLE_STRING="d"; + public static final String FLOAT_STRING="f"; + public static final String STRING_STRING="s"; + public static final String OBJECT_PATH_STRING="o"; + public static final String SIGNATURE_STRING="g"; + public static final String ARRAY_STRING="a"; + public static final String VARIANT_STRING="v"; + public static final String STRUCT_STRING="r"; + public static final String STRUCT1_STRING="("; + public static final String STRUCT2_STRING=")"; + public static final String DICT_ENTRY_STRING="e"; + public static final String DICT_ENTRY1_STRING="{"; + public static final String DICT_ENTRY2_STRING="}"; + + public static final byte BYTE='y'; + public static final byte BOOLEAN='b'; + public static final byte INT16='n'; + public static final byte UINT16='q'; + public static final byte INT32='i'; + public static final byte UINT32='u'; + public static final byte INT64='x'; + public static final byte UINT64='t'; + public static final byte DOUBLE='d'; + public static final byte FLOAT='f'; + public static final byte STRING='s'; + public static final byte OBJECT_PATH='o'; + public static final byte SIGNATURE='g'; + public static final byte ARRAY='a'; + public static final byte VARIANT='v'; + public static final byte STRUCT='r'; + public static final byte STRUCT1='('; + public static final byte STRUCT2=')'; + public static final byte DICT_ENTRY='e'; + public static final byte DICT_ENTRY1='{'; + public static final byte DICT_ENTRY2='}'; + } + /** Keep a static reference to each size of padding array to prevent allocation. */ + private static byte[][] padding; + static { + padding = new byte[][] { + null, + new byte[1], + new byte[2], + new byte[3], + new byte[4], + new byte[5], + new byte[6], + new byte[7] }; + } + /** Steps to increment the buffer array. */ + private static final int BUFFERINCREMENT = 20; + + private boolean big; + protected byte[][] wiredata; + protected long bytecounter; + protected Map headers; + protected static long globalserial = 0; + protected long serial; + protected byte type; + protected byte flags; + protected byte protover; + private Object[] args; + private byte[] body; + private long bodylen = 0; + private int preallocated = 0; + private int paofs = 0; + private byte[] pabuf; + private int bufferuse = 0; + + /** + * Returns the name of the given header field. + */ + public static String getHeaderFieldName(byte field) + { + switch (field) { + case HeaderField.PATH: return "Path"; + case HeaderField.INTERFACE: return "Interface"; + case HeaderField.MEMBER: return "Member"; + case HeaderField.ERROR_NAME: return "Error Name"; + case HeaderField.REPLY_SERIAL: return "Reply Serial"; + case HeaderField.DESTINATION: return "Destination"; + case HeaderField.SENDER: return "Sender"; + case HeaderField.SIGNATURE: return "Signature"; + default: return "Invalid"; + } + } + + /** + * Create a message; only to be called by sub-classes. + * @param endian The endianness to create the message. + * @param type The message type. + * @param flags Any message flags. + */ + protected Message(byte endian, byte type, byte flags) throws DBusException + { + wiredata = new byte[BUFFERINCREMENT][]; + headers = new HashMap(); + big = (Endian.BIG == endian); + bytecounter = 0; + synchronized (Message.class) { + serial = ++globalserial; + } + if (Debug.debug) Debug.print(Debug.DEBUG, "Creating message with serial "+serial); + this.type = type; + this.flags = flags; + preallocate(4); + append("yyyy", endian, type, flags, Message.PROTOCOL); + } + /** + * Create a blank message. Only to be used when calling populate. + */ + protected Message() + { + wiredata = new byte[BUFFERINCREMENT][]; + headers = new HashMap(); + bytecounter = 0; + } + /** + * Create a message from wire-format data. + * @param msg D-Bus serialized data of type yyyuu + * @param headers D-Bus serialized data of type a(yv) + * @param body D-Bus serialized data of the signature defined in headers. + */ + @SuppressWarnings("unchecked") + void populate(byte[] msg, byte[] headers, byte[] body) throws DBusException + { + big = (msg[0] == Endian.BIG); + type = msg[1]; + flags = msg[2]; + protover = msg[3]; + wiredata[0] = msg; + wiredata[1] = headers; + wiredata[2] = body; + this.body = body; + bufferuse = 3; + bodylen = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 4)[0]).longValue(); + serial = ((Number) extract(Message.ArgumentType.UINT32_STRING, msg, 8)[0]).longValue(); + bytecounter = msg.length+headers.length+body.length; + if (Debug.debug) Debug.print(Debug.VERBOSE, headers); + Object[] hs = extract("a(yv)", headers, 0); + if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.deepToString(hs)); + for (Object o: (Vector) hs[0]) { + this.headers.put((Byte) ((Object[])o)[0], ((Variant)((Object[])o)[1]).getValue()); + } + } + /** + * Create a buffer of num bytes. + * Data is copied to this rather than added to the buffer list. + */ + private void preallocate(int num) + { + preallocated = 0; + pabuf = new byte[num]; + appendBytes(pabuf); + preallocated = num; + paofs = 0; + } + /** + * Ensures there are enough free buffers. + * @param num number of free buffers to create. + */ + private void ensureBuffers(int num) + { + int increase = num - wiredata.length + bufferuse; + if (increase > 0) { + if (increase < BUFFERINCREMENT) increase = BUFFERINCREMENT; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing "+bufferuse); + byte[][] temp = new byte[wiredata.length+increase][]; + System.arraycopy(wiredata, 0, temp, 0, wiredata.length); + wiredata = temp; + } + } + /** + * Appends a buffer to the buffer list. + */ + protected void appendBytes(byte[] buf) + { + if (null == buf) return; + if (preallocated > 0) { + if (paofs+buf.length > pabuf.length) + throw new ArrayIndexOutOfBoundsException(MessageFormat.format($_("Array index out of bounds, paofs={0}, pabuf.length={1}, buf.length={2}."), new Object[] { paofs, pabuf.length, buf.length })); + System.arraycopy(buf, 0, pabuf, paofs, buf.length); + paofs += buf.length; + preallocated -= buf.length; + } else { + if (bufferuse == wiredata.length) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing "+bufferuse); + byte[][] temp = new byte[wiredata.length+BUFFERINCREMENT][]; + System.arraycopy(wiredata, 0, temp, 0, wiredata.length); + wiredata = temp; + } + wiredata[bufferuse++] = buf; + bytecounter += buf.length; + } + } + /** + * Appends a byte to the buffer list. + */ + protected void appendByte(byte b) + { + if (preallocated > 0) { + pabuf[paofs++] = b; + preallocated--; + } else { + if (bufferuse == wiredata.length) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Resizing "+bufferuse); + byte[][] temp = new byte[wiredata.length+BUFFERINCREMENT][]; + System.arraycopy(wiredata, 0, temp, 0, wiredata.length); + wiredata = temp; + } + wiredata[bufferuse++] = new byte[] { b }; + bytecounter++; + } + } + /** + * Demarshalls an integer of a given width from a buffer. + * Endianness is determined from the format of the message. + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param width The byte-width of the int. + */ + public long demarshallint(byte[] buf, int ofs, int width) + { return big ? demarshallintBig(buf,ofs,width) : demarshallintLittle(buf,ofs,width); } + /** + * Demarshalls an integer of a given width from a buffer. + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param endian The endianness to use in demarshalling. + * @param width The byte-width of the int. + */ + public static long demarshallint(byte[] buf, int ofs, byte endian, int width) + { return endian==Endian.BIG ? demarshallintBig(buf,ofs,width) : demarshallintLittle(buf,ofs,width); } + /** + * Demarshalls an integer of a given width from a buffer using big-endian format. + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param width The byte-width of the int. + */ + public static long demarshallintBig(byte[] buf, int ofs, int width) + { + long l = 0; + for (int i = 0; i < width; i++) { + l <<=8; + l |= (buf[ofs+i] & 0xFF); + } + return l; + } + /** + * Demarshalls an integer of a given width from a buffer using little-endian format. + * @param buf The buffer to demarshall from. + * @param ofs The offset to demarshall from. + * @param width The byte-width of the int. + */ + public static long demarshallintLittle(byte[] buf, int ofs, int width) + { + long l = 0; + for (int i = (width-1); i >= 0; i--) { + l <<=8; + l |= (buf[ofs+i] & 0xFF); + } + return l; + } + /** + * Marshalls an integer of a given width and appends it to the message. + * Endianness is determined from the message. + * @param l The integer to marshall. + * @param width The byte-width of the int. + */ + public void appendint(long l, int width) + { + byte[] buf = new byte[width]; + marshallint(l, buf, 0, width); + appendBytes(buf); + } + /** + * Marshalls an integer of a given width into a buffer. + * Endianness is determined from the message. + * @param l The integer to marshall. + * @param buf The buffer to marshall to. + * @param ofs The offset to marshall to. + * @param width The byte-width of the int. + */ + public void marshallint(long l, byte[] buf, int ofs, int width) + { + if (big) marshallintBig(l, buf, ofs, width); else marshallintLittle(l, buf, ofs, width); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Marshalled int "+l+" to "+Hexdump.toHex(buf,ofs,width)); + } + /** + * Marshalls an integer of a given width into a buffer using big-endian format. + * @param l The integer to marshall. + * @param buf The buffer to marshall to. + * @param ofs The offset to marshall to. + * @param width The byte-width of the int. + */ + public static void marshallintBig(long l, byte[] buf, int ofs, int width) + { + for (int i = (width-1); i >= 0; i--) { + buf[i+ofs] = (byte) (l & 0xFF); + l >>= 8; + } + } + /** + * Marshalls an integer of a given width into a buffer using little-endian format. + * @param l The integer to marshall. + * @param buf The buffer to demarshall to. + * @param ofs The offset to demarshall to. + * @param width The byte-width of the int. + */ + public static void marshallintLittle(long l, byte[] buf, int ofs, int width) + { + for (int i = 0; i < width; i++) { + buf[i+ofs] = (byte) (l & 0xFF); + l >>= 8; + } + } + public byte[][] getWireData() + { + return wiredata; + } + /** + * Formats the message in a human-readable format. + */ + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append(getClass().getSimpleName()); + sb.append ('('); + sb.append (flags); + sb.append (','); + sb.append(serial); + sb.append (')'); + sb.append (' '); + sb.append ('{'); + sb.append(' '); + if (headers.size() == 0) + sb.append('}'); + else { + for (Byte field: headers.keySet()) { + sb.append(getHeaderFieldName(field)); + sb.append('='); + sb.append('>'); + sb.append(headers.get(field).toString()); + sb.append(','); + sb.append(' '); + } + sb.setCharAt(sb.length()-2,' '); + sb.setCharAt(sb.length()-1,'}'); + } + sb.append(' '); + sb.append('{'); + sb.append(' '); + Object[] args = null; + try { + args = getParameters(); + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + } + if (null == args || 0 == args.length) + sb.append('}'); + else { + for (Object o: args) { + if (o instanceof Object[]) + sb.append(Arrays.deepToString((Object[]) o)); + else if (o instanceof byte[]) + sb.append(Arrays.toString((byte[]) o)); + else if (o instanceof int[]) + sb.append(Arrays.toString((int[]) o)); + else if (o instanceof short[]) + sb.append(Arrays.toString((short[]) o)); + else if (o instanceof long[]) + sb.append(Arrays.toString((long[]) o)); + else if (o instanceof boolean[]) + sb.append(Arrays.toString((boolean[]) o)); + else if (o instanceof double[]) + sb.append(Arrays.toString((double[]) o)); + else if (o instanceof float[]) + sb.append(Arrays.toString((float[]) o)); + else + sb.append(o.toString()); + sb.append(','); + sb.append(' '); + } + sb.setCharAt(sb.length()-2,' '); + sb.setCharAt(sb.length()-1,'}'); + } + return sb.toString(); + } + /** + * Returns the value of the header field of a given field. + * @param type The field to return. + * @return The value of the field or null if unset. + */ + public Object getHeader(byte type) { return headers.get(type); } + /** + * Appends a value to the message. + * The type of the value is read from a D-Bus signature and used to marshall + * the value. + * @param sigb A buffer of the D-Bus signature. + * @param sigofs The offset into the signature corresponding to this value. + * @param data The value to marshall. + * @return The offset into the signature of the end of this value's type. + */ + @SuppressWarnings("unchecked") + private int appendone(byte[] sigb, int sigofs, Object data) throws DBusException + { + try { + int i = sigofs; + if (Debug.debug) Debug.print(Debug.VERBOSE, (Object) bytecounter); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending type: "+((char)sigb[i])+" value: "+data); + + // pad to the alignment of this type. + pad(sigb[i]); + switch (sigb[i]) { + case ArgumentType.BYTE: + appendByte(((Number) data).byteValue()); + break; + case ArgumentType.BOOLEAN: + appendint(((Boolean) data).booleanValue() ? 1 : 0, 4); + break; + case ArgumentType.DOUBLE: + long l = Double.doubleToLongBits(((Number) data).doubleValue()); + appendint(l, 8); + break; + case ArgumentType.FLOAT: + int rf = Float.floatToIntBits(((Number) data).floatValue()); + appendint(rf, 4); + break; + case ArgumentType.UINT32: + appendint(((Number) data).longValue(), 4); + break; + case ArgumentType.INT64: + appendint(((Number) data).longValue(), 8); + break; + case ArgumentType.UINT64: + if (big) { + appendint(((UInt64) data).top(), 4); + appendint(((UInt64) data).bottom(), 4); + } else { + appendint(((UInt64) data).bottom(), 4); + appendint(((UInt64) data).top(), 4); + } + break; + case ArgumentType.INT32: + appendint(((Number) data).intValue(), 4); + break; + case ArgumentType.UINT16: + appendint(((Number) data).intValue(), 2); + break; + case ArgumentType.INT16: + appendint(((Number) data).shortValue(), 2); + break; + case ArgumentType.STRING: + case ArgumentType.OBJECT_PATH: + // Strings are marshalled as a UInt32 with the length, + // followed by the String, followed by a null byte. + String payload = data.toString(); + byte[] payloadbytes = null; + try { + payloadbytes = payload.getBytes("UTF-8"); + } catch (UnsupportedEncodingException UEe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe); + throw new DBusException($_("System does not support UTF-8 encoding")); + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending String of length "+payloadbytes.length); + appendint(payloadbytes.length, 4); + appendBytes(payloadbytes); + appendBytes(padding[1]); + //pad(ArgumentType.STRING);? do we need this? + break; + case ArgumentType.SIGNATURE: + // Signatures are marshalled as a byte with the length, + // followed by the String, followed by a null byte. + // Signatures are generally short, so preallocate the array + // for the string, length and null byte. + if (data instanceof Type[]) + payload = Marshalling.getDBusType((Type[]) data); + else + payload = (String) data; + byte[] pbytes = payload.getBytes(); + preallocate(2+pbytes.length); + appendByte((byte) pbytes.length); + appendBytes(pbytes); + appendByte((byte) 0); + break; + case ArgumentType.ARRAY: + // Arrays are given as a UInt32 for the length in bytes, + // padding to the element alignment, then elements in + // order. The length is the length from the end of the + // initial padding to the end of the last element. + if (Debug.debug) { + if (data instanceof Object[]) + Debug.print(Debug.VERBOSE, "Appending array: "+Arrays.deepToString((Object[])data)); + } + + byte[] alen = new byte[4]; + appendBytes(alen); + pad(sigb[++i]); + long c = bytecounter; + + // optimise primatives + if (data.getClass().isArray() && + data.getClass().getComponentType().isPrimitive()) { + byte[] primbuf; + int algn = getAlignment(sigb[i]); + int len = Array.getLength(data); + switch (sigb[i]) { + case ArgumentType.BYTE: + primbuf = (byte[]) data; + break; + case ArgumentType.INT16: + case ArgumentType.INT32: + case ArgumentType.INT64: + primbuf = new byte[len*algn]; + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Array.getLong(data, j), primbuf, k, algn); + break; + case ArgumentType.BOOLEAN: + primbuf = new byte[len*algn]; + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Array.getBoolean(data, j)?1:0, primbuf, k, algn); + break; + case ArgumentType.DOUBLE: + primbuf = new byte[len*algn]; + if (data instanceof float[]) + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Double.doubleToRawLongBits(((float[])data)[j]), + primbuf, k, algn); + else + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint(Double.doubleToRawLongBits(((double[])data)[j]), + primbuf, k, algn); + break; + case ArgumentType.FLOAT: + primbuf = new byte[len*algn]; + for (int j = 0, k = 0; j < len; j++, k += algn) + marshallint( + Float.floatToRawIntBits(((float[])data)[j]), + primbuf, k, algn); + break; + default: + throw new MarshallingException($_("Primative array being sent as non-primative array.")); + } + appendBytes(primbuf); + } else if (data instanceof List) { + Object[] contents = ((List) data).toArray(); + int diff = i; + ensureBuffers(contents.length*4); + for (Object o: contents) + diff = appendone(sigb, i, o); + i = diff; + } else if (data instanceof Map) { + int diff = i; + ensureBuffers(((Map) data).size()*6); + for (Map.Entry o: ((Map) data).entrySet()) + diff = appendone(sigb, i, o); + if (i == diff) { + // advance the type parser even on 0-size arrays. + Vector temp = new Vector(); + byte[] temp2 = new byte[sigb.length-diff]; + System.arraycopy(sigb, diff, temp2, 0, temp2.length); + String temp3 = new String(temp2); + int temp4 = Marshalling.getJavaType(temp3, temp, 1); + diff += temp4; + } + i = diff; + } else { + Object[] contents = (Object[]) data; + ensureBuffers(contents.length*4); + int diff = i; + for (Object o: contents) + diff = appendone(sigb, i, o); + i = diff; + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "start: "+c+" end: "+bytecounter+" length: "+(bytecounter-c)); + marshallint(bytecounter-c, alen, 0, 4); + break; + case ArgumentType.STRUCT1: + // Structs are aligned to 8 bytes + // and simply contain each element marshalled in order + Object[] contents; + if (data instanceof Container) + contents = ((Container) data).getParameters(); + else + contents = (Object[]) data; + ensureBuffers(contents.length*4); + int j = 0; + for (i++; sigb[i] != ArgumentType.STRUCT2; i++) + i = appendone(sigb, i, contents[j++]); + break; + case ArgumentType.DICT_ENTRY1: + // Dict entries are the same as structs. + if (data instanceof Map.Entry) { + i++; + i = appendone(sigb, i, ((Map.Entry) data).getKey()); + i++; + i = appendone(sigb, i, ((Map.Entry) data).getValue()); + i++; + } else { + contents = (Object[]) data; + j = 0; + for (i++; sigb[i] != ArgumentType.DICT_ENTRY2; i++) + i = appendone(sigb, i, contents[j++]); + } + break; + case ArgumentType.VARIANT: + // Variants are marshalled as a signature + // followed by the value. + if (data instanceof Variant) { + Variant var = (Variant) data; + appendone(new byte[] {ArgumentType.SIGNATURE}, 0, var.getSig()); + appendone((var.getSig()).getBytes(), 0, var.getValue()); + } else if (data instanceof Object[]) { + contents = (Object[]) data; + appendone(new byte[] {ArgumentType.SIGNATURE}, 0, contents[0]); + appendone(((String) contents[0]).getBytes(), 0, contents[1]); + } else { + String sig = Marshalling.getDBusType(data.getClass())[0]; + appendone(new byte[] {ArgumentType.SIGNATURE}, 0, sig); + appendone((sig).getBytes(), 0, data); + } + break; + } + return i; + } catch (ClassCastException CCe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, CCe); + throw new MarshallingException(MessageFormat.format($_("Trying to marshall to unconvertable type (from {0} to {1})."), new Object[] { data.getClass().getName(), sigb[sigofs] })); + } + } + /** + * Pad the message to the proper alignment for the given type. + */ + public void pad(byte type) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "padding for "+(char)type); + int a = getAlignment(type); + if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated+" "+paofs+" "+bytecounter+" "+a); + int b = (int) ((bytecounter-preallocated)%a); + if (0 == b) return; + a = (a-b); + if (preallocated > 0) { + paofs += a; + preallocated -= a; + } else + appendBytes(padding[a]); + if (Debug.debug) Debug.print(Debug.VERBOSE, preallocated+" "+paofs+" "+bytecounter+" "+a); + } + /** + * Return the alignment for a given type. + */ + public static int getAlignment(byte type) + { + switch (type) { + case 2: + case ArgumentType.INT16: + case ArgumentType.UINT16: + return 2; + case 4: + case ArgumentType.BOOLEAN: + case ArgumentType.FLOAT: + case ArgumentType.INT32: + case ArgumentType.UINT32: + case ArgumentType.STRING: + case ArgumentType.OBJECT_PATH: + case ArgumentType.ARRAY: + return 4; + case 8: + case ArgumentType.INT64: + case ArgumentType.UINT64: + case ArgumentType.DOUBLE: + case ArgumentType.STRUCT: + case ArgumentType.DICT_ENTRY: + case ArgumentType.STRUCT1: + case ArgumentType.DICT_ENTRY1: + case ArgumentType.STRUCT2: + case ArgumentType.DICT_ENTRY2: + return 8; + case 1: + case ArgumentType.BYTE: + case ArgumentType.SIGNATURE: + case ArgumentType.VARIANT: + default: + return 1; + } + } + /** + * Append a series of values to the message. + * @param sig The signature(s) of the value(s). + * @param data The value(s). + */ + public void append(String sig, Object... data) throws DBusException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Appending sig: "+sig+" data: "+Arrays.deepToString(data)); + byte[] sigb = sig.getBytes(); + int j = 0; + for (int i = 0; i < sigb.length; i++) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Appending item: "+i+" "+((char)sigb[i])+" "+j); + i = appendone(sigb, i, data[j++]); + } + } + /** + * Align a counter to the given type. + * @param current The current counter. + * @param type The type to align to. + * @return The new, aligned, counter. + */ + public int align(int current, byte type) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "aligning to "+(char)type); + int a = getAlignment(type); + if (0 == (current%a)) return current; + return current+(a-(current%a)); + } + /** + * Demarshall one value from a buffer. + * @param sigb A buffer of the D-Bus signature. + * @param buf The buffer to demarshall from. + * @param ofs An array of two ints, the offset into the signature buffer + * and the offset into the data buffer. These values will be + * updated to the start of the next value ofter demarshalling. + * @param contained converts nested arrays to Lists + * @return The demarshalled value. + */ + private Object extractone(byte[] sigb, byte[] buf, int[] ofs, boolean contained) throws DBusException + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Extracting type: "+((char)sigb[ofs[0]])+" from offset "+ofs[1]); + Object rv = null; + ofs[1] = align(ofs[1], sigb[ofs[0]]); + switch (sigb[ofs[0]]) { + case ArgumentType.BYTE: + rv = buf[ofs[1]++]; + break; + case ArgumentType.UINT32: + rv = new UInt32(demarshallint(buf, ofs[1], 4)); + ofs[1] += 4; + break; + case ArgumentType.INT32: + rv = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + break; + case ArgumentType.INT16: + rv = (short) demarshallint(buf, ofs[1], 2); + ofs[1] += 2; + break; + case ArgumentType.UINT16: + rv = new UInt16((int) demarshallint(buf, ofs[1], 2)); + ofs[1] += 2; + break; + case ArgumentType.INT64: + rv = demarshallint(buf, ofs[1], 8); + ofs[1] += 8; + break; + case ArgumentType.UINT64: + long top; + long bottom; + if (big) { + top = demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + bottom = demarshallint(buf, ofs[1], 4); + } else { + bottom = demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + top = demarshallint(buf, ofs[1], 4); + } + rv = new UInt64(top, bottom); + ofs[1] += 4; + break; + case ArgumentType.DOUBLE: + long l = demarshallint(buf, ofs[1], 8); + ofs[1] += 8; + rv = Double.longBitsToDouble(l); + break; + case ArgumentType.FLOAT: + int rf = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + rv = Float.intBitsToFloat(rf); + break; + case ArgumentType.BOOLEAN: + rf = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + rv = (1==rf)?Boolean.TRUE:Boolean.FALSE; + break; + case ArgumentType.ARRAY: + long size = demarshallint(buf, ofs[1], 4); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Reading array of size: "+size); + ofs[1] += 4; + byte algn = (byte) getAlignment(sigb[++ofs[0]]); + ofs[1] = align(ofs[1], sigb[ofs[0]]); + int length = (int) (size / algn); + if (length > DBusConnection.MAX_ARRAY_LENGTH) + throw new MarshallingException($_("Arrays must not exceed ")+DBusConnection.MAX_ARRAY_LENGTH); + // optimise primatives + switch (sigb[ofs[0]]) { + case ArgumentType.BYTE: + rv = new byte[length]; + System.arraycopy(buf, ofs[1], rv, 0, length); + ofs[1] += size; + break; + case ArgumentType.INT16: + rv = new short[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((short[]) rv)[j] = (short) demarshallint(buf, ofs[1], algn); + break; + case ArgumentType.INT32: + rv = new int[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((int[]) rv)[j] = (int) demarshallint(buf, ofs[1], algn); + break; + case ArgumentType.INT64: + rv = new long[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((long[]) rv)[j] = demarshallint(buf, ofs[1], algn); + break; + case ArgumentType.BOOLEAN: + rv = new boolean[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((boolean[]) rv)[j] = (1 == demarshallint(buf, ofs[1], algn)); + break; + case ArgumentType.FLOAT: + rv = new float[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((float[]) rv)[j] = + Float.intBitsToFloat((int)demarshallint(buf, ofs[1], algn)); + break; + case ArgumentType.DOUBLE: + rv = new double[length]; + for (int j = 0; j < length; j++, ofs[1] += algn) + ((double[]) rv)[j] = + Double.longBitsToDouble(demarshallint(buf, ofs[1], algn)); + break; + case ArgumentType.DICT_ENTRY1: + if (0 == size) { + // advance the type parser even on 0-size arrays. + Vector temp = new Vector(); + byte[] temp2 = new byte[sigb.length-ofs[0]]; + System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length); + String temp3 = new String(temp2); + // ofs[0] gets incremented anyway. Leave one character on the stack + int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1; + ofs[0] += temp4; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Aligned type: "+temp3+" "+temp4+" "+ofs[0]); + } + int ofssave = ofs[0]; + long end = ofs[1]+size; + Vector entries = new Vector(); + while (ofs[1] < end) { + ofs[0] = ofssave; + entries.add((Object[]) extractone(sigb, buf, ofs, true)); + } + rv = new DBusMap(entries.toArray(new Object[0][])); + break; + default: + if (0 == size) { + // advance the type parser even on 0-size arrays. + Vector temp = new Vector(); + byte[] temp2 = new byte[sigb.length-ofs[0]]; + System.arraycopy(sigb, ofs[0], temp2, 0, temp2.length); + String temp3 = new String(temp2); + // ofs[0] gets incremented anyway. Leave one character on the stack + int temp4 = Marshalling.getJavaType(temp3, temp, 1) - 1; + ofs[0] += temp4; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Aligned type: "+temp3+" "+temp4+" "+ofs[0]); + } + ofssave = ofs[0]; + end = ofs[1]+size; + Vector contents = new Vector(); + while (ofs[1] < end) { + ofs[0] = ofssave; + contents.add(extractone(sigb, buf, ofs, true)); + } + rv = contents; + } + if (contained && !(rv instanceof List) && !(rv instanceof Map)) + rv = ArrayFrob.listify(rv); + break; + case ArgumentType.STRUCT1: + Vector contents = new Vector(); + while (sigb[++ofs[0]] != ArgumentType.STRUCT2) + contents.add(extractone(sigb, buf, ofs, true)); + rv = contents.toArray(); + break; + case ArgumentType.DICT_ENTRY1: + Object[] decontents = new Object[2]; + if (Debug.debug) Debug.print(Debug.VERBOSE, "Extracting Dict Entry ("+Hexdump.toAscii(sigb,ofs[0],sigb.length-ofs[0])+") from: "+Hexdump.toHex(buf,ofs[1],buf.length-ofs[1])); + ofs[0]++; + decontents[0] = extractone(sigb, buf, ofs, true); + ofs[0]++; + decontents[1] = extractone(sigb, buf, ofs, true); + ofs[0]++; + rv = decontents; + break; + case ArgumentType.VARIANT: + int[] newofs = new int[] { 0, ofs[1] }; + String sig = (String) extract(ArgumentType.SIGNATURE_STRING, buf, newofs)[0]; + newofs[0] = 0; + rv = new Variant(extract(sig, buf, newofs)[0] , sig); + ofs[1] = newofs[1]; + break; + case ArgumentType.STRING: + length = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + try { + rv = new String(buf, ofs[1], length, "UTF-8"); + } catch (UnsupportedEncodingException UEe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(UEe); + throw new DBusException($_("System does not support UTF-8 encoding")); + } + ofs[1] += length + 1; + break; + case ArgumentType.OBJECT_PATH: + length = (int) demarshallint(buf, ofs[1], 4); + ofs[1] += 4; + rv = new ObjectPath(getSource(), new String(buf, ofs[1], length)); + ofs[1] += length + 1; + break; + case ArgumentType.SIGNATURE: + length = (buf[ofs[1]++] & 0xFF); + rv = new String(buf, ofs[1], length); + ofs[1] += length + 1; + break; + default: + throw new UnknownTypeCodeException(sigb[ofs[0]]); + } + if (Debug.debug) if (rv instanceof Object[]) + Debug.print(Debug.VERBOSE, "Extracted: "+Arrays.deepToString((Object[]) rv)+" (now at "+ofs[1]+")"); + else + Debug.print(Debug.VERBOSE, "Extracted: "+rv+" (now at "+ofs[1]+")"); + return rv; + } + /** + * Demarshall values from a buffer. + * @param sig The D-Bus signature(s) of the value(s). + * @param buf The buffer to demarshall from. + * @param ofs The offset into the data buffer to start. + * @return The demarshalled value(s). + */ + public Object[] extract(String sig, byte[] buf, int ofs) throws DBusException + { + return extract(sig, buf, new int[] { 0, ofs }); + } + /** + * Demarshall values from a buffer. + * @param sig The D-Bus signature(s) of the value(s). + * @param buf The buffer to demarshall from. + * @param ofs An array of two ints, the offset into the signature + * and the offset into the data buffer. These values will be + * updated to the start of the next value ofter demarshalling. + * @return The demarshalled value(s). + */ + public Object[] extract(String sig, byte[] buf, int[] ofs) throws DBusException + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "extract("+sig+",#"+buf.length+", {"+ofs[0]+","+ofs[1]+"}"); + Vector rv = new Vector(); + byte[] sigb = sig.getBytes(); + for (int[] i = ofs; i[0] < sigb.length; i[0]++) { + rv.add(extractone(sigb, buf, i, false)); + } + return rv.toArray(); + } + /** + * Returns the Bus ID that sent the message. + */ + public String getSource() { return (String) headers.get(HeaderField.SENDER); } + /** + * Returns the destination of the message. + */ + public String getDestination() { return (String) headers.get(HeaderField.DESTINATION); } + /** + * Returns the interface of the message. + */ + public String getInterface() { return (String) headers.get(HeaderField.INTERFACE); } + /** + * Returns the object path of the message. + */ + public String getPath() + { + Object o = headers.get(HeaderField.PATH); + if (null == o) return null; + return o.toString(); + } + /** + * Returns the member name or error name this message represents. + */ + public String getName() + { + if (this instanceof Error) + return (String) headers.get(HeaderField.ERROR_NAME); + else + return (String) headers.get(HeaderField.MEMBER); + } + /** + * Returns the dbus signature of the parameters. + */ + public String getSig() { return (String) headers.get(HeaderField.SIGNATURE); } + /** + * Returns the message flags. + */ + public int getFlags() { return flags; } + /** + * Returns the message serial ID (unique for this connection) + * @return the message serial. + */ + public long getSerial() { return serial; } + /** + * If this is a reply to a message, this returns its serial. + * @return The reply serial, or 0 if it is not a reply. + */ + public long getReplySerial() + { + Number l = (Number) headers.get(HeaderField.REPLY_SERIAL); + if (null == l) return 0; + return l.longValue(); + } + /** + * Parses and returns the parameters to this message as an Object array. + */ + public Object[] getParameters() throws DBusException + { + if (null == args && null != body) { + String sig = (String) headers.get(HeaderField.SIGNATURE); + if (null != sig && 0 != body.length) { + args = extract(sig, body, 0); + } else args = new Object[0]; + } + return args; + } + protected void setArgs(Object[] args) { this.args = args; } + /** + * Warning, do not use this method unless you really know what you are doing. + */ + public void setSource(String source) throws DBusException + { + if (null != body) { + wiredata = new byte[BUFFERINCREMENT][]; + bufferuse = 0; + bytecounter = 0; + preallocate(12); + append("yyyyuu", big ? Endian.BIG : Endian.LITTLE, type, flags, protover, bodylen, serial); + headers.put(HeaderField.SENDER, source); + Object[][] newhead = new Object[headers.size()][]; + int i = 0; + for (Byte b: headers.keySet()) { + newhead[i] = new Object[2]; + newhead[i][0] = b; + newhead[i][1] = headers.get(b); + i++; + } + append("a(yv)", (Object) newhead); + pad((byte) 8); + appendBytes(body); + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/MessageReader.java b/app/src/main/java/org/freedesktop/dbus/MessageReader.java new file mode 100644 index 00000000..6ce7090a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MessageReader.java @@ -0,0 +1,175 @@ +/* + 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.io.BufferedInputStream; +import java.io.EOFException; +import java.io.InputStream; +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.text.MessageFormat; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.utils.Hexdump; + +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MessageTypeException; +import org.freedesktop.dbus.exceptions.MessageProtocolVersionException; + +public class MessageReader +{ + private InputStream in; + private byte[] buf = null; + private byte[] tbuf = null; + private byte[] header = null; + private byte[] body = null; + private int[] len = new int[4]; + public MessageReader(InputStream in) + { + this.in = new BufferedInputStream(in); + } + public Message readMessage() throws IOException, DBusException + { + int rv; + /* Read the 12 byte fixed header, retrying as neccessary */ + if (null == buf) { buf = new byte[12]; len[0] = 0; } + if (len[0] < 12) { + try { rv = in.read(buf, len[0], 12-len[0]); } + catch (SocketTimeoutException STe) { return null; } + if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); + len[0] += rv; + } + if (len[0] == 0) return null; + if (len[0] < 12) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[0]+" of 12 bytes of header"); + return null; + } + + /* Parse the details from the header */ + byte endian = buf[0]; + byte type = buf[1]; + byte protover = buf[3]; + if (protover > Message.PROTOCOL) { + buf = null; + throw new MessageProtocolVersionException(MessageFormat.format($_("Protocol version {0} is unsupported"), new Object[] { protover })); + } + + /* Read the length of the variable header */ + if (null == tbuf) { tbuf = new byte[4]; len[1] = 0; } + if (len[1] < 4) { + try { rv = in.read(tbuf, len[1], 4-len[1]); } + catch (SocketTimeoutException STe) { return null; } + if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); + len[1] += rv; + } + if (len[1] < 4) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[1]+" of 4 bytes of header"); + return null; + } + + /* Parse the variable header length */ + int headerlen = 0; + if (null == header) { + headerlen = (int) Message.demarshallint(tbuf, 0, endian, 4); + if (0 != headerlen % 8) + headerlen += 8-(headerlen%8); + } else + headerlen = header.length-8; + + /* Read the variable header */ + if (null == header) { + header = new byte[headerlen+8]; + System.arraycopy(tbuf, 0, header, 0, 4); + len[2] = 0; + } + if (len[2] < headerlen) { + try { rv = in.read(header, 8+len[2], headerlen-len[2]); } + catch (SocketTimeoutException STe) { return null; } + if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); + len[2] += rv; + } + if (len[2] < headerlen) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[2]+" of "+headerlen+" bytes of header"); + return null; + } + + /* Read the body */ + int bodylen = 0; + if (null == body) bodylen = (int) Message.demarshallint(buf, 4, endian, 4); + if (null == body) { body=new byte[bodylen]; len[3] = 0; } + if (len[3] < body.length) { + try { rv = in.read(body, len[3], body.length-len[3]); } + catch (SocketTimeoutException STe) { return null; } + if (-1 == rv) throw new EOFException($_("Underlying transport returned EOF")); + len[3] += rv; + } + if (len[3] < body.length) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Only got "+len[3]+" of "+body.length+" bytes of body"); + return null; + } + + Message m; + switch (type) { + case Message.MessageType.METHOD_CALL: + m = new MethodCall(); + break; + case Message.MessageType.METHOD_RETURN: + m = new MethodReturn(); + break; + case Message.MessageType.SIGNAL: + m = new DBusSignal(); + break; + case Message.MessageType.ERROR: + m = new Error(); + break; + default: + throw new MessageTypeException(MessageFormat.format($_("Message type {0} unsupported"), new Object[] {type})); + } + if (Debug.debug) { + Debug.print(Debug.VERBOSE, Hexdump.format(buf)); + Debug.print(Debug.VERBOSE, Hexdump.format(tbuf)); + Debug.print(Debug.VERBOSE, Hexdump.format(header)); + Debug.print(Debug.VERBOSE, Hexdump.format(body)); + } + try { + m.populate(buf, header, body); + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + buf = null; + tbuf = null; + body = null; + header = null; + throw DBe; + } catch (RuntimeException Re) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, Re); + buf = null; + tbuf = null; + body = null; + header = null; + throw Re; + } + if (Debug.debug) { + Debug.print(Debug.INFO, "=> "+m); + } + buf = null; + tbuf = null; + body = null; + header = null; + return m; + } + public void close() throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Reader"); + in.close(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/MessageWriter.java b/app/src/main/java/org/freedesktop/dbus/MessageWriter.java new file mode 100644 index 00000000..e95bf78a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MessageWriter.java @@ -0,0 +1,68 @@ +/* + 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 java.io.BufferedOutputStream; +import java.io.OutputStream; +import java.io.IOException; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.unix.USOutputStream; +import cx.ath.matthew.utils.Hexdump; + +public class MessageWriter +{ + private OutputStream out; + private boolean isunix; + public MessageWriter(OutputStream out) + { + this.out = out; + this.isunix = false; + try { + if (out instanceof USOutputStream) + this.isunix = true; + } catch (Throwable t) { + } + if (!this.isunix) + this.out = new BufferedOutputStream(this.out); + } + public void writeMessage(Message m) throws IOException + { + if (Debug.debug) { + Debug.print(Debug.INFO, "<= "+m); + } + if (null == m) return; + if (null == m.getWireData()) { + if (Debug.debug) Debug.print(Debug.WARN, "Message "+m+" wire-data was null!"); + return; + } + if (isunix) { + if (Debug.debug) { + Debug.print(Debug.DEBUG, "Writing all "+m.getWireData().length+" buffers simultaneously to Unix Socket"); + for (byte[] buf: m.getWireData()) + Debug.print(Debug.VERBOSE, "("+buf+"):"+ (null==buf? "": Hexdump.format(buf))); + } + ((USOutputStream) out).write(m.getWireData()); + } else + for (byte[] buf: m.getWireData()) { + if (Debug.debug) + Debug.print(Debug.VERBOSE, "("+buf+"):"+ (null==buf? "": Hexdump.format(buf))); + if (null == buf) break; + out.write(buf); + } + out.flush(); + } + public void close() throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Closing Message Writer"); + out.close(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/MethodCall.java b/app/src/main/java/org/freedesktop/dbus/MethodCall.java new file mode 100644 index 00000000..41b688f7 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MethodCall.java @@ -0,0 +1,126 @@ +/* + 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.util.Vector; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.MessageFormatException; +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.utils.Hexdump; + +public class MethodCall extends Message +{ + MethodCall() { } + public MethodCall(String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException + { + this(null, dest, path, iface, member, flags, sig, args); + } + public MethodCall(String source, String dest, String path, String iface, String member, byte flags, String sig, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.METHOD_CALL, flags); + + if (null == member || null == path) + throw new MessageFormatException($_("Must specify destination, path and function name to MethodCalls.")); + headers.put(Message.HeaderField.PATH,path); + headers.put(Message.HeaderField.MEMBER,member); + + Vector hargs = new Vector(); + + hargs.add(new Object[] { Message.HeaderField.PATH, new Object[] { ArgumentType.OBJECT_PATH_STRING, path } }); + + if (null != source) { + headers.put(Message.HeaderField.SENDER,source); + hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); + } + + if (null != dest) { + headers.put(Message.HeaderField.DESTINATION,dest); + hargs.add(new Object[] { Message.HeaderField.DESTINATION, new Object[] { ArgumentType.STRING_STRING, dest } }); + } + + if (null != iface) { + hargs.add(new Object[] { Message.HeaderField.INTERFACE, new Object[] { ArgumentType.STRING_STRING, iface } }); + headers.put(Message.HeaderField.INTERFACE,iface); + } + + hargs.add(new Object[] { Message.HeaderField.MEMBER, new Object[] { ArgumentType. STRING_STRING, member } }); + + if (null != sig) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Appending arguments with signature: "+sig); + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } + + byte[] blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", serial, hargs.toArray()); + pad((byte)8); + + long c = bytecounter; + if (null != sig) append(sig, args); + if (Debug.debug) Debug.print(Debug.DEBUG, "Appended body, type: "+sig+" start: "+c+" end: "+bytecounter+" size: "+(bytecounter-c)); + marshallint(bytecounter-c, blen, 0, 4); + if (Debug.debug) Debug.print("marshalled size ("+blen+"): "+Hexdump.format(blen)); + } + private static long REPLY_WAIT_TIMEOUT = 20000; + /** + * Set the default timeout for method calls. + * Default is 20s. + * @param timeout New timeout in ms. + */ + public static void setDefaultTimeout(long timeout) + { + REPLY_WAIT_TIMEOUT = timeout; + } + Message reply = null; + public synchronized boolean hasReply() + { + return null != reply; + } + /** + * Block (if neccessary) for a reply. + * @return The reply to this MethodCall, or null if a timeout happens. + * @param timeout The length of time to block before timing out (ms). + */ + public synchronized Message getReply(long timeout) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on "+this); + if (null != reply) return reply; + try { + wait(timeout); + return reply; + } catch (InterruptedException Ie) { return reply; } + } + /** + * Block (if neccessary) for a reply. + * Default timeout is 20s, or can be configured with setDefaultTimeout() + * @return The reply to this MethodCall, or null if a timeout happens. + */ + public synchronized Message getReply() + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Blocking on "+this); + if (null != reply) return reply; + try { + wait(REPLY_WAIT_TIMEOUT); + return reply; + } catch (InterruptedException Ie) { return reply; } + } + protected synchronized void setReply(Message reply) + { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting reply to "+this+" to "+reply); + this.reply = reply; + notifyAll(); + } + +} diff --git a/app/src/main/java/org/freedesktop/dbus/MethodReturn.java b/app/src/main/java/org/freedesktop/dbus/MethodReturn.java new file mode 100644 index 00000000..b0f719ae --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MethodReturn.java @@ -0,0 +1,69 @@ +/* + 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 java.util.Vector; +import org.freedesktop.dbus.exceptions.DBusException; + +public class MethodReturn extends Message +{ + MethodReturn() { } + public MethodReturn(String dest, long replyserial, String sig, Object... args) throws DBusException + { + this(null, dest, replyserial, sig, args); + } + public MethodReturn(String source, String dest, long replyserial, String sig, Object... args) throws DBusException + { + super(Message.Endian.BIG, Message.MessageType.METHOD_RETURN, (byte) 0); + + headers.put(Message.HeaderField.REPLY_SERIAL,replyserial); + + Vector hargs = new Vector(); + hargs.add(new Object[] { Message.HeaderField.REPLY_SERIAL, new Object[] { ArgumentType.UINT32_STRING, replyserial } }); + + if (null != source) { + headers.put(Message.HeaderField.SENDER,source); + hargs.add(new Object[] { Message.HeaderField.SENDER, new Object[] { ArgumentType.STRING_STRING, source } }); + } + + if (null != dest) { + headers.put(Message.HeaderField.DESTINATION,dest); + hargs.add(new Object[] { Message.HeaderField.DESTINATION, new Object[] { ArgumentType.STRING_STRING, dest } }); + } + + if (null != sig) { + hargs.add(new Object[] { Message.HeaderField.SIGNATURE, new Object[] { ArgumentType.SIGNATURE_STRING, sig } }); + headers.put(Message.HeaderField.SIGNATURE,sig); + setArgs(args); + } + + byte[] blen = new byte[4]; + appendBytes(blen); + append("ua(yv)", serial, hargs.toArray()); + pad((byte)8); + + long c = bytecounter; + if (null != sig) append(sig, args); + marshallint(bytecounter-c, blen, 0, 4); + } + public MethodReturn(MethodCall mc, String sig, Object... args) throws DBusException + { + this(null, mc, sig, args); + } + public MethodReturn(String source, MethodCall mc, String sig, Object... args) throws DBusException + { + this(source, mc.getSource(), mc.getSerial(), sig, args); + this.call = mc; + } + MethodCall call; + public MethodCall getCall() { return call; } + protected void setCall(MethodCall call) { this.call = call; } +} diff --git a/app/src/main/java/org/freedesktop/dbus/MethodTuple.java b/app/src/main/java/org/freedesktop/dbus/MethodTuple.java new file mode 100644 index 00000000..dd2c4a98 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/MethodTuple.java @@ -0,0 +1,38 @@ +/* + 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 cx.ath.matthew.debug.Debug; + +class MethodTuple +{ + String name; + String sig; + public MethodTuple(String name, String sig) + { + this.name = name; + if (null != sig) + this.sig = sig; + else + this.sig = ""; + if (Debug.debug) Debug.print(Debug.VERBOSE, "new MethodTuple("+this.name+", "+this.sig+")"); + } + public boolean equals(Object o) + { + return o.getClass().equals(MethodTuple.class) + && ((MethodTuple) o).name.equals(this.name) + && ((MethodTuple) o).sig.equals(this.sig); + } + public int hashCode() + { + return name.hashCode()+sig.hashCode(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/ObjectPath.java b/app/src/main/java/org/freedesktop/dbus/ObjectPath.java new file mode 100644 index 00000000..409d26da --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/ObjectPath.java @@ -0,0 +1,23 @@ +/* + 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; + +class ObjectPath extends Path +{ + public String source; +// public DBusConnection conn; + public ObjectPath(String source, String path/*, DBusConnection conn*/) + { + super(path); + this.source = source; + // this.conn = conn; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/ObjectTree.java b/app/src/main/java/org/freedesktop/dbus/ObjectTree.java new file mode 100644 index 00000000..f7d05916 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/ObjectTree.java @@ -0,0 +1,163 @@ +/* + 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 cx.ath.matthew.debug.Debug; + +import java.util.regex.Pattern; + +/** + * Keeps track of the exported objects for introspection data */ +class ObjectTree +{ + class TreeNode + { + String name; + ExportedObject object; + String data; + TreeNode right; + TreeNode down; + public TreeNode(String name) + { + this.name = name; + } + public TreeNode(String name, ExportedObject object, String data) + { + this.name = name; + this.object = object; + this.data = data; + } + } + private TreeNode root; + public ObjectTree() + { + root = new TreeNode(""); + } + public static final Pattern slashpattern = Pattern.compile("/"); + + private TreeNode recursiveFind(TreeNode current, String path) + { + if ("/".equals(path)) return current; + String[] elements = path.split("/", 2); + // this is us or a parent node + if (path.startsWith(current.name)) { + // this is us + if (path.equals(current.name)) { + return current; + } + // recurse down + else { + if (current.down == null) + return null; + else return recursiveFind(current.down, elements[1]); + } + } + else if (current.right == null) { + return null; + } + else if (0 > current.right.name.compareTo(elements[0])) { + return null; + } + // recurse right + else { + return recursiveFind(current.right, path); + } + } + private TreeNode recursiveAdd(TreeNode current, String path, ExportedObject object, String data) + { + String[] elements = slashpattern.split(path, 2); + // this is us or a parent node + if (path.startsWith(current.name)) { + // this is us + if (1 == elements.length || "".equals(elements[1])) { + current.object = object; + current.data = data; + } + // recurse down + else { + if (current.down == null) { + String[] el = elements[1].split("/", 2); + current.down = new TreeNode(el[0]); + } + current.down = recursiveAdd(current.down, elements[1], object, data); + } + } + // need to create a new sub-tree on the end + else if (current.right == null) { + current.right = new TreeNode(elements[0]); + current.right = recursiveAdd(current.right, path, object, data); + } + // need to insert here + else if (0 > current.right.name.compareTo(elements[0])) { + TreeNode t = new TreeNode(elements[0]); + t.right = current.right; + current.right = t; + current.right = recursiveAdd(current.right, path, object, data); + } + // recurse right + else { + current.right = recursiveAdd(current.right, path, object, data); + } + return current; + } + public void add(String path, ExportedObject object, String data) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Adding "+path+" to object tree"); + root = recursiveAdd(root, path, object, data); + } + public void remove(String path) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "Removing "+path+" from object tree"); + TreeNode t = recursiveFind(root, path); + t.object = null; + t.data = null; + } + + public String Introspect(String path) + { + TreeNode t = recursiveFind(root, path); + if (null == t) return null; + StringBuilder sb = new StringBuilder(); + sb.append("\n"); + if (null != t.data) sb.append(t.data); + t = t.down; + while (null != t) { + sb.append("\n"); + t = t.right; + } + sb.append(""); + return sb.toString(); + } + + private String recursivePrint(TreeNode current) + { + String s = ""; + if (null != current) { + s += current.name; + if (null != current.object) + s += "*"; + if (null != current.down) + s += "/{"+recursivePrint(current.down)+"}"; + if (null != current.right) + s += ", "+recursivePrint(current.right); + } + return s; + } + + public String toString() + { + return recursivePrint(root); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Path.java b/app/src/main/java/org/freedesktop/dbus/Path.java new file mode 100644 index 00000000..baaa7a62 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Path.java @@ -0,0 +1,40 @@ +/* + 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; + +public class Path implements Comparable +{ + protected String path; + public Path(String path) + { + this.path = path; + } + public String getPath() + { + return path; + } + public String toString() + { + return path; + } + public boolean equals(Object other) + { + return (other instanceof Path) && path.equals(((Path) other).path); + } + public int hashCode() + { + return path.hashCode(); + } + public int compareTo(Path that) + { + return path.compareTo(that.path); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Position.java b/app/src/main/java/org/freedesktop/dbus/Position.java new file mode 100644 index 00000000..45e68d88 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Position.java @@ -0,0 +1,28 @@ +/* + 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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Position annotation, to annotate Struct fields + * to be sent over DBus. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Position +{ + /** The order of this field in the Struct. */ + int value(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java b/app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java new file mode 100644 index 00000000..e0302999 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/RemoteInvocationHandler.java @@ -0,0 +1,191 @@ +/* + 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.Constructor; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.lang.reflect.Type; +import java.text.MessageFormat; +import java.util.Arrays; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import cx.ath.matthew.debug.Debug; + +class RemoteInvocationHandler implements InvocationHandler +{ + public static final int CALL_TYPE_SYNC = 0; + public static final int CALL_TYPE_ASYNC = 1; + public static final int CALL_TYPE_CALLBACK = 2; + public static Object convertRV(String sig, Object[] rp, Method m, AbstractConnection conn) throws DBusException + { + Class c = m.getReturnType(); + + if (null == rp) { + if(null == c || Void.TYPE.equals(c)) return null; + else throw new DBusExecutionException($_("Wrong return type (got void, expected a value)")); + } else { + try { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Converting return parameters from "+Arrays.deepToString(rp)+" to type "+m.getGenericReturnType()); + rp = Marshalling.deSerializeParameters(rp, + new Type[] { m.getGenericReturnType() }, conn); + } + catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(MessageFormat.format($_("Wrong return type (failed to de-serialize correct types: {0} )"), new Object[] { e.getMessage() })); + } + } + + switch (rp.length) { + case 0: + if (null == c || Void.TYPE.equals(c)) + return null; + else throw new DBusExecutionException($_("Wrong return type (got void, expected a value)")); + case 1: + return rp[0]; + default: + + // check we are meant to return multiple values + if (!Tuple.class.isAssignableFrom(c)) + throw new DBusExecutionException($_("Wrong return type (not expecting Tuple)")); + + Constructor cons = c.getConstructors()[0]; + try { + return cons.newInstance(rp); + } catch (Exception e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusException(e.getMessage()); + } + } + } + @SuppressWarnings("unchecked") + public static Object executeRemoteMethod(RemoteObject ro, Method m, AbstractConnection conn, int syncmethod, CallbackHandler callback, Object... args) throws DBusExecutionException + { + Type[] ts = m.getGenericParameterTypes(); + String sig = null; + if (ts.length > 0) try { + sig = Marshalling.getDBusType(ts); + args = Marshalling.convertParameters(args, ts, conn); + } catch (DBusException DBe) { + throw new DBusExecutionException($_("Failed to construct D-Bus type: ")+DBe.getMessage()); + } + MethodCall call; + byte flags = 0; + if (!ro.autostart) flags |= Message.Flags.NO_AUTO_START; + if (syncmethod == CALL_TYPE_ASYNC) flags |= Message.Flags.ASYNC; + if (m.isAnnotationPresent(DBus.Method.NoReply.class)) flags |= Message.Flags.NO_REPLY_EXPECTED; + try { + String name; + if (m.isAnnotationPresent(DBusMemberName.class)) + name = m.getAnnotation(DBusMemberName.class).value(); + else + name = m.getName(); + if (null == ro.iface) + call = new MethodCall(ro.busname, ro.objectpath, null, name,flags, sig, args); + else { + if (null != ro.iface.getAnnotation(DBusInterfaceName.class)) { + call = new MethodCall(ro.busname, ro.objectpath, ro.iface.getAnnotation(DBusInterfaceName.class).value(), name, flags, sig, args); + } else + call = new MethodCall(ro.busname, ro.objectpath, AbstractConnection.dollar_pattern.matcher(ro.iface.getName()).replaceAll("."), name, flags, sig, args); + } + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new DBusExecutionException($_("Failed to construct outgoing method call: ")+DBe.getMessage()); + } + if (null == conn.outgoing) throw new NotConnected($_("Not Connected")); + + switch (syncmethod) { + case CALL_TYPE_ASYNC: + conn.queueOutgoing(call); + return new DBusAsyncReply(call, m, conn); + case CALL_TYPE_CALLBACK: + synchronized (conn.pendingCallbacks) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Queueing Callback "+callback+" for "+call); + conn.pendingCallbacks.put(call, callback); + conn.pendingCallbackReplys.put(call, new DBusAsyncReply(call, m, conn)); + } + conn.queueOutgoing(call); + return null; + case CALL_TYPE_SYNC: + conn.queueOutgoing(call); + break; + } + + // get reply + if (m.isAnnotationPresent(DBus.Method.NoReply.class)) return null; + + Message reply = call.getReply(); + if (null == reply) throw new DBus.Error.NoReply($_("No reply within specified time")); + + if (reply instanceof Error) + ((Error) reply).throwException(); + + try { + return convertRV(reply.getSig(), reply.getParameters(), m, conn); + } catch (DBusException e) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, e); + throw new DBusExecutionException(e.getMessage()); + } + } + + AbstractConnection conn; + RemoteObject remote; + public RemoteInvocationHandler(AbstractConnection conn, RemoteObject remote) + { + this.remote = remote; + this.conn = conn; + } + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + if (method.getName().equals("isRemote")) return true; + else if (method.getName().equals("clone")) return null; + else if (method.getName().equals("equals")) { + try { + if (1 == args.length) + return new Boolean(remote.equals(((RemoteInvocationHandler) Proxy.getInvocationHandler(args[0])).remote)); + } catch (IllegalArgumentException IAe) { + return Boolean.FALSE; + } + } + else if (method.getName().equals("finalize")) return null; + else if (method.getName().equals("getClass")) return DBusInterface.class; + else if (method.getName().equals("hashCode")) return remote.hashCode(); + else if (method.getName().equals("notify")) { + remote.notify(); + return null; + } else if (method.getName().equals("notifyAll")) { + remote.notifyAll(); + return null; + } else if (method.getName().equals("wait")) { + if (0 == args.length) remote.wait(); + else if (1 == args.length + && args[0] instanceof Long) remote.wait((Long) args[0]); + else if (2 == args.length + && args[0] instanceof Long + && args[1] instanceof Integer) + remote.wait((Long) args[0], (Integer) args[1]); + if (args.length <= 2) + return null; + } + else if (method.getName().equals("toString")) + return remote.toString(); + + return executeRemoteMethod(remote, method, conn, CALL_TYPE_SYNC, null, args); + } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/RemoteObject.java b/app/src/main/java/org/freedesktop/dbus/RemoteObject.java new file mode 100644 index 00000000..8a1f620b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/RemoteObject.java @@ -0,0 +1,56 @@ +/* + 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; + +class RemoteObject +{ + String busname; + String objectpath; + Class iface; + boolean autostart; + public RemoteObject(String busname, String objectpath, Class iface, boolean autostart) + { + this.busname = busname; + this.objectpath = objectpath; + this.iface = iface; + this.autostart = autostart; + } + public boolean equals(Object o) + { + if (!(o instanceof RemoteObject)) return false; + RemoteObject them = (RemoteObject) o; + + if (!them.objectpath.equals(this.objectpath)) return false; + + if (null == this.busname && null != them.busname) return false; + if (null != this.busname && null == them.busname) return false; + if (null != them.busname && !them.busname.equals(this.busname)) return false; + + if (null == this.iface && null != them.iface) return false; + if (null != this.iface && null == them.iface) return false; + if (null != them.iface && !them.iface.equals(this.iface)) return false; + + return true; + } + public int hashCode() + { + return (null == busname ? 0 : busname.hashCode()) + objectpath.hashCode() + + (null == iface ? 0 : iface.hashCode()); + } + public boolean autoStarting() { return autostart; } + public String getBusName() { return busname; } + public String getObjectPath() { return objectpath; } + public Class getInterface() { return iface; } + public String toString() + { + return busname+":"+objectpath+":"+iface; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/SignalTuple.java b/app/src/main/java/org/freedesktop/dbus/SignalTuple.java new file mode 100644 index 00000000..94cadeed --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/SignalTuple.java @@ -0,0 +1,51 @@ +/* + 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; +class SignalTuple +{ + String type; + String name; + String object; + String source; + public SignalTuple(String type, String name, String object, String source) + { + this.type = type; + this.name = name; + this.object = object; + this.source = source; + } + public boolean equals(Object o) + { + if (!(o instanceof SignalTuple)) return false; + SignalTuple other = (SignalTuple) o; + if (null == this.type && null != other.type) return false; + if (null != this.type && !this.type.equals(other.type)) return false; + if (null == this.name && null != other.name) return false; + if (null != this.name && !this.name.equals(other.name)) return false; + if (null == this.object && null != other.object) return false; + if (null != this.object && !this.object.equals(other.object)) return false; + if (null == this.source && null != other.source) return false; + if (null != this.source && !this.source.equals(other.source)) return false; + return true; + } + public int hashCode() + { + return (null == type ? 0 : type.hashCode()) + + (null == name ? 0 : name.hashCode()) + + (null == source ? 0 : source.hashCode()) + + (null == object ? 0 : object.hashCode()); + } + public String toString() + { + return "SignalTuple("+type+","+name+","+object+","+source+")"; + } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/StrongReference.java b/app/src/main/java/org/freedesktop/dbus/StrongReference.java new file mode 100644 index 00000000..c4d142dc --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/StrongReference.java @@ -0,0 +1,43 @@ +/* + 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 java.lang.ref.WeakReference; + +/** + * An alternative to a WeakReference when you don't want + * that behaviour. + */ +public class StrongReference extends WeakReference +{ + T referant; + public StrongReference(T referant) + { + super(referant); + this.referant = referant; + } + public void clear() + { + referant = null; + } + public boolean enqueue() + { + return false; + } + public T get() + { + return referant; + } + public boolean isEnqueued() + { + return false; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/Struct.java b/app/src/main/java/org/freedesktop/dbus/Struct.java new file mode 100644 index 00000000..7d37ed07 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Struct.java @@ -0,0 +1,23 @@ +/* + 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; + +/** + * This class should be extended to create Structs. + * Any such class may be sent over DBus to a method which takes a Struct. + * All fields in the Struct which you wish to be serialized and sent to the + * remote method should be annotated with the org.freedesktop.dbus.Position + * annotation, in the order they should appear in to Struct to DBus. + */ +public abstract class Struct extends Container +{ + public Struct() {} +} diff --git a/app/src/main/java/org/freedesktop/dbus/Transport.java b/app/src/main/java/org/freedesktop/dbus/Transport.java new file mode 100644 index 00000000..a7689a7d --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Transport.java @@ -0,0 +1,827 @@ +/* + 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.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.text.ParseException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Random; +import java.util.Vector; +import cx.ath.matthew.unix.UnixSocket; +import cx.ath.matthew.unix.UnixServerSocket; +import cx.ath.matthew.unix.UnixSocketAddress; +import cx.ath.matthew.utils.Hexdump; +import cx.ath.matthew.debug.Debug; + +import android.icu.text.Collator; + +public class Transport +{ + public static class SASL + { + public static class Command + { + private int command; + private int mechs; + private String data; + private String response; + public Command() + { + } + public Command(String s) throws IOException + { + String[] ss = s.split(" "); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Creating command from: "+Arrays.toString(ss)); + if (0 == col.compare(ss[0], "OK")) { + command = COMMAND_OK; + data = ss[1]; + } else if (0 == col.compare(ss[0], "AUTH")) { + command = COMMAND_AUTH; + if (ss.length > 1) { + if (0 == col.compare(ss[1], "EXTERNAL")) + mechs = AUTH_EXTERNAL; + else if (0 == col.compare(ss[1], "DBUS_COOKIE_SHA1")) + mechs = AUTH_SHA; + else if (0 == col.compare(ss[1], "ANONYMOUS")) + mechs = AUTH_ANON; + } + if (ss.length > 2) + data = ss[2]; + } else if (0 == col.compare(ss[0], "DATA")) { + command = COMMAND_DATA; + data = ss[1]; + } else if (0 == col.compare(ss[0], "REJECTED")) { + command = COMMAND_REJECTED; + for (int i = 1; i < ss.length; i++) + if (0 == col.compare(ss[i], "EXTERNAL")) + mechs |= AUTH_EXTERNAL; + else if (0 == col.compare(ss[i], "DBUS_COOKIE_SHA1")) + mechs |= AUTH_SHA; + else if (0 == col.compare(ss[i], "ANONYMOUS")) + mechs |= AUTH_ANON; + } else if (0 == col.compare(ss[0], "BEGIN")) { + command = COMMAND_BEGIN; + } else if (0 == col.compare(ss[0], "CANCEL")) { + command = COMMAND_CANCEL; + } else if (0 == col.compare(ss[0], "ERROR")) { + command = COMMAND_ERROR; + data = ss[1]; + } else { + throw new IOException($_("Invalid Command ")+ss[0]); + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "Created command: "+this); + } + public int getCommand() { return command; } + public int getMechs() { return mechs; } + public String getData() { return data; } + public String getResponse() { return response; } + public void setResponse(String s) { response = s; } + public String toString() + { + return "Command("+command+", "+mechs+", "+data+", "+null+")"; + } + } + private static Collator col = Collator.getInstance(); + static { + col.setStrength(Collator.PRIMARY); + } + public static final int LOCK_TIMEOUT = 1000; + public static final int NEW_KEY_TIMEOUT_SECONDS = 60 * 5; + public static final int EXPIRE_KEYS_TIMEOUT_SECONDS = NEW_KEY_TIMEOUT_SECONDS + (60 * 2); + public static final int MAX_TIME_TRAVEL_SECONDS = 60 * 5; + public static final int COOKIE_TIMEOUT = 240; + public static final String COOKIE_CONTEXT = "org_freedesktop_java"; + + private String findCookie(String context, String ID) throws IOException + { + String homedir = System.getProperty("user.home"); + File f = new File(homedir+"/.dbus-keyrings/"+context); + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f))); + String s = null; + String cookie = null; + long now = System.currentTimeMillis()/1000; + while (null != (s = r.readLine())) { + String[] line = s.split(" "); + long timestamp = Long.parseLong(line[1]); + if (line[0].equals(ID) && (! (timestamp < 0 || + (now + MAX_TIME_TRAVEL_SECONDS) < timestamp || + (now - EXPIRE_KEYS_TIMEOUT_SECONDS) > timestamp))) { + cookie = line[2]; + break; + } + } + r.close(); + return cookie; + } + private void addCookie(String context, String ID, long timestamp, String cookie) throws IOException + { + String homedir = System.getProperty("user.home"); + File keydir = new File(homedir+"/.dbus-keyrings/"); + File cookiefile = new File(homedir+"/.dbus-keyrings/"+context); + File lock = new File(homedir+"/.dbus-keyrings/"+context+".lock"); + File temp = new File(homedir+"/.dbus-keyrings/"+context+".temp"); + + // ensure directory exists + if (!keydir.exists()) keydir.mkdirs(); + + // acquire lock + long start = System.currentTimeMillis(); + while (!lock.createNewFile() && LOCK_TIMEOUT > (System.currentTimeMillis()-start)); + + // read old file + Vector lines = new Vector(); + if (cookiefile.exists()) { + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(cookiefile))); + String s = null; + while (null != (s = r.readLine())) { + String[] line = s.split(" "); + long time = Long.parseLong(line[1]); + // expire stale cookies + if ((timestamp - time) < COOKIE_TIMEOUT) + lines.add(s); + } + r.close(); + } + + // add cookie + lines.add(ID+" "+timestamp+" "+cookie); + + // write temp file + PrintWriter w = new PrintWriter(new FileOutputStream(temp)); + for (String l: lines) + w.println(l); + w.close(); + + // atomically move to old file + if (!temp.renameTo(cookiefile)) { + cookiefile.delete(); + temp.renameTo(cookiefile); + } + + // remove lock + lock.delete(); + } + /** + * Takes the string, encodes it as hex and then turns it into a string again. + * No, I don't know why either. + */ + private String stupidlyEncode(String data) + { + return Hexdump.toHex(data.getBytes()).replaceAll(" ",""); + } + private String stupidlyEncode(byte[] data) + { + return Hexdump.toHex(data).replaceAll(" ",""); + } + private byte getNibble(char c) + { + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return (byte) (c-'0'); + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + return (byte) (c-'A'+10); + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + return (byte) (c-'a'+10); + default: + return 0; + } + } + private String stupidlyDecode(String data) + { + char[] cs = new char[data.length()]; + char[] res = new char[cs.length/2]; + data.getChars(0, data.length(), cs, 0); + for (int i = 0, j = 0; j < res.length; i += 2, j++) { + int b = 0; + b |= getNibble(cs[i])<<4; + b |= getNibble(cs[i+1]); + res[j] = (char) b; + } + return new String(res); + } + + public static final int MODE_SERVER=1; + public static final int MODE_CLIENT=2; + + public static final int AUTH_NONE=0; + public static final int AUTH_EXTERNAL=1; + public static final int AUTH_SHA=2; + public static final int AUTH_ANON=4; + + public static final int COMMAND_AUTH=1; + public static final int COMMAND_DATA=2; + public static final int COMMAND_REJECTED=3; + public static final int COMMAND_OK=4; + public static final int COMMAND_BEGIN=5; + public static final int COMMAND_CANCEL=6; + public static final int COMMAND_ERROR=7; + + public static final int INITIAL_STATE=0; + public static final int WAIT_DATA=1; + public static final int WAIT_OK=2; + public static final int WAIT_REJECT=3; + public static final int WAIT_AUTH=4; + public static final int WAIT_BEGIN=5; + public static final int AUTHENTICATED=6; + public static final int FAILED=7; + + public static final int OK=1; + public static final int CONTINUE=2; + public static final int ERROR=3; + public static final int REJECT=4; + + public Command receive(InputStream s) throws IOException + { + StringBuffer sb = new StringBuffer(); + top: while (true) { + int c = s.read(); + switch (c) { + case -1: + throw new IOException("Stream unexpectedly short (broken pipe)"); + case 0: + case '\r': + continue; + case '\n': + break top; + default: + sb.append((char) c); + } + } + if (Debug.debug) Debug.print(Debug.VERBOSE, "received: "+sb); + try { + return new Command(sb.toString()); + } catch (Exception e) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); + return new Command(); + } + } + public void send(OutputStream out, int command, String... data) throws IOException + { + StringBuffer sb = new StringBuffer(); + switch (command) { + case COMMAND_AUTH: + sb.append("AUTH"); + break; + case COMMAND_DATA: + sb.append("DATA"); + break; + case COMMAND_REJECTED: + sb.append("REJECTED"); + break; + case COMMAND_OK: + sb.append("OK"); + break; + case COMMAND_BEGIN: + sb.append("BEGIN"); + break; + case COMMAND_CANCEL: + sb.append("CANCEL"); + break; + case COMMAND_ERROR: + sb.append("ERROR"); + break; + default: + return; + } + for (String s: data) { + sb.append(' '); + sb.append(s); + } + sb.append('\r'); + sb.append('\n'); + if (Debug.debug) Debug.print(Debug.VERBOSE, "sending: "+sb); + out.write(sb.toString().getBytes()); + } + public int do_challenge(int auth, Command c) throws IOException + { + switch (auth) { + case AUTH_SHA: + String[] reply=stupidlyDecode(c.getData()).split(" "); + if (Debug.debug) Debug.print(Debug.VERBOSE, Arrays.toString(reply)); + if (3 != reply.length) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Reply is not length 3"); + return ERROR; + } + String context = reply[0]; + String ID = reply[1]; + String serverchallenge = reply[2]; + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException NSAe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe); + return ERROR; + } + byte[] buf = new byte[8]; + Message.marshallintBig(System.currentTimeMillis(), buf, 0, 8); + String clientchallenge = stupidlyEncode(md.digest(buf)); + md.reset(); + long start = System.currentTimeMillis(); + String cookie = null; + while (null == cookie && (System.currentTimeMillis()-start) < LOCK_TIMEOUT) + cookie = findCookie(context, ID); + if (null == cookie) { + if (Debug.debug) Debug.print(Debug.DEBUG, "Did not find a cookie in context "+context+" with ID "+ID); + return ERROR; + } + String response = serverchallenge+":"+clientchallenge+":"+cookie; + buf = md.digest(response.getBytes()); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Response: "+response+" hash: "+Hexdump.format(buf)); + response = stupidlyEncode(buf); + c.setResponse(stupidlyEncode(clientchallenge+" "+response)); + return OK; + default: + if (Debug.debug) Debug.print(Debug.DEBUG, "Not DBUS_COOKIE_SHA1 authtype."); + return ERROR; + } + } + public String challenge = ""; + public String cookie = ""; + public int do_response(int auth, String Uid, String kernelUid, Command c) + { + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException NSAe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, NSAe); + return ERROR; + } + switch (auth) { + case AUTH_NONE: + switch (c.getMechs()) { + case AUTH_ANON: + return OK; + case AUTH_EXTERNAL: + if (0 == col.compare(Uid, c.getData()) && + (null == kernelUid || 0 == col.compare(Uid, kernelUid))) + return OK; + else + return ERROR; + case AUTH_SHA: + String context = COOKIE_CONTEXT; + long id = System.currentTimeMillis(); + byte[] buf = new byte[8]; + Message.marshallintBig(id, buf, 0, 8); + challenge = stupidlyEncode(md.digest(buf)); + Random r = new Random(); + r.nextBytes(buf); + cookie = stupidlyEncode(md.digest(buf)); + try { addCookie(context, ""+id, id/1000, cookie); + } catch (IOException IOe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "Sending challenge: "+context+' '+id+' '+challenge); + c.setResponse(stupidlyEncode(context+' '+id+' '+challenge)); + return CONTINUE; + default: + return ERROR; + } + case AUTH_SHA: + String[] response = stupidlyDecode(c.getData()).split(" "); + if (response.length < 2) return ERROR; + String cchal = response[0]; + String hash = response[1]; + String prehash = challenge+":"+cchal+":"+cookie; + byte[] buf = md.digest(prehash.getBytes()); + String posthash = stupidlyEncode(buf); + if (Debug.debug) Debug.print(Debug.DEBUG, "Authenticating Hash; data="+prehash+" remote hash="+hash+" local hash="+posthash); + if (0 == col.compare(posthash, hash)) + return OK; + else + return ERROR; + default: + return ERROR; + } + } + public String[] getTypes(int types) + { + switch (types) { + case AUTH_EXTERNAL: + return new String[] { "EXTERNAL" }; + case AUTH_SHA: + return new String[] { "DBUS_COOKIE_SHA1" }; + case AUTH_ANON: + return new String[] { "ANONYMOUS" }; + case AUTH_SHA+AUTH_EXTERNAL: + return new String[] { "EXTERNAL", "DBUS_COOKIE_SHA1" }; + case AUTH_SHA+AUTH_ANON: + return new String[] { "ANONYMOUS", "DBUS_COOKIE_SHA1" }; + case AUTH_EXTERNAL+AUTH_ANON: + return new String[] { "ANONYMOUS", "EXTERNAL" }; + case AUTH_EXTERNAL+AUTH_ANON+AUTH_SHA: + return new String[] { "ANONYMOUS", "EXTERNAL", "DBUS_COOKIE_SHA1" }; + default: + return new String[] { }; + } + } + /** + * performs SASL auth on the given streams. + * Mode selects whether to run as a SASL server or client. + * Types is a bitmask of the available auth types. + * Returns true if the auth was successful and false if it failed. + */ + @SuppressWarnings("unchecked") + public boolean auth(int mode, int types, String guid, OutputStream out, InputStream in, UnixSocket us) throws IOException + { + String username = System.getProperty("user.name"); + String Uid = null; + String kernelUid = null; + try { + Class c = Class.forName("com.sun.security.auth.module.UnixSystem"); + Method m = c.getMethod("getUid"); + Object o = c.newInstance(); + long uid = (Long) m.invoke(o); + Uid = stupidlyEncode(""+uid); + } catch (Exception e) { + Uid = stupidlyEncode(username); + } + Command c; + int failed = 0; + int current = 0; + int state = INITIAL_STATE; + + while (state != AUTHENTICATED && state != FAILED) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "AUTH state: "+state); + switch (mode) { + case MODE_CLIENT: + switch (state) { + case INITIAL_STATE: + if (null == us) + out.write(new byte[] { 0 }); + else + us.sendCredentialByte((byte) 0); + send(out, COMMAND_AUTH); + state = WAIT_DATA; + break; + case WAIT_DATA: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_DATA: + switch (do_challenge(current, c)) { + case CONTINUE: + send(out, COMMAND_DATA, c.getResponse()); + break; + case OK: + send(out, COMMAND_DATA, c.getResponse()); + state = WAIT_OK; + break; + case ERROR: + send(out, COMMAND_ERROR, c.getResponse()); + break; + } + break; + case COMMAND_REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); +// if (0 != (available & AUTH_EXTERNAL)){ +// send(out, COMMAND_AUTH, "EXTERNAL", Uid); +// current = AUTH_EXTERNAL; +// } else if (0 != (available & AUTH_SHA)) { +// send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); +// current = AUTH_SHA; +// } else + if (0 != (available & AUTH_ANON)) { + send(out, COMMAND_AUTH, "ANONYMOUS"); + current = AUTH_ANON; + } + else state = FAILED; + break; + case COMMAND_ERROR: + send(out, COMMAND_CANCEL); + state = WAIT_REJECT; + break; + case COMMAND_OK: + send(out, COMMAND_BEGIN); + state = AUTHENTICATED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_OK: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_OK: + send(out, COMMAND_BEGIN); + state = AUTHENTICATED; + break; + case COMMAND_ERROR: + case COMMAND_DATA: + send(out, COMMAND_CANCEL); + state = WAIT_REJECT; + break; + case COMMAND_REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); + state = WAIT_DATA; + if (0 != (available & AUTH_EXTERNAL)) { + send(out, COMMAND_AUTH, "EXTERNAL", Uid); + current = AUTH_EXTERNAL; + } else if (0 != (available & AUTH_SHA)) { + send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); + current = AUTH_SHA; + } else if (0 != (available & AUTH_ANON)) { + send(out, COMMAND_AUTH, "ANONYMOUS"); + current = AUTH_ANON; + } + else state = FAILED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_REJECT: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_REJECTED: + failed |= current; + int available = c.getMechs() & (~failed); + if (0 != (available & AUTH_EXTERNAL)) { + send(out, COMMAND_AUTH, "EXTERNAL", Uid); + current = AUTH_EXTERNAL; + } else if (0 != (available & AUTH_SHA)) { + send(out, COMMAND_AUTH, "DBUS_COOKIE_SHA1", Uid); + current = AUTH_SHA; + } else if (0 != (available & AUTH_ANON)) { + send(out, COMMAND_AUTH, "ANONYMOUS"); + current = AUTH_ANON; + } + else state = FAILED; + break; + default: + state = FAILED; + break; + } + break; + default: + state = FAILED; + } + break; + case MODE_SERVER: + switch (state) { + case INITIAL_STATE: + byte[] buf = new byte[1]; + if (null == us) { + in.read(buf); + } else { + buf[0] = us.recvCredentialByte(); + int kuid = us.getPeerUID(); + if (kuid >= 0) + kernelUid = stupidlyEncode(""+kuid); + } + if (0 != buf[0]) state = FAILED; + else state = WAIT_AUTH; + break; + case WAIT_AUTH: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_AUTH: + if (null == c.getData()) { + send(out, COMMAND_REJECTED, getTypes(types)); + } else { + switch (do_response(current, Uid, kernelUid, c)) { + case CONTINUE: + send(out, COMMAND_DATA, c.getResponse()); + current = c.getMechs(); + state = WAIT_DATA; + break; + case OK: + send(out, COMMAND_OK, guid); + state = WAIT_BEGIN; + current = 0; + break; + case REJECT: + send(out, COMMAND_REJECTED, getTypes(types)); + current = 0; + break; + } + } + break; + case COMMAND_ERROR: + send(out, COMMAND_REJECTED, getTypes(types)); + break; + case COMMAND_BEGIN: + state = FAILED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_DATA: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_DATA: + switch (do_response(current, Uid, kernelUid, c)) { + case CONTINUE: + send(out, COMMAND_DATA, c.getResponse()); + state = WAIT_DATA; + break; + case OK: + send(out, COMMAND_OK, guid); + state = WAIT_BEGIN; + current = 0; + break; + case REJECT: + send(out, COMMAND_REJECTED, getTypes(types)); + current = 0; + break; + } + break; + case COMMAND_ERROR: + case COMMAND_CANCEL: + send(out, COMMAND_REJECTED, getTypes(types)); + state = WAIT_AUTH; + break; + case COMMAND_BEGIN: + state = FAILED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + case WAIT_BEGIN: + c = receive(in); + switch (c.getCommand()) { + case COMMAND_ERROR: + case COMMAND_CANCEL: + send(out, COMMAND_REJECTED, getTypes(types)); + state = WAIT_AUTH; + break; + case COMMAND_BEGIN: + state = AUTHENTICATED; + break; + default: + send(out, COMMAND_ERROR, "Got invalid command"); + break; + } + break; + default: + state = FAILED; + } + break; + default: + return false; + } + } + + return state == AUTHENTICATED; + } + } + public MessageReader min; + public MessageWriter mout; + public Transport() {} + public static String genGUID() + { + Random r = new Random(); + byte[] buf = new byte[16]; + r.nextBytes(buf); + String guid = Hexdump.toHex(buf); + return guid.replaceAll(" ", ""); + } + public Transport(BusAddress address) throws IOException + { + connect(address); + } + public Transport(String address) throws IOException, ParseException + { + connect(new BusAddress(address)); + } + public Transport(String address, int timeout) throws IOException, ParseException + { + connect(new BusAddress(address), timeout); + } + public void connect(String address) throws IOException, ParseException + { + connect(new BusAddress(address), 0); + } + public void connect(String address, int timeout) throws IOException, ParseException + { + connect(new BusAddress(address), timeout); + } + public void connect(BusAddress address) throws IOException + { + connect(address, 0); + } + public void connect(BusAddress address, int timeout) throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Connecting to "+address); + OutputStream out = null; + InputStream in = null; + UnixSocket us = null; + Socket s = null; + int mode = 0; + int types = 0; + if ("unix".equals(address.getType())) { + types = SASL.AUTH_EXTERNAL; + if (null != address.getParameter("listen")) { + mode = SASL.MODE_SERVER; + UnixServerSocket uss = new UnixServerSocket(); + if (null != address.getParameter("abstract")) + uss.bind(new UnixSocketAddress(address.getParameter("abstract"), true)); + else if (null != address.getParameter("path")) + uss.bind(new UnixSocketAddress(address.getParameter("path"), false)); + us = uss.accept(); + } else { + mode = SASL.MODE_CLIENT; + us = new UnixSocket(); + if (null != address.getParameter("abstract")) + us.connect(new UnixSocketAddress(address.getParameter("abstract"), true)); + else if (null != address.getParameter("path")) + us.connect(new UnixSocketAddress(address.getParameter("path"), false)); + } + us.setPassCred(true); + in = us.getInputStream(); + out = us.getOutputStream(); + } else if ("tcp".equals(address.getType())) { + types = SASL.AUTH_SHA; + if (null != address.getParameter("listen")) { + mode = SASL.MODE_SERVER; + ServerSocket ss = new ServerSocket(); + ss.bind(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port")))); + s = ss.accept(); + } else { + types = SASL.AUTH_ANON; + mode = SASL.MODE_CLIENT; + s = new Socket(); + s.connect(new InetSocketAddress(address.getParameter("host"), Integer.parseInt(address.getParameter("port")))); + } + in = s.getInputStream(); + out = s.getOutputStream(); + } else { + throw new IOException($_("unknown address type ")+address.getType()); + } + + if (!(new SASL()).auth(mode, types, address.getParameter("guid"), out, in, us)) { + out.close(); + throw new IOException($_("Failed to auth")); + } + if (null != us) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to "+timeout+" on Socket"); + if (timeout == 1) + us.setBlocking(false); + else + us.setSoTimeout(timeout); + } + if (null != s) { + if (Debug.debug) Debug.print(Debug.VERBOSE, "Setting timeout to "+timeout+" on Socket"); + s.setSoTimeout(timeout); + } + mout = new MessageWriter(out); + min = new MessageReader(in); + } + public void disconnect() throws IOException + { + if (Debug.debug) Debug.print(Debug.INFO, "Disconnecting Transport"); + min.close(); + mout.close(); + } +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/Tuple.java b/app/src/main/java/org/freedesktop/dbus/Tuple.java new file mode 100644 index 00000000..becfc60c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Tuple.java @@ -0,0 +1,24 @@ +/* + 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; + +/** + * This class should be extended to create Tuples. + * Any such class may be used as the return type for a method + * which returns multiple values. + * All fields in the Tuple which you wish to be serialized and sent to the + * remote method should be annotated with the org.freedesktop.dbus.Position + * annotation, in the order they should appear to DBus. + */ +public abstract class Tuple extends Container +{ + public Tuple() {} +} diff --git a/app/src/main/java/org/freedesktop/dbus/TypeSignature.java b/app/src/main/java/org/freedesktop/dbus/TypeSignature.java new file mode 100644 index 00000000..c297821d --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/TypeSignature.java @@ -0,0 +1,37 @@ +/* + 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 java.lang.reflect.Type; +import org.freedesktop.dbus.exceptions.DBusException; + +public class TypeSignature +{ + String sig; + public TypeSignature(String sig) + { + this.sig = sig; + } + public TypeSignature(Type[] types) throws DBusException + { + StringBuffer sb = new StringBuffer(); + for (Type t: types) { + String[] ts = Marshalling.getDBusType(t); + for (String s: ts) + sb.append(s); + } + this.sig = sb.toString(); + } + public String getSig() + { + return sig; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/UInt16.java b/app/src/main/java/org/freedesktop/dbus/UInt16.java new file mode 100644 index 00000000..a47e2dcc --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/UInt16.java @@ -0,0 +1,79 @@ +/* + 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.text.MessageFormat; + +/** + * Class to represent 16-bit unsigned integers. + */ +@SuppressWarnings("serial") +public class UInt16 extends Number implements Comparable +{ + /** Maximum possible value. */ + public static final int MAX_VALUE = 65535; + /** Minimum possible value. */ + public static final int MIN_VALUE = 0; + private int value; + /** Create a UInt16 from an int. + * @param value Must be within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE + */ + public UInt16(int value) + { + if (value < MIN_VALUE || value > MAX_VALUE) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_VALUE})); + this.value = value; + } + /** Create a UInt16 from a String. + * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE + */ + public UInt16(String value) + { + this(Integer.parseInt(value)); + } + /** The value of this as a byte. */ + public byte byteValue() { return (byte) value; } + /** The value of this as a double. */ + public double doubleValue() { return (double) value; } + /** The value of this as a float. */ + public float floatValue() { return (float) value; } + /** The value of this as a int. */ + public int intValue() { return /*(int)*/ value; } + /** The value of this as a long. */ + public long longValue() { return (long) value; } + /** The value of this as a short. */ + public short shortValue(){ return (short) value; } + /** Test two UInt16s for equality. */ + public boolean equals(Object o) + { + return o instanceof UInt16 && ((UInt16) o).value == this.value; + } + public int hashCode() + { + return /*(int)*/ value; + } + /** Compare two UInt16s. + * @return 0 if equal, -ve or +ve if they are different. + */ + public int compareTo(UInt16 other) + { + return /*(int)*/ (this.value - other.value); + } + /** The value of this as a string. */ + public String toString() + { + return ""+value; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/UInt32.java b/app/src/main/java/org/freedesktop/dbus/UInt32.java new file mode 100644 index 00000000..f2afc3dd --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/UInt32.java @@ -0,0 +1,83 @@ +/* + 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.text.MessageFormat; + +/** + * Class to represent unsigned 32-bit numbers. + */ +@SuppressWarnings("serial") +public class UInt32 extends Number implements Comparable +{ + /** Maximum allowed value */ + public static final long MAX_VALUE = 4294967295L; + /** Minimum allowed value */ + public static final long MIN_VALUE = 0; + private long value; + /** Create a UInt32 from a long. + * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE + */ + public UInt32(long value) + { + if (value < MIN_VALUE || value > MAX_VALUE) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_VALUE})); + this.value = value; + } + /** Create a UInt32 from a String. + * @param value Must parse to a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_VALUE + */ + public UInt32(String value) + { + this(Long.parseLong(value)); + } + + public static UInt32 fromUnsignedInt(int value) { + return new UInt32(Long.parseUnsignedLong(Integer.toUnsignedString(value, 16), 16)); + } + /** The value of this as a byte. */ + public byte byteValue() { return (byte) value; } + /** The value of this as a double. */ + public double doubleValue() { return (double) value; } + /** The value of this as a float. */ + public float floatValue() { return (float) value; } + /** The value of this as a int. */ + public int intValue() { return (int) value; } + /** The value of this as a long. */ + public long longValue() { return /*(long)*/ value; } + /** The value of this as a short. */ + public short shortValue(){ return (short) value; } + /** Test two UInt32s for equality. */ + public boolean equals(Object o) + { + return o instanceof UInt32 && ((UInt32) o).value == this.value; + } + public int hashCode() + { + return (int) value; + } + /** Compare two UInt32s. + * @return 0 if equal, -ve or +ve if they are different. + */ + public int compareTo(UInt32 other) + { + return (int) (this.value - other.value); + } + /** The value of this as a string */ + public String toString() + { + return ""+value; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/UInt64.java b/app/src/main/java/org/freedesktop/dbus/UInt64.java new file mode 100644 index 00000000..1fe745f9 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/UInt64.java @@ -0,0 +1,151 @@ +/* + 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.math.BigInteger; + +import java.text.MessageFormat; + +/** + * Class to represent unsigned 64-bit numbers. + * Warning: Any functions which take or return a long + * are restricted to the range of a signed 64bit number. + * Use the BigInteger methods if you wish access to the full + * range. + */ +@SuppressWarnings("serial") +public class UInt64 extends Number implements Comparable +{ + /** Maximum allowed value (when accessed as a long) */ + public static final long MAX_LONG_VALUE = Long.MAX_VALUE; + /** Maximum allowed value (when accessed as a BigInteger) */ + public static final BigInteger MAX_BIG_VALUE = new BigInteger("18446744073709551615"); + /** Minimum allowed value */ + public static final long MIN_VALUE = 0; + private BigInteger value; + private long top; + private long bottom; + /** Create a UInt64 from a long. + * @param value Must be a valid integer within MIN_VALUE–MAX_VALUE + * @throws NumberFormatException if value is not between MIN_VALUE and MAX_VALUE + */ + public UInt64(long value) + { + if (value < MIN_VALUE || value > MAX_LONG_VALUE) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_LONG_VALUE})); + this.value = new BigInteger(""+value); + this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); + this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); + } + /** + * Create a UInt64 from two longs. + * @param top Most significant 4 bytes. + * @param bottom Least significant 4 bytes. + */ + public UInt64(long top, long bottom) + { + BigInteger a = new BigInteger(""+top); + a = a.shiftLeft(32); + a = a.add(new BigInteger(""+bottom)); + if (0 > a.compareTo(BigInteger.ZERO)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { a, MIN_VALUE, MAX_BIG_VALUE})); + if (0 < a.compareTo(MAX_BIG_VALUE)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { a, MIN_VALUE, MAX_BIG_VALUE})); + this.value = a; + this.top = top; + this.bottom = bottom; + } + /** Create a UInt64 from a BigInteger + * @param value Must be a valid BigInteger between MIN_VALUE–MAX_BIG_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE + */ + public UInt64(BigInteger value) + { + if (null == value) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + if (0 > value.compareTo(BigInteger.ZERO)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + if (0 < value.compareTo(MAX_BIG_VALUE)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + this.value = value; + this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); + this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); + } + /** Create a UInt64 from a String. + * @param value Must parse to a valid integer within MIN_VALUE–MAX_BIG_VALUE + * @throws NumberFormatException if value is not an integer between MIN_VALUE and MAX_BIG_VALUE + */ + public UInt64(String value) + { + if (null == value) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + BigInteger a = new BigInteger(value); + if (0 > a.compareTo(BigInteger.ZERO)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + if (0 < a.compareTo(MAX_BIG_VALUE)) + throw new NumberFormatException(MessageFormat.format($_("{0} is not between {1} and {2}."), new Object[] { value, MIN_VALUE, MAX_BIG_VALUE})); + this.value = a; + this.top = this.value.shiftRight(32).and(new BigInteger("4294967295")).longValue(); + this.bottom = this.value.and(new BigInteger("4294967295")).longValue(); + } + /** The value of this as a BigInteger. */ + public BigInteger value() { return value; } + /** The value of this as a byte. */ + public byte byteValue() { return value.byteValue(); } + /** The value of this as a double. */ + public double doubleValue() { return value.doubleValue(); } + /** The value of this as a float. */ + public float floatValue() { return value.floatValue(); } + /** The value of this as a int. */ + public int intValue() { return value.intValue(); } + /** The value of this as a long. */ + public long longValue() { return value.longValue(); } + /** The value of this as a short. */ + public short shortValue(){ return value.shortValue(); } + /** Test two UInt64s for equality. */ + public boolean equals(Object o) + { + return o instanceof UInt64 && this.value.equals(((UInt64) o).value); + } + public int hashCode() + { + return value.hashCode(); + } + /** Compare two UInt32s. + * @return 0 if equal, -ve or +ve if they are different. + */ + public int compareTo(UInt64 other) + { + return this.value.compareTo(other.value); + } + /** The value of this as a string. */ + public String toString() + { + return value.toString(); + } + /** + * Most significant 4 bytes. + */ + public long top() + { + return top; + } + /** + * Least significant 4 bytes. + */ + public long bottom() + { + return bottom; + } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/Variant.java b/app/src/main/java/org/freedesktop/dbus/Variant.java new file mode 100644 index 00000000..c729685c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/Variant.java @@ -0,0 +1,112 @@ +/* + 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.Type; +import java.text.MessageFormat; +import java.util.Vector; +import org.freedesktop.dbus.exceptions.DBusException; + +import cx.ath.matthew.debug.Debug; + +/** + * A Wrapper class for Variant values. + * A method on DBus can send or receive a Variant. + * This will wrap another value whose type is determined at runtime. + * The Variant may be parameterized to restrict the types it may accept. + */ +public class Variant +{ + private final T o; + private final Type type; + private final String sig; + /** + * Create a Variant from a basic type object. + * @param o The wrapped value. + * @throws IllegalArugmentException If you try and wrap Null or an object of a non-basic type. + */ + public Variant(T o) throws IllegalArgumentException + { + if (null == o) throw new IllegalArgumentException($_("Can't wrap Null in a Variant")); + type = o.getClass(); + try { + String[] ss = Marshalling.getDBusType(o.getClass(), true); + if (ss.length != 1) + throw new IllegalArgumentException($_("Can't wrap a multi-valued type in a Variant: ")+type); + this.sig = ss[0]; + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new IllegalArgumentException(MessageFormat.format($_("Can't wrap {0} in an unqualified Variant ({1})."), new Object[] { o.getClass(), DBe.getMessage() })); + } + this.o = o; + } + /** + * Create a Variant. + * @param o The wrapped value. + * @param type The explicit type of the value. + * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus. + */ + public Variant(T o, Type type) throws IllegalArgumentException + { + if (null == o) throw new IllegalArgumentException($_("Can't wrap Null in a Variant")); + this.type = type; + try { + String[] ss = Marshalling.getDBusType(type); + if (ss.length != 1) + throw new IllegalArgumentException($_("Can't wrap a multi-valued type in a Variant: ")+type); + this.sig = ss[0]; + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new IllegalArgumentException(MessageFormat.format($_("Can't wrap {0} in an unqualified Variant ({1})."), new Object[] { type, DBe.getMessage() })); + } + this.o = o; + } + /** + * Create a Variant. + * @param o The wrapped value. + * @param sig The explicit type of the value, as a dbus type string. + * @throws IllegalArugmentException If you try and wrap Null or an object which cannot be sent over DBus. + */ + public Variant(T o, String sig) throws IllegalArgumentException + { + if (null == o) throw new IllegalArgumentException($_("Can't wrap Null in a Variant")); + this.sig = sig; + try { + Vector ts = new Vector(); + Marshalling.getJavaType(sig, ts,1); + if (ts.size() != 1) + throw new IllegalArgumentException($_("Can't wrap multiple or no types in a Variant: ")+sig); + this.type = ts.get(0); + } catch (DBusException DBe) { + if (AbstractConnection.EXCEPTION_DEBUG && Debug.debug) Debug.print(Debug.ERR, DBe); + throw new IllegalArgumentException(MessageFormat.format($_("Can't wrap {0} in an unqualified Variant ({1})."), new Object[] { sig, DBe.getMessage() })); + } + this.o = o; + } + /** Return the wrapped value. */ + public T getValue() { return o; } + /** Return the type of the wrapped value. */ + public Type getType() { return type; } + /** Return the dbus signature of the wrapped value. */ + public String getSig() { return sig; } + /** Format the Variant as a string. */ + public String toString() { return "["+o+"]"; } + /** Compare this Variant with another by comparing contents */ + @SuppressWarnings("unchecked") + public boolean equals(Object other) + { + if (null == other) return false; + if (!(other instanceof Variant)) return false; + return this.o.equals(((Variant)other).o); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/Caller.java b/app/src/main/java/org/freedesktop/dbus/bin/Caller.java new file mode 100644 index 00000000..ecd6002f --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/Caller.java @@ -0,0 +1,83 @@ +/* + 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 java.lang.reflect.Constructor; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Vector; +import java.io.File; +import org.freedesktop.dbus.BusAddress; +import org.freedesktop.dbus.Error; +import org.freedesktop.dbus.Marshalling; +import org.freedesktop.dbus.Message; +import org.freedesktop.dbus.MethodCall; +import org.freedesktop.dbus.Transport; +import cx.ath.matthew.debug.Debug; + +public class Caller +{ + @SuppressWarnings("unchecked") + public static void main(String[] args) + { + try { + if (Debug.debug) { + Debug.setHexDump(true); + Debug.loadConfig(new File("debug.conf")); + } + if (args.length < 4) { + System.out.println ("Syntax: Caller [ ]"); + System.exit(1); + } + String addr = System.getenv("DBUS_SESSION_BUS_ADDRESS"); + BusAddress address = new BusAddress(addr); + Transport conn = new Transport(address); + + Message m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello", (byte) 0, null);; + conn.mout.writeMessage(m); + + if ("".equals(args[2])) args[2] = null; + if (args.length == 4) + m = new MethodCall(args[0], args[1], args[2], args[3], (byte) 0, null); + else { + Vector lts = new Vector(); + Marshalling.getJavaType(args[4],lts, -1); + Type[] ts = lts.toArray(new Type[0]); + Object[] os = new Object[args.length-5]; + for (int i = 5; i < args.length; i++) { + if (ts[i-5] instanceof Class) { + try { + Constructor c = ((Class) ts[i-5]).getConstructor(String.class); + os[i-5] = c.newInstance(args[i]); + } catch (Exception e) { + os[i-5] = args[i]; + } + } else + os[i-5] = args[i]; + } + m = new MethodCall(args[0], args[1], args[2], args[3], (byte) 0, args[4], os); + } + long serial = m.getSerial(); + conn.mout.writeMessage(m); + do { + m = conn.min.readMessage(); + } while (serial != m.getReplySerial()); + if (m instanceof Error) ((Error) m).throwException(); + else { + Object[] os = m.getParameters(); + System.out.println(Arrays.deepToString(os)); + } + } catch (Exception e) { + System.out.println (e.getClass().getSimpleName()+": "+e.getMessage()); + System.exit(1); + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java b/app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java new file mode 100644 index 00000000..cfadcc62 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/CreateInterface.java @@ -0,0 +1,711 @@ +/* + 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 imports, Map structs, boolean container, boolean fullnames) throws DBusException + { + if (t instanceof ParameterizedType) { + String s; + Class c = (Class) ((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 c = (Class) 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 d = (Class) f.get(c); + return d.getSimpleName(); + } catch (Exception e) { + return c.getSimpleName(); + } + } + } + } else return ""; + } + private static String getJavaType(String dbus, Set imports, Map structs, boolean container, boolean fullnames) throws DBusException + { + if (null == dbus || "".equals(dbus)) return ""; + Vector v = new Vector(); + 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 out, Set imports, Map tuples, Map 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 imports, Map tuples, Map structs, Set exceptions, Set anns) throws DBusException + { + Vector in = new Vector(); + Vector out = new Vector(); + 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 imports, Map structs, Set anns) throws DBusException + { + Map params = new HashMap(); + Vector porder = new Vector(); + 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 imports, Set 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 tuples, Map structs, Set exceptions, Set 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 imports = new TreeSet(); + 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 existing) throws DBusException, IOException + { + out.println("package "+pack+";"); + + Set imports = new TreeSet(); + imports.add("org.freedesktop.dbus.Position"); + imports.add("org.freedesktop.dbus.Struct"); + Map structs = new HashMap(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 tocreate = new HashMap(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 structs = new HashMap(); + Set exceptions = new TreeSet(); + Set annotations = new TreeSet(); + + for (Node iface: new IterableNodeList(root.getChildNodes())) { + + if (Node.ELEMENT_NODE != iface.getNodeType()) continue; + + checkNode(iface, "interface", "node"); + + if ("interface".equals(iface.getNodeName())) { + + Map tuples = new HashMap(); + 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 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 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 structs, Map 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 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 [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); + + } +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java b/app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java new file mode 100644 index 00000000..8595fe07 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/DBusDaemon.java @@ -0,0 +1,878 @@ +/* + 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 org.freedesktop.DBus; +import org.freedesktop.dbus.AbstractConnection; +import org.freedesktop.dbus.BusAddress; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.DirectConnection; +import org.freedesktop.dbus.Error; +import org.freedesktop.dbus.Marshalling; +import org.freedesktop.dbus.Message; +import org.freedesktop.dbus.MessageReader; +import org.freedesktop.dbus.MessageWriter; +import org.freedesktop.dbus.MethodCall; +import org.freedesktop.dbus.MethodReturn; +import org.freedesktop.dbus.Transport; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.FatalException; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +import cx.ath.matthew.debug.Debug; +import cx.ath.matthew.unix.UnixServerSocket; +import cx.ath.matthew.unix.UnixSocket; +import cx.ath.matthew.unix.UnixSocketAddress; + +/** + * A replacement DBusDaemon + */ +public class DBusDaemon extends Thread +{ + public static final int QUEUE_POLL_WAIT = 500; + static class Connstruct + { + public UnixSocket usock; + public Socket tsock; + public MessageReader min; + public MessageWriter mout; + public String unique; + public Connstruct(UnixSocket sock) + { + this.usock = sock; + min = new MessageReader(sock.getInputStream()); + mout = new MessageWriter(sock.getOutputStream()); + } + public Connstruct(Socket sock) throws IOException + { + this.tsock = sock; + min = new MessageReader(sock.getInputStream()); + mout = new MessageWriter(sock.getOutputStream()); + } + public String toString() + { + return null == unique ? ":?-?" : unique; + } + } + static class MagicMap + { + private Map> m; + private LinkedList q; + private String name; + public MagicMap(String name) + { + m = new HashMap>(); + q = new LinkedList(); + this.name = name; + } + public A head() + { + return q.getFirst(); + } + public void putFirst(A a, B b) + { + if (Debug.debug) Debug.print("<"+name+"> Queueing {"+a+" => "+b+"}"); + if (m.containsKey(a)) + m.get(a).add(b); + else { + LinkedList l = new LinkedList(); + l.add(b); + m.put(a, l); + } + q.addFirst(a); + } + public void putLast(A a, B b) + { + if (Debug.debug) Debug.print("<"+name+"> Queueing {"+a+" => "+b+"}"); + if (m.containsKey(a)) + m.get(a).add(b); + else { + LinkedList l = new LinkedList(); + l.add(b); + m.put(a, l); + } + q.addLast(a); + } + public List remove(A a) + { + if (Debug.debug) Debug.print("<"+name+"> Removing {"+a+"}"); + q.remove(a); + return m.remove(a); + } + public int size() + { + return q.size(); + } + } + public class DBusServer extends Thread implements DBus, DBus.Introspectable, DBus.Peer + { + public DBusServer() + { + setName("Server"); + } + public Connstruct c; + public Message m; + public boolean isRemote() { return false; } + public String Hello() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + synchronized (c) { + if (null != c.unique) + throw new org.freedesktop.DBus.Error.AccessDenied($_("Connection has already sent a Hello message")); + synchronized (unique_lock) { + c.unique = ":1."+(++next_unique); + } + } + synchronized (names) { + names.put(c.unique, c); + } + + if (Debug.debug) Debug.print(Debug.WARN, "Client "+c.unique+" registered"); + + try { + send(c, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameAcquired", "s", c.unique)); + DBusSignal s = new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", c.unique, "", c.unique); + send(null, s); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return c.unique; + } + public String[] ListNames() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + String[] ns; + synchronized (names) { + Set nss = names.keySet(); + ns = nss.toArray(new String[0]); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return ns; + } + + public boolean NameHasOwner(String name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + boolean rv; + synchronized (names) { + rv = names.containsKey(name); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return rv; + } + public String GetNameOwner(String name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + Connstruct owner = names.get(name); + String o; + if (null == owner) + o = ""; + else + o = owner.unique; + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return o; + } + + public UInt32 GetConnectionUnixUser(String connection_name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(0); + } + public UInt32 StartServiceByName(String name, UInt32 flags) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(0); + } + public UInt32 RequestName(String name, UInt32 flags) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + + boolean exists = false; + synchronized (names) { + if (!(exists = names.containsKey(name))) + names.put(name, c); + } + + int rv; + if (exists) { + rv = DBus.DBUS_REQUEST_NAME_REPLY_EXISTS; + } else { + if (Debug.debug) Debug.print(Debug.WARN, "Client "+c.unique+" acquired name "+name); + rv = DBus.DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER; + try { + send(c, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameAcquired", "s", name)); + send(null, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", name, "", c.unique)); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + } + + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(rv); + } + + public UInt32 ReleaseName(String name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + + boolean exists = false; + synchronized (names) { + if ((exists = (names.containsKey(name) && names.get(name).equals(c)))) + names.remove(name); + } + + int rv; + if (!exists) { + rv = DBus.DBUS_RELEASE_NAME_REPLY_NON_EXISTANT; + } else { + if (Debug.debug) Debug.print(Debug.WARN, "Client "+c.unique+" acquired name "+name); + rv = DBus.DBUS_RELEASE_NAME_REPLY_RELEASED; + try { + send(c, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameLost", "s", name)); + send(null, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", name, c.unique, "")); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + } + + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(rv); + } + public void AddMatch(String matchrule) throws Error.MatchRuleInvalid + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Adding match rule: "+matchrule); + synchronized (sigrecips) { + if (!sigrecips.contains(c)) + sigrecips.add(c); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return; + } + public void RemoveMatch(String matchrule) throws Error.MatchRuleInvalid + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Removing match rule: "+matchrule); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return; + } + public String[] ListQueuedOwners(String name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new String[0]; + } + public UInt32 GetConnectionUnixProcessID(String connection_name) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new UInt32(0); + } + public Byte[] GetConnectionSELinuxSecurityContext(String a) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return new Byte[0]; + } + public void ReloadConfig() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return; + } + @SuppressWarnings("unchecked") + private void handleMessage(Connstruct c, Message m) throws DBusException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.VERBOSE, "Handling message "+m+" from "+c.unique); + if (!(m instanceof MethodCall)) return; + Object[] args = m.getParameters(); + + Class[] cs = new Class[args.length]; + + for (int i = 0; i < cs.length; i++) + cs[i] = args[i].getClass(); + + java.lang.reflect.Method meth = null; + Object rv = null; + + try { + meth = DBusServer.class.getMethod(m.getName(), cs); + try { + this.c = c; + this.m = m; + rv = meth.invoke(dbus_server, args); + if (null == rv) { + send(c, new MethodReturn("org.freedesktop.DBus", (MethodCall) m, null), true); + } else { + String sig = Marshalling.getDBusType(meth.getGenericReturnType())[0]; + send(c, new MethodReturn("org.freedesktop.DBus", (MethodCall) m, sig, rv), true); + } + } catch (InvocationTargetException ITe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, ITe); + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, ITe.getCause()); + send(c, new org.freedesktop.dbus.Error("org.freedesktop.DBus", m, ITe.getCause())); + } catch (DBusExecutionException DBEe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBEe); + send(c, new org.freedesktop.dbus.Error("org.freedesktop.DBus", m, DBEe)); + } catch (Exception e) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, e); + send(c,new org.freedesktop.dbus.Error("org.freedesktop.DBus", c.unique, "org.freedesktop.DBus.Error.GeneralError", m.getSerial(), "s", $_("An error occurred while calling ")+m.getName())); + } + } catch (NoSuchMethodException NSMe) { + send(c,new org.freedesktop.dbus.Error("org.freedesktop.DBus", c.unique, "org.freedesktop.DBus.Error.UnknownMethod", m.getSerial(), "s", $_("This service does not support ")+m.getName())); + } + + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + public String Introspect() + { + return "\n"+ + "\n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + " \n"+ + ""; + } + public void Ping() {} + + public void run() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + while (_run) { + Message m; + List> wcs; + // block on outqueue + synchronized (localqueue) { + while (localqueue.size() == 0) try { + localqueue.wait(); + } catch (InterruptedException Ie) { } + m = localqueue.head(); + wcs = localqueue.remove(m); + } + if (null != wcs) try { + for (WeakReference wc: wcs) { + Connstruct c = wc.get(); + if (null != c) { + if (Debug.debug) Debug.print(Debug.VERBOSE, " Got message "+m+" from "+c); + handleMessage(c, m); + } + } + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + else if (Debug.debug) Debug.print(Debug.INFO, "Discarding "+m+" connection reaped"); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + } + public class Sender extends Thread + { + public Sender() + { + setName("Sender"); + } + public void run() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + while (_run) { + + if (Debug.debug) Debug.print(Debug.VERBOSE, "Acquiring lock on outqueue and blocking for data"); + Message m = null; + List> wcs = null; + // block on outqueue + synchronized (outqueue) { + while (outqueue.size() == 0) try { + outqueue.wait(); + } catch (InterruptedException Ie) { } + + m = outqueue.head(); + wcs = outqueue.remove(m); + } + if (null != wcs) { + for (WeakReference wc: wcs) { + Connstruct c = wc.get(); + if (null != c) { + if (Debug.debug) Debug.print(Debug.VERBOSE, " Got message "+m+" for "+c.unique); + if (Debug.debug) Debug.print(Debug.INFO, "Sending message "+m+" to "+c.unique); + try { + c.mout.writeMessage(m); + } + catch (IOException IOe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); + removeConnection(c); + } + } + } + } + else if (Debug.debug) Debug.print(Debug.INFO, "Discarding "+m+" connection reaped"); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + } + public class Reader extends Thread + { + private Connstruct conn; + private WeakReference weakconn; + private boolean _lrun = true; + public Reader(Connstruct conn) + { + this.conn = conn; + weakconn = new WeakReference(conn); + setName("Reader"); + } + public void stopRunning() + { + _lrun = false; + } + public void run() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + while (_run && _lrun) { + + Message m = null; + try { + m = conn.min.readMessage(); + } catch (IOException IOe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, IOe); + removeConnection(conn); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + if (DBe instanceof FatalException) + removeConnection(conn); + } + + if (null != m) { + if (Debug.debug) Debug.print(Debug.INFO, "Read "+m+" from "+conn.unique); + synchronized (inqueue) { + inqueue.putLast(m, weakconn); + inqueue.notifyAll(); + } + } + } + conn = null; + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + } + + private Map conns = new HashMap(); + private HashMap names = new HashMap(); + private MagicMap> outqueue = new MagicMap>("out"); + private MagicMap> inqueue = new MagicMap>("in"); + private MagicMap> localqueue = new MagicMap>("local"); + private List sigrecips = new Vector(); + private boolean _run = true; + private int next_unique = 0; + private Object unique_lock = new Object(); + DBusServer dbus_server = new DBusServer(); + Sender sender = new Sender(); + + public DBusDaemon() + { + setName("Daemon"); + synchronized (names) { + names.put("org.freedesktop.DBus", null); + } + } + @SuppressWarnings("unchecked") + private void send(Connstruct c, Message m) + { + send (c, m, false); + } + private void send(Connstruct c, Message m, boolean head) + { + if (Debug.debug){ + Debug.print(Debug.DEBUG, "enter"); + if (null == c) + Debug.print(Debug.VERBOSE, "Queing message "+m+" for all connections"); + else + Debug.print(Debug.VERBOSE, "Queing message "+m+" for "+c.unique); + } + // send to all connections + if (null == c) { + synchronized (conns) { + synchronized (outqueue) { + for (Connstruct d: conns.keySet()) + if (head) + outqueue.putFirst(m, new WeakReference(d)); + else + outqueue.putLast(m, new WeakReference(d)); + outqueue.notifyAll(); + } + } + } else { + synchronized (outqueue) { + if (head) + outqueue.putFirst(m, new WeakReference(c)); + else + outqueue.putLast(m, new WeakReference(c)); + outqueue.notifyAll(); + } + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + @SuppressWarnings("unchecked") + private List findSignalMatches(DBusSignal sig) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + List l; + synchronized (sigrecips) { + l = new Vector(sigrecips); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + return l; + } + @SuppressWarnings("unchecked") + public void run() + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + while (_run) { + try { + Message m; + List> wcs; + synchronized (inqueue) { + while (0 == inqueue.size()) try { + inqueue.wait(); + } catch (InterruptedException Ie) {} + + m = inqueue.head(); + wcs = inqueue.remove(m); + } + if (null != wcs) { + for (WeakReference wc: wcs) { + Connstruct c = wc.get(); + if (null != c) { + if (Debug.debug) Debug.print(Debug.INFO, " Got message "+m+" from "+c.unique); + // check if they have hello'd + if (null == c.unique + && (!(m instanceof MethodCall) + || !"org.freedesktop.DBus".equals(m.getDestination()) + || !"Hello".equals(m.getName()))) { + send(c,new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.AccessDenied", m.getSerial(), "s", $_("You must send a Hello message"))); + } else { + try { + if (null != c.unique) m.setSource(c.unique); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + send(c,new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.GeneralError", m.getSerial(), "s", $_("Sending message failed"))); + } + + if ("org.freedesktop.DBus".equals(m.getDestination())) { + synchronized (localqueue) { + localqueue.putLast(m, wc); + localqueue.notifyAll(); + } + } else { + if (m instanceof DBusSignal) { + List list = findSignalMatches((DBusSignal) m); + for (Connstruct d: list) + send(d, m); + } else { + Connstruct dest = names.get(m.getDestination()); + + if (null == dest) { + send(c, new Error("org.freedesktop.DBus", null, "org.freedesktop.DBus.Error.ServiceUnknown", m.getSerial(), "s", MessageFormat.format($_("The name `{0}' does not exist"), new Object[] { m.getDestination() }))); + } else + send(dest, m); + } + } + } + } + } + } + } + catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + private void removeConnection(Connstruct c) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + boolean exists; + synchronized(conns) { + if ((exists = conns.containsKey(c))) { + Reader r = conns.get(c); + r.stopRunning(); + conns.remove(c); + } + } + if (exists) { + try { + if (null != c.usock) c.usock.close(); + if (null != c.tsock) c.tsock.close(); + } catch (IOException IOe) {} + synchronized(names) { + List toRemove = new Vector(); + for (String name: names.keySet()) + if (names.get(name) == c) { + toRemove.add(name); + try { + send(null, new DBusSignal("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "NameOwnerChanged", "sss", name, c.unique, "")); + } catch (DBusException DBe) { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.ERR, DBe); + } + } + for (String name: toRemove) + names.remove(name); + } + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + public void addSock(UnixSocket us) + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.WARN, "New Client"); + Connstruct c = new Connstruct(us); + Reader r = new Reader(c); + synchronized (conns) { + conns.put(c, r); + } + r.start(); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + public void addSock(Socket s) throws IOException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + if (Debug.debug) Debug.print(Debug.WARN, "New Client"); + Connstruct c = new Connstruct(s); + Reader r = new Reader(c); + synchronized (conns) { + conns.put(c, r); + } + r.start(); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + public static void syntax() + { + System.out.println("Syntax: DBusDaemon [--version] [-v] [--help] [-h] [--listen address] [-l address] [--print-address] [-r] [--pidfile file] [-p file] [--addressfile file] [-a file] [--unix] [-u] [--tcp] [-t] "); + System.exit(1); + } + public static void version() + { + System.out.println("D-Bus Java Version: "+System.getProperty("Version")); + System.exit(1); + } + public static void saveFile(String data, String file) throws IOException + { + PrintWriter w = new PrintWriter(new FileOutputStream(file)); + w.println(data); + w.close(); + } + public static void main(String args[]) throws Exception + { + if (Debug.debug && AbstractConnection.EXCEPTION_DEBUG) Debug.print(Debug.DEBUG, "enter"); + else if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + String addr = null; + String pidfile = null; + String addrfile = null; + boolean printaddress = false; + boolean unix = true; + boolean tcp = false; + + // parse options + try { + for (int i=0; i < args.length; i++) + if ("--help".equals(args[i]) || "-h".equals(args[i])) + syntax(); + else if ("--version".equals(args[i]) || "-v".equals(args[i])) + version(); + else if ("--listen".equals(args[i]) || "-l".equals(args[i])) + addr = args[++i]; + else if ("--pidfile".equals(args[i]) || "-p".equals(args[i])) + pidfile = args[++i]; + else if ("--addressfile".equals(args[i]) || "-a".equals(args[i])) + addrfile = args[++i]; + else if ("--print-address".equals(args[i]) || "-r".equals(args[i])) + printaddress = true; + else if ("--unix".equals(args[i]) || "-u".equals(args[i])) { + unix = true; + tcp = false; + } else if ("--tcp".equals(args[i]) || "-t".equals(args[i])) { + tcp = true; + unix = false; + } else syntax(); + } catch (ArrayIndexOutOfBoundsException AIOOBe) { + syntax(); + } + + // generate a random address if none specified + if (null == addr && unix) addr = DirectConnection.createDynamicSession(); + else if (null == addr && tcp) addr = DirectConnection.createDynamicTCPSession(); + + BusAddress address = new BusAddress(addr); + if (null == address.getParameter("guid")) { + addr += ",guid="+Transport.genGUID(); + address = new BusAddress(addr); + } + + // print address to stdout + if (printaddress) System.out.println(addr); + + // print address to file + if (null != addrfile) saveFile(addr, addrfile); + + // print PID to file + if (null != pidfile) saveFile(System.getProperty("Pid"), pidfile); + + // start the daemon + if (Debug.debug) Debug.print(Debug.WARN, "Binding to "+addr); + if ("unix".equals(address.getType())) + doUnix(address); + else if ("tcp".equals(address.getType())) + doTCP(address); + else throw new Exception("Unknown address type: "+address.getType()); + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + private static void doUnix(BusAddress address) throws IOException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + UnixServerSocket uss; + if (null != address. getParameter("abstract")) + uss = new UnixServerSocket(new UnixSocketAddress(address.getParameter("abstract"), true)); + else + uss = new UnixServerSocket(new UnixSocketAddress(address.getParameter("path"), false)); + DBusDaemon d = new DBusDaemon(); + d.start(); + d.sender.start(); + d.dbus_server.start(); + + // accept new connections + while (d._run) { + UnixSocket s = uss.accept(); + if ((new Transport.SASL()).auth(Transport.SASL.MODE_SERVER, Transport.SASL.AUTH_EXTERNAL, address.getParameter("guid"), s.getOutputStream(), s.getInputStream(), s)) { + // s.setBlocking(false); + d.addSock(s); + } else + s.close(); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } + private static void doTCP(BusAddress address) throws IOException + { + if (Debug.debug) Debug.print(Debug.DEBUG, "enter"); + ServerSocket ss = new ServerSocket(Integer.parseInt(address.getParameter("port")),10, InetAddress.getByName(address.getParameter("host"))); + DBusDaemon d = new DBusDaemon(); + d.start(); + d.sender.start(); + d.dbus_server.start(); + + // accept new connections + while (d._run) { + Socket s = ss.accept(); + boolean authOK=false; + try { + authOK = (new Transport.SASL()).auth(Transport.SASL.MODE_SERVER, Transport.SASL.AUTH_EXTERNAL, address.getParameter("guid"), s.getOutputStream(), s.getInputStream(), null); + } catch (Exception e) { + if (Debug.debug) Debug. print(Debug.DEBUG, e); + } + if (authOK) { + d.addSock(s); + } else + s.close(); + } + if (Debug.debug) Debug.print(Debug.DEBUG, "exit"); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java b/app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java new file mode 100644 index 00000000..db596f41 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/IdentifierMangler.java @@ -0,0 +1,43 @@ +/* + 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 java.util.Arrays; + +/** + * Checks identifiers for keywords etc and mangles them if so. + */ +public class IdentifierMangler +{ + private static String[] keywords; + static { + keywords = new String[] { + "true","false","null", + "abstract","continue","for","new","switch", + "assert","default","goto","package","synchronized", + "boolean","do","if","private","this", + "break","double","implements","protected","throw", + "byte","else","import","public","throws", + "case","enum","instanceof","return","transient", + "catch","extends","int","short","try", + "char","final","interface","static","void", + "class","finally","long","strictfp","volatile", + "const","float","native","super","while" + }; + Arrays.sort(keywords); + } + public static String mangle(String name) + { + if (Arrays.binarySearch(keywords, name) >= 0) + name = "_"+name; + return name; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java b/app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java new file mode 100644 index 00000000..ddd5884b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/IterableNodeList.java @@ -0,0 +1,29 @@ +/* + 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 java.util.Iterator; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +class IterableNodeList implements Iterable +{ + private NodeList nl; + public IterableNodeList(NodeList nl) + { + this.nl = nl; + } + public Iterator iterator() + { + return new NodeListIterator(nl); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java b/app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java new file mode 100644 index 00000000..93d4ad59 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/ListDBus.java @@ -0,0 +1,74 @@ +/* + 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 org.freedesktop.DBus; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +/** + * This class lists all the names currently connected on the bus + */ +public class ListDBus +{ + public static void syntax() + { + System.out.println("Syntax: ListDBus [--version] [-v] [--help] [-h] [--owners] [-o] [--uids] [-u] [--session] [-s] [--system] [-y]"); + System.exit(1); + } + public static void version() + { + System.out.println("Java D-Bus Version "+System.getProperty("Version")); + System.exit(1); + } + public static void main(String args[]) throws Exception + { + boolean owners = false; + boolean users = false; + int connection = DBusConnection.SESSION; + + for (String a: args) + if ("--help".equals(a)) syntax(); + else if ("-h".equals(a)) syntax(); + else if ("--version".equals(a)) version(); + else if ("-v".equals(a)) version(); + else if ("-u".equals(a)) users = true; + else if ("--uids".equals(a)) users = true; + else if ("-o".equals(a)) owners = true; + else if ("--owners".equals(a)) owners = true; + else if ("--session".equals(a)) connection = DBusConnection.SESSION; + else if ("-s".equals(a)) connection = DBusConnection.SESSION; + else if ("--system".equals(a)) connection = DBusConnection.SYSTEM; + else if ("-y".equals(a)) connection = DBusConnection.SYSTEM; + else syntax(); + + DBusConnection conn = DBusConnection.getConnection(connection); + DBus dbus = conn.getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); + String[] names = dbus.ListNames(); + for (String s: names) { + if (users) + try { + System.out.print(dbus.GetConnectionUnixUser(s)+"\t"); + } catch (DBusExecutionException DBEe) { + System.out.print("\t"); + } + System.out.print(s); + if (!s.startsWith(":") && owners) { + try { + System.out.print("\t"+dbus.GetNameOwner(s)); + } catch (DBusExecutionException DBEe) { + } + } + System.out.println(); + } + conn.disconnect(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java b/app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java new file mode 100644 index 00000000..ea2d23a1 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/NodeListIterator.java @@ -0,0 +1,38 @@ +/* + 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 java.util.Iterator; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +class NodeListIterator implements Iterator +{ + NodeList nl; + int i; + NodeListIterator(NodeList nl) + { + this.nl = nl; + i = 0; + } + public boolean hasNext() + { + return i < nl.getLength(); + } + public Node next() + { + Node n = nl.item(i); + i++; + return n; + } + public void remove() {}; +} diff --git a/app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java b/app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java new file mode 100644 index 00000000..f302f6dc --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/bin/StructStruct.java @@ -0,0 +1,54 @@ +/* + 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 java.lang.reflect.Type; +import java.util.Map; +import java.util.HashMap; + +class StructStruct +{ + public static Map fillPackages(Map structs, String pack) + { + Map newmap = new HashMap(); + for (StructStruct ss: structs.keySet()) { + Type[] type = structs.get(ss); + if (null == ss.pack) ss.pack = pack; + newmap.put(ss, type); + } + return newmap; + } + public String name; + public String pack; + public StructStruct(String name) + { + this.name = name; + } + public StructStruct(String name, String pack) + { + this.name = name; + this.pack = pack; + } + public int hashCode() + { + return name.hashCode(); + } + public boolean equals(Object o) + { + if (!(o instanceof StructStruct)) return false; + if (!name.equals(((StructStruct) o).name)) return false; + return true; + } + public String toString() + { + return "<"+name+", "+pack+">"; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java new file mode 100644 index 00000000..15f46d0c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusException.java @@ -0,0 +1,26 @@ +/* + 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.exceptions; + +/** + * An exception within DBus. + */ +@SuppressWarnings("serial") +public class DBusException extends Exception +{ + /** + * Create an exception with the specified message + */ + public DBusException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java new file mode 100644 index 00000000..7dd54fd0 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/DBusExecutionException.java @@ -0,0 +1,40 @@ +/* + 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.exceptions; + +/** + * An exception while running a remote method within DBus. + */ +@SuppressWarnings("serial") +public class DBusExecutionException extends RuntimeException +{ + private String type; + /** + * Create an exception with the specified message + */ + public DBusExecutionException(String message) + { + super(message); + } + public void setType(String type) + { + this.type = type; + } + /** + * Get the DBus type of this exception. Use if this + * was an exception we don't have a class file for. + */ + public String getType() + { + if (null == type) return getClass().getName(); + else return type; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java new file mode 100644 index 00000000..6f8d4010 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalDBusException.java @@ -0,0 +1,20 @@ +/* + 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.exceptions; + +@SuppressWarnings("serial") +public class FatalDBusException extends DBusException implements FatalException +{ + public FatalDBusException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java new file mode 100644 index 00000000..d69efa5a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/FatalException.java @@ -0,0 +1,15 @@ +/* + 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.exceptions; + +public interface FatalException +{ +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java new file mode 100644 index 00000000..4113bca8 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/InternalMessageException.java @@ -0,0 +1,20 @@ +/* + 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.exceptions; + +@SuppressWarnings("serial") +public class InternalMessageException extends DBusExecutionException implements NonFatalException +{ + public InternalMessageException(String message) + { + super (message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java new file mode 100644 index 00000000..cf60d3bd --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/MarshallingException.java @@ -0,0 +1,20 @@ +/* + 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.exceptions; + +@SuppressWarnings("serial") +public class MarshallingException extends DBusException implements NonFatalException +{ + public MarshallingException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java new file mode 100644 index 00000000..b7efcf7b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageFormatException.java @@ -0,0 +1,23 @@ +/* + 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.exceptions; + +/** + * Thrown if a message is formatted incorrectly. + */ +@SuppressWarnings("serial") +public class MessageFormatException extends DBusException implements NonFatalException +{ + public MessageFormatException(String message) + { + super (message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java new file mode 100644 index 00000000..9f6e44e3 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageProtocolVersionException.java @@ -0,0 +1,22 @@ +/* + 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.exceptions; + +import java.io.IOException; + +@SuppressWarnings("serial") +public class MessageProtocolVersionException extends IOException implements FatalException +{ + public MessageProtocolVersionException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java new file mode 100644 index 00000000..b36f30ee --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/MessageTypeException.java @@ -0,0 +1,22 @@ +/* + 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.exceptions; + +import java.io.IOException; + +@SuppressWarnings("serial") +public class MessageTypeException extends IOException implements NonFatalException +{ + public MessageTypeException(String message) + { + super(message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java new file mode 100644 index 00000000..dc565f2e --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/NonFatalException.java @@ -0,0 +1,15 @@ +/* + 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.exceptions; + +public interface NonFatalException +{ +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java b/app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java new file mode 100644 index 00000000..71b8d44a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/NotConnected.java @@ -0,0 +1,23 @@ +/* + 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.exceptions; + +/** + * Thrown if a DBus action is called when not connected to the Bus. + */ +@SuppressWarnings("serial") +public class NotConnected extends DBusExecutionException implements FatalException +{ + public NotConnected(String message) + { + super (message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java b/app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java new file mode 100644 index 00000000..76fe2800 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/exceptions/UnknownTypeCodeException.java @@ -0,0 +1,21 @@ +/* + 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.exceptions; +import static org.freedesktop.dbus.Gettext.$_; + +@SuppressWarnings("serial") +public class UnknownTypeCodeException extends DBusException implements NonFatalException +{ + public UnknownTypeCodeException(byte code) + { + super($_("Not a valid D-Bus type code: ") + code); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java b/app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java new file mode 100644 index 00000000..9fa963b8 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/ProfileStruct.java @@ -0,0 +1,32 @@ +/* + 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.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.UInt32; + +public final class ProfileStruct extends Struct +{ + @Position(0) + public final String a; + @Position(1) + public final UInt32 b; + @Position(2) + public final long c; + + public ProfileStruct(String a, UInt32 b, long c) + { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/Profiler.java b/app/src/main/java/org/freedesktop/dbus/test/Profiler.java new file mode 100644 index 00000000..efe9ab21 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/Profiler.java @@ -0,0 +1,40 @@ +/* + 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.test; + +import org.freedesktop.DBus.Method.NoReply; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.exceptions.DBusException; +import java.util.List; +import java.util.Map; + +public interface Profiler extends DBusInterface +{ + public class ProfileSignal extends DBusSignal + { + public ProfileSignal(String path) throws DBusException + { + super(path); + } + } + public void array(int[] v); + public void stringarray(String[] v); + public void map(Map m); + public void list(List l); + public void bytes(byte[] b); + public void struct(ProfileStruct ps); + public void string(String s); + public void NoReply(); + public void Pong(); +} + + diff --git a/app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java b/app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java new file mode 100644 index 00000000..b99d1b75 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/ProfilerInstance.java @@ -0,0 +1,28 @@ +/* + 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.test; + +import java.util.List; +import java.util.Map; + +public class ProfilerInstance implements Profiler +{ + public boolean isRemote() { return false; } + public void array(int[] v) { return; } + public void stringarray(String[] v) { return; } + public void map(Map m) { return; } + public void list(List l) { return; } + public void bytes(byte[] b) { return; } + public void struct(ProfileStruct ps) { return; } + public void string(String s) { return; } + public void NoReply() { return; } + public void Pong() { return; } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestException.java b/app/src/main/java/org/freedesktop/dbus/test/TestException.java new file mode 100644 index 00000000..2314ec33 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestException.java @@ -0,0 +1,24 @@ +/* + 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.test; + +import org.freedesktop.DBus.Description; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +@Description("A test exception to throw over DBus") +@SuppressWarnings("serial") +public class TestException extends DBusExecutionException +{ + public TestException(String message) + { + super (message); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java new file mode 100644 index 00000000..4199403a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestNewInterface.java @@ -0,0 +1,26 @@ +/* + 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.test; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.DBus.Description; + +/** + * A sample remote interface which exports one method. + */ +public interface TestNewInterface extends DBusInterface +{ + /** + * A simple method with no parameters which returns a String + */ + @Description("Simple test method") + public String getName(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java new file mode 100644 index 00000000..a24ef43b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface.java @@ -0,0 +1,57 @@ +/* + 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.test; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.Path; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.DBus.Description; +import org.freedesktop.DBus.Method; + +import java.lang.reflect.Type; + +import java.util.Map; +import java.util.List; +/** + * A sample remote interface which exports one method. + */ +public interface TestRemoteInterface extends DBusInterface +{ + /** + * A simple method with no parameters which returns a String + */ + @Description("Simple test method") + public String getName(); + public String getNameAndThrow(); + @Description("Test of nested maps") + public int frobnicate(List n, Map> m, T v); + @Description("Throws a TestException when called") + public void throwme() throws TestException; + @Description("Waits then doesn't return") + @Method.NoReply() + public void waitawhile(); + @Description("Interface-overloaded method") + public int overload(); + @Description("Testing Type Signatures") + public void sig(Type[] s); + @Description("Testing object paths as Path objects") + public void newpathtest(Path p); + @Description("Testing the float type") + public float testfloat(float[] f); + @Description("Testing structs of structs") + public int[][] teststructstruct(TestStruct3 in); + @Description("Regression test for #13291") + public void reg13291(byte[] as, byte[] bs); + /* test lots of things involving Path */ + public Path pathrv(Path a); + public List pathlistrv(List a); + public Map pathmaprv(Map a); +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java new file mode 100644 index 00000000..a44d6279 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestRemoteInterface2.java @@ -0,0 +1,54 @@ +/* + 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.test; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusInterfaceName; +import org.freedesktop.dbus.DBusMemberName; +import org.freedesktop.dbus.Variant; +import org.freedesktop.DBus.Description; + +import java.util.List; + +@Description("An example remote interface") +@DBusInterfaceName("org.freedesktop.dbus.test.AlternateTestInterface") +public interface TestRemoteInterface2 extends DBusInterface +{ + @Description("Test multiple return values and implicit variant parameters.") + public TestTuple,Boolean> show(A in); + @Description("Test passing structs and explicit variants, returning implicit variants") + public T dostuff(TestStruct foo); + @Description("Test arrays, boxed arrays and lists.") + public List sampleArray(List l, Integer[] is, long[] ls); + @Description("Test passing objects as object paths.") + public DBusInterface getThis(DBusInterface t); + @Description("Test bools work") + @DBusMemberName("checkbool") + public boolean check(); + @Description("Test Serializable Object") + public TestSerializable testSerializable(byte b, TestSerializable s, int i); + @Description("Call another method on itself from within a call") + public String recursionTest(); + @Description("Parameter-overloaded method (string)") + public int overload(String s); + @Description("Parameter-overloaded method (byte)") + public int overload(byte b); + @Description("Parameter-overloaded method (void)") + public int overload(); + @Description("Nested List Check") + public List> checklist(List> lli); + @Description("Get new objects as object paths.") + public TestNewInterface getNew(); + @Description("Test Complex Variants") + public void complexv(Variant v); + @Description("Test Introspect on a different interface") + public String Introspect(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java b/app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java new file mode 100644 index 00000000..80142990 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestSerializable.java @@ -0,0 +1,48 @@ +/* + 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.test; + +import java.util.List; +import java.util.Vector; + +import org.freedesktop.dbus.DBusSerializable; +import org.freedesktop.dbus.exceptions.DBusException; + +public class TestSerializable implements DBusSerializable +{ + private int a; + private String b; + private Vector c; + public TestSerializable(int a, A b, Vector c) + { + this.a = a; + this.b = b.toString(); + this.c = c; + } + public TestSerializable() {} + public void deserialize(int a, String b, List c) + { + this.a = a; + this.b = b; + this.c = new Vector(c); + } + public Object[] serialize() throws DBusException + { + return new Object[] { a, b, c }; + } + public int getInt() { return a; } + public String getString() { return b; } + public Vector getVector() { return c; } + public String toString() + { + return "TestSerializable{"+a+","+b+","+c+"}"; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java new file mode 100644 index 00000000..406ce36c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface.java @@ -0,0 +1,97 @@ +/* + 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.test; + +import org.freedesktop.DBus.Description; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusMemberName; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.Path; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; +import java.util.Map; + +/** + * A sample signal with two parameters + */ +@Description("Test interface containing signals") +public interface TestSignalInterface extends DBusInterface +{ + @Description("Test basic signal") + public static class TestSignal extends DBusSignal + { + public final String value; + public final UInt32 number; + /** + * Create a signal. + */ + public TestSignal(String path, String value, UInt32 number) throws DBusException + { + super(path, value, number); + this.value = value; + this.number = number; + } + } + public static class StringSignal extends DBusSignal + { + public final String aoeu; + public StringSignal(String path, String aoeu) throws DBusException + { + super(path, aoeu); + this.aoeu = aoeu; + } + } + public static class EmptySignal extends DBusSignal + { + public EmptySignal(String path) throws DBusException + { + super(path); + } + } + @Description("Test signal with arrays") + public static class TestArraySignal extends DBusSignal + { + public final List v; + public final Map m; + public TestArraySignal(String path, List v, Map m) throws DBusException + { + super(path, v, m); + this.v = v; + this.m = m; + } + } + @Description("Test signal sending an object path") + @DBusMemberName("TestSignalObject") + public static class TestObjectSignal extends DBusSignal + { + public final DBusInterface otherpath; + public TestObjectSignal(String path, DBusInterface otherpath) throws DBusException + { + super(path, otherpath); + this.otherpath = otherpath; + } + } + public static class TestPathSignal extends DBusSignal + { + public final Path otherpath; + public final List pathlist; + public final Map pathmap; + public TestPathSignal(String path, Path otherpath, List pathlist, Map pathmap) throws DBusException + { + super(path, otherpath, pathlist, pathmap); + this.otherpath = otherpath; + this.pathlist = pathlist; + this.pathmap = pathmap; + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java new file mode 100644 index 00000000..d5c9ac52 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestSignalInterface2.java @@ -0,0 +1,45 @@ +/* + 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.test; + +import org.freedesktop.DBus.Description; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusInterfaceName; +import org.freedesktop.dbus.DBusMemberName; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +/** + * A sample signal with two parameters + */ +@Description("Test interface containing signals") +@DBusInterfaceName("some.other.interface.Name") +public interface TestSignalInterface2 extends DBusInterface +{ + @Description("Test basic signal") + public static class TestRenamedSignal extends DBusSignal + { + public final String value; + public final UInt32 number; + /** + * Create a signal. + */ + public TestRenamedSignal(String path, String value, UInt32 number) throws DBusException + { + super(path, value, number); + this.value = value; + this.number = number; + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestStruct.java b/app/src/main/java/org/freedesktop/dbus/test/TestStruct.java new file mode 100644 index 00000000..ca5deacd --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestStruct.java @@ -0,0 +1,32 @@ +/* + 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.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.Variant; + +public final class TestStruct extends Struct +{ + @Position(0) + public final String a; + @Position(1) + public final UInt32 b; + @Position(2) + public final Variant c; + public TestStruct(String a, UInt32 b, Variant c) + { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java b/app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java new file mode 100644 index 00000000..5c8797b4 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestStruct2.java @@ -0,0 +1,31 @@ +/* + 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.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +public final class TestStruct2 extends Struct +{ + @Position(0) + public final List a; + @Position(1) + public final Variant b; + public TestStruct2(List a, Variant b) throws DBusException + { + this.a = a; + this.b = b; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java b/app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java new file mode 100644 index 00000000..52edbe0a --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestStruct3.java @@ -0,0 +1,30 @@ +/* + 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.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.exceptions.DBusException; + +import java.util.List; + +public final class TestStruct3 extends Struct +{ + @Position(0) + public final TestStruct2 a; + @Position(1) + public final List> b; + public TestStruct3(TestStruct2 a, List> b) throws DBusException + { + this.a = a; + this.b = b; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TestTuple.java b/app/src/main/java/org/freedesktop/dbus/test/TestTuple.java new file mode 100644 index 00000000..1cf507a6 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TestTuple.java @@ -0,0 +1,30 @@ +/* + 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.test; + +import org.freedesktop.dbus.Position; +import org.freedesktop.dbus.Tuple; + +public final class TestTuple extends Tuple +{ + @Position(0) + public final A a; + @Position(1) + public final B b; + @Position(2) + public final C c; + public TestTuple(A a, B b, C c) + { + this.a = a; + this.b = b; + this.c = c; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java b/app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java new file mode 100644 index 00000000..af214cff --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TwoPartInterface.java @@ -0,0 +1,29 @@ +/* + 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.test; + +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.exceptions.DBusException; + +public interface TwoPartInterface extends DBusInterface +{ + public TwoPartObject getNew(); + public class TwoPartSignal extends DBusSignal + { + public final TwoPartObject o; + public TwoPartSignal(String path, TwoPartObject o) throws DBusException + { + super (path, o); + this.o = o; + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java b/app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java new file mode 100644 index 00000000..3c7237b5 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/TwoPartObject.java @@ -0,0 +1,18 @@ +/* + 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.test; + +import org.freedesktop.dbus.DBusInterface; + +public interface TwoPartObject extends DBusInterface +{ + public String getName(); +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java b/app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java new file mode 100644 index 00000000..ae8369b2 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/cross_test_client.java @@ -0,0 +1,513 @@ +/* + 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.test; + +import java.lang.reflect.Array; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.TreeSet; +import java.util.Vector; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.Struct; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.UInt64; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.types.DBusMapType; + +import cx.ath.matthew.debug.Debug; + +public class cross_test_client implements DBus.Binding.TestClient, DBusSigHandler +{ + private DBusConnection conn; + private static Set passed = new TreeSet(); + private static Map> failed = new HashMap>(); + private static cross_test_client ctc; + static { + List l = new Vector(); + l.add("Signal never arrived"); + failed.put("org.freedesktop.DBus.Binding.TestSignals.Triggered", l); + l = new Vector(); + l.add("Method never called"); + failed.put("org.freedesktop.DBus.Binding.TestClient.Response", l); + } + public cross_test_client(DBusConnection conn) + { + this.conn = conn; + } + public boolean isRemote() { return false; } + public void handle(DBus.Binding.TestSignals.Triggered t) + { + failed.remove("org.freedesktop.DBus.Binding.TestSignals.Triggered"); + if (new UInt64(21389479283L).equals(t.a) && "/Test".equals(t.getPath())) + pass("org.freedesktop.DBus.Binding.TestSignals.Triggered"); + else if (!new UInt64(21389479283L).equals(t.a)) + fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal content; expected 21389479283 got "+t.a); + else if (!"/Test".equals(t.getPath())) + fail("org.freedesktop.DBus.Binding.TestSignals.Triggered", "Incorrect signal source object; expected /Test got "+t.getPath()); + } + public void Response(UInt16 a, double b) + { + failed.remove("org.freedesktop.DBus.Binding.TestClient.Response"); + if (a.equals(new UInt16(15)) && (b == 12.5)) + pass("org.freedesktop.DBus.Binding.TestClient.Response"); + else + fail("org.freedesktop.DBus.Binding.TestClient.Response", "Incorrect parameters; expected 15, 12.5 got "+a+", "+b); + } + public static void pass(String test) + { + passed.add(test.replaceAll("[$]", ".")); + } + public static void fail(String test, String reason) + { + test = test.replaceAll("[$]", "."); + List reasons = failed.get(test); + if (null == reasons) { + reasons = new Vector(); + failed.put(test, reasons); + } + reasons.add(reason); + } + @SuppressWarnings("unchecked") + public static void test(Class iface, Object proxy, String method, Object rv, Object... parameters) + { + try { + Method[] ms = iface.getMethods(); + Method m = null; + for (Method t: ms) { + if (t.getName().equals(method)) + m = t; + } + Object o = m.invoke(proxy, parameters); + + String msg = "Incorrect return value; sent ( "; + if (null != parameters) + for (Object po: parameters) + if (null != po) + msg += collapseArray(po) + ","; + msg = msg.replaceAll(".$",");"); + msg += " expected "+collapseArray(rv)+" got "+collapseArray(o); + + if (null != rv && rv.getClass().isArray()) { + compareArray(iface.getName()+"."+method, rv,o); + } else if (rv instanceof Map) { + if (o instanceof Map) { + Map a = (Map) o; + Map b = (Map) rv; + if (a.keySet().size() != b.keySet().size()) { + fail(iface.getName()+"."+method, msg); + } else for (Object k: a.keySet()) + if (a.get(k) instanceof List) { + if (b.get(k) instanceof List) + if (setCompareLists((List) a.get(k), (List) b.get(k))) + ; + else + fail(iface.getName()+"."+method, msg); + else + fail(iface.getName()+"."+method, msg); + } else if (!a.get(k).equals(b.get(k))) { + fail(iface.getName()+"."+method, msg); + return; + } + pass(iface.getName()+"."+method); + } else + fail(iface.getName()+"."+method, msg); + } else { + if (o == rv || (o != null && o.equals(rv))) + pass(iface.getName()+"."+method); + else + fail(iface.getName()+"."+method, msg); + } + } catch (DBusExecutionException DBEe) { + DBEe.printStackTrace(); + fail(iface.getName()+"."+method, "Error occurred during execution: "+DBEe.getClass().getName()+" "+DBEe.getMessage()); + } catch (InvocationTargetException ITe) { + ITe.printStackTrace(); + fail(iface.getName()+"."+method, "Error occurred during execution: "+ITe.getCause().getClass().getName()+" "+ITe.getCause().getMessage()); + } catch (Exception e) { + e.printStackTrace(); + fail(iface.getName()+"."+method, "Error occurred during execution: "+e.getClass().getName()+" "+e.getMessage()); + } + } + @SuppressWarnings("unchecked") + public static String collapseArray(Object array) + { + if (null == array) return "null"; + if (array.getClass().isArray()) { + String s = "{ "; + for (int i = 0; i < Array.getLength(array); i++) + s += collapseArray(Array.get(array, i))+","; + s = s.replaceAll(".$"," }"); + return s; + } else if (array instanceof List) { + String s = "{ "; + for (Object o: (List) array) + s += collapseArray(o)+","; + s = s.replaceAll(".$"," }"); + return s; + } else if (array instanceof Map) { + String s = "{ "; + for (Object o: ((Map) array).keySet()) + s += collapseArray(o)+" => "+collapseArray(((Map) array).get(o))+","; + s = s.replaceAll(".$"," }"); + return s; + } else return array.toString(); + } + public static boolean setCompareLists(List a, List b) + { + if (a.size() != b.size()) return false; + for (Object v: a) + if (!b.contains(v)) return false; + return true; + } + @SuppressWarnings("unchecked") + public static List> PrimitizeRecurse(Object a, Type t) + { + List> vs = new Vector>(); + if (t instanceof ParameterizedType) { + Class c = (Class) ((ParameterizedType) t).getRawType(); + if (List.class.isAssignableFrom(c)) { + Object os; + if (a instanceof List) + os = ((List) a).toArray(); + else + os = a; + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (int i = 0; i < Array.getLength(os); i++) + vs.addAll(PrimitizeRecurse(Array.get(os, i), ts[0])); + } else if (Map.class.isAssignableFrom(c)) { + Object[] os = ((Map) a).keySet().toArray(); + Object[] ks = ((Map) a).values().toArray(); + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (int i = 0; i < ks.length; i++) + vs.addAll(PrimitizeRecurse(ks[i], ts[0])); + for (int i = 0; i < os.length; i++) + vs.addAll(PrimitizeRecurse(os[i], ts[1])); + } else if (Struct.class.isAssignableFrom(c)) { + Object[] os = ((Struct) a).getParameters(); + Type[] ts = ((ParameterizedType) t).getActualTypeArguments(); + for (int i = 0; i < os.length; i++) + vs.addAll(PrimitizeRecurse(os[i], ts[i])); + + } else if (Variant.class.isAssignableFrom(c)) { + vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType())); + } + } else if (Variant.class.isAssignableFrom((Class) t)) + vs.addAll(PrimitizeRecurse(((Variant) a).getValue(), ((Variant) a).getType())); + else if (t instanceof Class && ((Class) t).isArray()) { + Type t2 = ((Class) t).getComponentType(); + for (int i = 0; i < Array.getLength(a); i++) + vs.addAll(PrimitizeRecurse(Array.get(a, i), t2)); + } + else vs.add(new Variant(a)); + + return vs; + } + + @SuppressWarnings("unchecked") + public static List> Primitize(Variant a) + { + return PrimitizeRecurse(a.getValue(), a.getType()); + } + + @SuppressWarnings("unchecked") + public static void primitizeTest(DBus.Binding.Tests tests, Object input) + { + Variant in = new Variant(input); + List> vs = Primitize(in); + List> res; + + try { + + res = tests.Primitize(in); + if (setCompareLists(res, vs)) + pass("org.freedesktop.DBus.Binding.Tests.Primitize"); + else + fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Wrong Return Value; expected "+collapseArray(vs)+" got "+collapseArray(res)); + + } catch (Exception e) { + if (Debug.debug) Debug.print(e); + fail("org.freedesktop.DBus.Binding.Tests.Primitize", "Exception occurred during test: ("+e.getClass().getName()+") "+e.getMessage()); + } + } + public static void doTests(DBus.Peer peer, DBus.Introspectable intro, DBus.Introspectable rootintro, DBus.Binding.Tests tests, DBus.Binding.SingleTests singletests) + { + Random r = new Random(); + int i; + test(DBus.Peer.class, peer, "Ping", null); + + try { if (intro.Introspect().startsWith("(new Integer(1)), new Variant(new Integer(1))); + test(DBus.Binding.Tests.class, tests, "Identity", new Variant("Hello"), new Variant("Hello")); + + test(DBus.Binding.Tests.class, tests, "IdentityBool", false, false); + test(DBus.Binding.Tests.class, tests, "IdentityBool", true, true); + + test(DBus.Binding.Tests.class, tests, "Invert", false, true); + test(DBus.Binding.Tests.class, tests, "Invert", true, false); + + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 0, (byte) 0); + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) 1, (byte) 1); + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) -1, (byte) -1); + test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MAX_VALUE, Byte.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityByte", Byte.MIN_VALUE, Byte.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityByte", (byte) i, (byte) i); + + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 0, (short) 0); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) 1, (short) 1); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) -1, (short) -1); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MAX_VALUE, Short.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", Short.MIN_VALUE, Short.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityInt16", (short) i, (short) i); + + test(DBus.Binding.Tests.class, tests, "IdentityInt32", 0, 0); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", 1, 1); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", -1, -1); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MAX_VALUE, Integer.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", Integer.MIN_VALUE, Integer.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityInt32", i, i); + + + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 0, (long) 0); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) 1, (long) 1); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) -1, (long) -1); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MAX_VALUE, Long.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", Long.MIN_VALUE, Long.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityInt64", (long) i, (long) i); + + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(0), new UInt16(0)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(1), new UInt16(1)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MAX_VALUE), new UInt16(UInt16.MAX_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(UInt16.MIN_VALUE), new UInt16(UInt16.MIN_VALUE)); + i = r.nextInt(); + i = i > 0 ? i : -i; + test(DBus.Binding.Tests.class, tests, "IdentityUInt16", new UInt16(i%UInt16.MAX_VALUE), new UInt16(i%UInt16.MAX_VALUE)); + + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(0), new UInt32(0)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(1), new UInt32(1)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MAX_VALUE), new UInt32(UInt32.MAX_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(UInt32.MIN_VALUE), new UInt32(UInt32.MIN_VALUE)); + i = r.nextInt(); + i = i > 0 ? i : -i; + test(DBus.Binding.Tests.class, tests, "IdentityUInt32", new UInt32(i%UInt32.MAX_VALUE), new UInt32(i%UInt32.MAX_VALUE)); + + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(0), new UInt64(0)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(1), new UInt64(1)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_LONG_VALUE), new UInt64(UInt64.MAX_LONG_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MAX_BIG_VALUE), new UInt64(UInt64.MAX_BIG_VALUE)); + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(UInt64.MIN_VALUE), new UInt64(UInt64.MIN_VALUE)); + i = r.nextInt(); + i = i > 0 ? i : -i; + test(DBus.Binding.Tests.class, tests, "IdentityUInt64", new UInt64(i%UInt64.MAX_LONG_VALUE), new UInt64(i%UInt64.MAX_LONG_VALUE)); + + test(DBus.Binding.Tests.class, tests, "IdentityDouble", 0.0, 0.0); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", 1.0, 1.0); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", -1.0, -1.0); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MAX_VALUE, Double.MAX_VALUE); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", Double.MIN_VALUE, Double.MIN_VALUE); + i = r.nextInt(); + test(DBus.Binding.Tests.class, tests, "IdentityDouble", (double) i, (double) i); + + test(DBus.Binding.Tests.class, tests, "IdentityString", "", ""); + test(DBus.Binding.Tests.class, tests, "IdentityString", "The Quick Brown Fox Jumped Over The Lazy Dog", "The Quick Brown Fox Jumped Over The Lazy Dog"); + test(DBus.Binding.Tests.class, tests, "IdentityString", "ひらがなゲーム - かなぶん", "ひらがなゲーム - かなぶん"); + + testArray(DBus.Binding.Tests.class, tests, "IdentityBoolArray", Boolean.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityByteArray", Byte.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityInt16Array", Short.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityInt32Array", Integer.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityInt64Array", Long.TYPE, null); + testArray(DBus.Binding.Tests.class, tests, "IdentityDoubleArray", Double.TYPE, null); + + testArray(DBus.Binding.Tests.class, tests, "IdentityArray", Variant.class, new Variant("aoeu")); + testArray(DBus.Binding.Tests.class, tests, "IdentityUInt16Array", UInt16.class, new UInt16(12)); + testArray(DBus.Binding.Tests.class, tests, "IdentityUInt32Array", UInt32.class, new UInt32(190)); + testArray(DBus.Binding.Tests.class, tests, "IdentityUInt64Array", UInt64.class, new UInt64(103948)); + testArray(DBus.Binding.Tests.class, tests, "IdentityStringArray", String.class, "asdf"); + + int[] is = new int[0]; + test(DBus.Binding.Tests.class, tests, "Sum", 0L, is); + r = new Random(); + int len = (r.nextInt() % 100) + 15; + len = (len<0 ? -len: len)+15; + is = new int[len]; + long result = 0; + for (i = 0; i < len; i++) { + is[i] = r.nextInt(); + result += is[i]; + } + test(DBus.Binding.Tests.class, tests, "Sum", result, is); + + byte[] bs = new byte[0]; + test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(0), bs); + len = (r.nextInt() % 100); + len = (len<0 ? -len: len)+15; + bs = new byte[len]; + int res = 0; + for (i = 0; i < len; i++) { + bs[i] = (byte) r.nextInt(); + res += (bs[i] < 0 ? bs[i]+256 : bs[i]); + } + test(DBus.Binding.SingleTests.class, singletests, "Sum", new UInt32(res % (UInt32.MAX_VALUE+1)), bs); + + test(DBus.Binding.Tests.class, tests, "DeStruct", new DBus.Binding.Triplet("hi", new UInt32(12), new Short((short) 99)), new DBus.Binding.TestStruct("hi", new UInt32(12), new Short((short) 99))); + + Map in = new HashMap(); + Map> out = new HashMap>(); + test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in); + + in.put("hi", "there"); + in.put("to", "there"); + in.put("from", "here"); + in.put("in", "out"); + List l = new Vector(); + l.add("hi"); + l.add("to"); + out.put("there", l); + l = new Vector(); + l.add("from"); + out.put("here", l); + l = new Vector(); + l.add("in"); + out.put("out", l); + test(DBus.Binding.Tests.class, tests, "InvertMapping", out, in); + + primitizeTest(tests, new Integer(1)); + primitizeTest(tests, + new Variant>>>( + new Variant>>( + new Variant>( + new Variant("Hi"))))); + primitizeTest(tests, new Variant>(in, new DBusMapType(String.class,String.class))); + + test(DBus.Binding.Tests.class, tests, "Trigger", null, "/Test", new UInt64(21389479283L)); + + try { + ctc.conn.sendSignal(new DBus.Binding.TestClient.Trigger("/Test", new UInt16(15), 12.5)); + } catch (DBusException DBe) { + if (Debug.debug) Debug.print(DBe); + throw new DBusExecutionException(DBe.getMessage()); + } + + try { Thread.sleep(10000); } catch (InterruptedException Ie) {} + + test(DBus.Binding.Tests.class, tests, "Exit", null); + } + public static void testArray(Class iface, Object proxy, String method, Class arrayType, Object content) + { + Object array = Array.newInstance(arrayType, 0); + test(iface, proxy, method, array, array); + Random r = new Random(); + int l = (r.nextInt() % 100); + array = Array.newInstance(arrayType, (l < 0 ? -l : l) + 15); + if (null != content) + Arrays.fill((Object[]) array, content); + test(iface, proxy, method, array, array); + } + public static void compareArray(String test, Object a, Object b) + { + if (!a.getClass().equals(b.getClass())) { + fail(test, "Incorrect return type; expected "+a.getClass()+" got "+b.getClass()); + return; + } + boolean pass = false; + + if (a instanceof Object[]) + pass = Arrays.equals((Object[]) a, (Object[]) b); + else if (a instanceof byte[]) + pass = Arrays.equals((byte[]) a, (byte[]) b); + else if (a instanceof boolean[]) + pass = Arrays.equals((boolean[]) a, (boolean[]) b); + else if (a instanceof int[]) + pass = Arrays.equals((int[]) a, (int[]) b); + else if (a instanceof short[]) + pass = Arrays.equals((short[]) a, (short[]) b); + else if (a instanceof long[]) + pass = Arrays.equals((long[]) a, (long[]) b); + else if (a instanceof double[]) + pass = Arrays.equals((double[]) a, (double[]) b); + + if (pass) + pass(test); + else { + String s = "Incorrect return value; expected "; + s += collapseArray(a); + s += " got "; + s += collapseArray(b); + fail(test, s); + } + } + + public static void main(String[] args) + { try { + /* init */ + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + ctc = new cross_test_client(conn); + conn.exportObject("/Test", ctc); + conn.addSigHandler(DBus.Binding.TestSignals.Triggered.class, ctc); + DBus.Binding.Tests tests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.Tests.class); + DBus.Binding.SingleTests singletests = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Binding.SingleTests.class); + DBus.Peer peer = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Peer.class); + DBus.Introspectable intro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/Test", DBus.Introspectable.class); + + DBus.Introspectable rootintro = conn.getRemoteObject("org.freedesktop.DBus.Binding.TestServer", "/", DBus.Introspectable.class); + + doTests(peer, intro, rootintro, tests, singletests); + + /* report results */ + for (String s: passed) + System.out.println(s+" pass"); + int i = 1; + for (String s: failed.keySet()) + for (String r: failed.get(s)) { + System.out.println(s+" fail "+i); + System.out.println("report "+i+": "+r); + i++; + } + + conn.disconnect(); + } catch (DBusException DBe) { + DBe.printStackTrace(); + System.exit(1); + }} +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java b/app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java new file mode 100644 index 00000000..acfb2fa9 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/cross_test_server.java @@ -0,0 +1,344 @@ +/* + 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.test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeSet; +import java.util.Set; +import java.util.Vector; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.UInt64; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; + +public class cross_test_server implements DBus.Binding.Tests, DBus.Binding.SingleTests, DBusSigHandler +{ + private DBusConnection conn; + boolean run = true; + private Set done = new TreeSet(); + private Set notdone = new TreeSet(); + { + notdone.add("org.freedesktop.DBus.Binding.Tests.Identity"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByte"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBool"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityString"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Sum"); + notdone.add("org.freedesktop.DBus.Binding.SingleTests.Sum"); + notdone.add("org.freedesktop.DBus.Binding.Tests.InvertMapping"); + notdone.add("org.freedesktop.DBus.Binding.Tests.DeStruct"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Primitize"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Invert"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Trigger"); + notdone.add("org.freedesktop.DBus.Binding.Tests.Exit"); + notdone.add("org.freedesktop.DBus.Binding.TestClient.Trigger"); + } + + public cross_test_server(DBusConnection conn) + { + this.conn = conn; + } + public boolean isRemote() { return false; } + @SuppressWarnings("unchecked") + @DBus.Description("Returns whatever it is passed") + public Variant Identity(Variant input) + { + done.add("org.freedesktop.DBus.Binding.Tests.Identity"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Identity"); + return new Variant(input.getValue()); + } + @DBus.Description("Returns whatever it is passed") + public byte IdentityByte(byte input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityByte"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByte"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public boolean IdentityBool(boolean input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityBool"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBool"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public short IdentityInt16(short input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt16 IdentityUInt16(UInt16 input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public int IdentityInt32(int input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt32 IdentityUInt32(UInt32 input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public long IdentityInt64(long input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt64 IdentityUInt64(UInt64 input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public double IdentityDouble(double input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDouble"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public String IdentityString(String input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityString"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityString"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public Variant[] IdentityArray(Variant[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityArray"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public byte[] IdentityByteArray(byte[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityByteArray"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public boolean[] IdentityBoolArray(boolean[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityBoolArray"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public short[] IdentityInt16Array(short[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt16Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt16[] IdentityUInt16Array(UInt16[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt16Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public int[] IdentityInt32Array(int[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt32Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt32[] IdentityUInt32Array(UInt32[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt32Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public long[] IdentityInt64Array(long[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityInt64Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public UInt64[] IdentityUInt64Array(UInt64[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityUInt64Array"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public double[] IdentityDoubleArray(double[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityDoubleArray"); + return input; + } + @DBus.Description("Returns whatever it is passed") + public String[] IdentityStringArray(String[] input) + { + done.add("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.IdentityStringArray"); + return input; + } + @DBus.Description("Returns the sum of the values in the input list") + public long Sum(int[] a) + { + done.add("org.freedesktop.DBus.Binding.Tests.Sum"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Sum"); + long sum = 0; + for (int b: a) sum += b; + return sum; + } + @DBus.Description("Returns the sum of the values in the input list") + public UInt32 Sum(byte[] a) + { + done.add("org.freedesktop.DBus.Binding.SingleTests.Sum"); + notdone.remove("org.freedesktop.DBus.Binding.SingleTests.Sum"); + int sum = 0; + for (byte b: a) sum += (b < 0 ? b+256 : b); + return new UInt32(sum % (UInt32.MAX_VALUE+1)); + } + @DBus.Description("Given a map of A => B, should return a map of B => a list of all the As which mapped to B") + public Map> InvertMapping(Map a) + { + done.add("org.freedesktop.DBus.Binding.Tests.InvertMapping"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.InvertMapping"); + HashMap> m = new HashMap>(); + for (String s: a.keySet()) { + String b = a.get(s); + List l = m.get(b); + if (null == l) { + l = new Vector(); + m.put(b, l); + } + l.add(s); + } + return m; + } + @DBus.Description("This method returns the contents of a struct as separate values") + public DBus.Binding.Triplet DeStruct(DBus.Binding.TestStruct a) + { + done.add("org.freedesktop.DBus.Binding.Tests.DeStruct"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.DeStruct"); + return new DBus.Binding.Triplet(a.a, a.b, a.c); + } + @DBus.Description("Given any compound type as a variant, return all the primitive types recursively contained within as an array of variants") + @SuppressWarnings("unchecked") + public List> Primitize(Variant a) + { + done.add("org.freedesktop.DBus.Binding.Tests.Primitize"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Primitize"); + return cross_test_client.PrimitizeRecurse(a.getValue(), a.getType()); + } + @DBus.Description("inverts it's input") + public boolean Invert(boolean a) + { + done.add("org.freedesktop.DBus.Binding.Tests.Invert"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Invert"); + return !a; + } + @DBus.Description("triggers sending of a signal from the supplied object with the given parameter") + public void Trigger(String a, UInt64 b) + { + done.add("org.freedesktop.DBus.Binding.Tests.Trigger"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Trigger"); + try { + conn.sendSignal(new DBus.Binding.TestSignals.Triggered(a, b)); + } catch (DBusException DBe) { + throw new DBusExecutionException(DBe.getMessage()); + } + } + public void Exit() + { + done.add("org.freedesktop.DBus.Binding.Tests.Exit"); + notdone.remove("org.freedesktop.DBus.Binding.Tests.Exit"); + run = false; + synchronized (this) { + notifyAll(); + } + } + public void handle(DBus.Binding.TestClient.Trigger t) + { + done.add("org.freedesktop.DBus.Binding.TestClient.Trigger"); + notdone.remove("org.freedesktop.DBus.Binding.TestClient.Trigger"); + try { + DBus.Binding.TestClient cb = conn.getRemoteObject(t.getSource(), "/Test", DBus.Binding.TestClient.class); + cb.Response(t.a, t.b); + } catch (DBusException DBe) { + throw new DBusExecutionException(DBe.getMessage()); + } + } + + public static void main(String[] args) + { try { + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + conn.requestBusName("org.freedesktop.DBus.Binding.TestServer"); + cross_test_server cts = new cross_test_server(conn); + conn.addSigHandler(DBus.Binding.TestClient.Trigger.class, cts); + conn.exportObject("/Test", cts); + synchronized (cts) { + while (cts.run) { + try { + cts.wait(); + } catch (InterruptedException Ie) {} + } + } + for (String s: cts.done) + System.out.println(s+" ok"); + for (String s: cts.notdone) + System.out.println(s+" untested"); + conn.disconnect(); + System.exit(0); + } catch (DBusException DBe) { + DBe.printStackTrace(); + System.exit(1); + }} +} + diff --git a/app/src/main/java/org/freedesktop/dbus/test/profile.java b/app/src/main/java/org/freedesktop/dbus/test/profile.java new file mode 100644 index 00000000..f998fa2c --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/profile.java @@ -0,0 +1,381 @@ +/* + 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.test; + +import java.util.Random; +import java.util.HashMap; +import java.util.Vector; + +import org.freedesktop.DBus.Peer; +import org.freedesktop.DBus.Introspectable; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.UInt32; + +class ProfileHandler implements DBusSigHandler +{ + public int c = 0; + public void handle(Profiler.ProfileSignal s) + { + if (0 == (c++%profile.SIGNAL_INNER)) System.out.print("-"); + } +} + +/** + * Profiling tests. + */ +public class profile +{ + public static final int SIGNAL_INNER = 100; + public static final int SIGNAL_OUTER = 100; + public static final int PING_INNER = 100; + public static final int PING_OUTER = 100; + public static final int BYTES = 2000000; + public static final int INTROSPECTION_OUTER = 100; + public static final int INTROSPECTION_INNER = 10; + public static final int STRUCT_OUTER = 100; + public static final int STRUCT_INNER = 10; + public static final int LIST_OUTER = 100; + public static final int LIST_INNER = 10; + public static final int LIST_LENGTH = 100; + public static final int MAP_OUTER = 100; + public static final int MAP_INNER = 10; + public static final int MAP_LENGTH = 100; + public static final int ARRAY_OUTER = 100; + public static final int ARRAY_INNER = 10; + public static final int ARRAY_LENGTH = 1000; + public static final int STRING_ARRAY_OUTER = 10; + public static final int STRING_ARRAY_INNER = 1; + public static final int STRING_ARRAY_LENGTH = 20000; + + public static class Log + { + private long last; + private int[] deltas; + private int current = 0; + public Log(int size) + { + deltas = new int[size]; + } + public void start() + { + last = System.currentTimeMillis(); + } + public void stop() + { + deltas[current] = (int) (System.currentTimeMillis()-last); + current++; + } + public double mean() + { + if (0 == current) return 0; + long sum = 0; + for (int i = 0; i < current; i++) + sum+=deltas[i]; + return sum /= current; + } + public long min() + { + int m = Integer.MAX_VALUE; + for (int i = 0; i < current; i++) + if (deltas[i] < m) m = deltas[i]; + return m; + } + public long max() + { + int m = 0; + for (int i = 0; i < current; i++) + if (deltas[i] > m) m = deltas[i]; + return m; + } + public double stddev() + { + double mean = mean(); + double sum = 0; + for (int i=0; i < current; i++) + sum += (deltas[i]-mean)*(deltas[i]-mean); + return Math.sqrt(sum / (current-1)); + } + } + public static void main(String[] args) + { + try { + if (0==args.length) { + System.out.println("You must specify a profile type."); + System.out.println("Syntax: profile "); + System.exit(1); + } + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + conn.requestBusName("org.freedesktop.DBus.java.profiler"); + if ("pings".equals(args[0])) { + int count = PING_INNER*PING_OUTER; + System.out.print("Sending "+count+" pings..."); + Peer p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < PING_OUTER; i++) { + for (int j = 0; j < PING_INNER; j++) { + l.start(); + p.Ping(); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("strings".equals(args[0])) { + int count = STRING_ARRAY_INNER*STRING_ARRAY_OUTER; + System.out.print("Sending array of "+STRING_ARRAY_LENGTH+" strings "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + String[] v = new String[STRING_ARRAY_LENGTH]; + Random r = new Random(); + for (int i = 0; i < STRING_ARRAY_LENGTH; i++) v[i] = ""+r.nextInt(); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < STRING_ARRAY_OUTER; i++) { + for (int j = 0; j < STRING_ARRAY_INNER; j++) { + l.start(); + p.stringarray(v); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("arrays".equals(args[0])) { + int count = ARRAY_INNER*ARRAY_OUTER; + System.out.print("Sending array of "+ARRAY_LENGTH+" ints "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + int[] v = new int[ARRAY_LENGTH]; + Random r = new Random(); + for (int i = 0; i < ARRAY_LENGTH; i++) v[i] = r.nextInt(); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < ARRAY_OUTER; i++) { + for (int j = 0; j < ARRAY_INNER; j++) { + l.start(); + p.array(v); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("maps".equals(args[0])) { + int count = MAP_INNER*MAP_OUTER; + System.out.print("Sending map of "+MAP_LENGTH+" string=>strings "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + HashMap m = new HashMap(); + for (int i = 0; i < MAP_LENGTH; i++) + m.put(""+i, "hello"); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < MAP_OUTER; i++) { + for (int j=0; j < MAP_INNER; j++) { + l.start(); + p.map(m); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("lists".equals(args[0])) { + int count = LIST_OUTER*LIST_INNER; + System.out.print("Sending list of "+LIST_LENGTH+" strings "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + Vector v = new Vector(); + for (int i = 0; i < LIST_LENGTH; i++) + v.add("hello "+i); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < LIST_OUTER; i++) { + for (int j=0; j < LIST_INNER; j++) { + l.start(); + p.list(v); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("structs".equals(args[0])) { + int count = STRUCT_OUTER*STRUCT_INNER; + System.out.print("Sending a struct "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + ProfileStruct ps = new ProfileStruct("hello", new UInt32(18), 500L); + Log l = new Log(count); + long t = System.currentTimeMillis(); + for (int i = 0; i < STRUCT_OUTER; i++) { + for (int j=0; j < STRUCT_INNER; j++) { + l.start(); + p.struct(ps); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + } else if ("introspect".equals(args[0])) { + int count = INTROSPECTION_OUTER*INTROSPECTION_INNER; + System.out.print("Recieving introspection data "+count+" times."); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Introspectable is = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Introspectable.class); + Log l = new Log(count); + long t = System.currentTimeMillis(); + String s = null; + for (int i = 0; i < INTROSPECTION_OUTER; i++) { + for (int j = 0; j < INTROSPECTION_INNER; j++) { + l.start(); + s = is.Introspect(); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + System.out.println("Introspect data: "+s); + } else if ("bytes".equals(args[0])) { + System.out.print("Sending "+BYTES+" bytes"); + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + byte[] bs = new byte[BYTES]; + for (int i = 0; i < BYTES; i++) + bs[i] = (byte) i; + long t = System.currentTimeMillis(); + p.bytes(bs); + System.out.println(" done in "+(System.currentTimeMillis()-t)+"ms."); + } else if ("rate".equals(args[0])) { + ProfilerInstance pi = new ProfilerInstance(); + conn.exportObject("/Profiler", pi); + Profiler p = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Profiler.class); + Peer peer = conn.getRemoteObject("org.freedesktop.DBus.java.profiler", "/Profiler", Peer.class); + conn.changeThreadCount((byte)1); + + long start = System.currentTimeMillis(); + int count = 0; + do { + p.Pong(); + count++; + } while(count < 10000); + long end = System.currentTimeMillis(); + System.out.println("No payload: "+((count*1000)/(end-start))+" RT/second"); + start = System.currentTimeMillis(); + count = 0; + do { + p.Pong(); + count++; + } while(count < 10000); + peer.Ping(); + end = System.currentTimeMillis(); + System.out.println("No payload, One way: "+((count*1000)/(end-start))+" /second"); + int len = 256; + while (len <= 32768) { + byte[] bs = new byte[len]; + count = 0; + start = System.currentTimeMillis(); + do { + p.bytes(bs); + count++; + } while(count < 1000); + end = System.currentTimeMillis(); + long ms = end-start; + double cps = (count*1000)/ms; + double rate = (len*cps)/(1024.0*1024.0); + System.out.println(len+" byte array) "+(count*len)+" bytes in "+ms+"ms (in "+count+" calls / "+(int)cps+" CPS): "+rate+"MB/s"); + len <<= 1; + } + len = 256; + while (len <= 32768) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < len; i++) sb.append('a'); + String s = sb.toString(); + end = System.currentTimeMillis()+500; + count = 0; + do { + p.string(s); + count++; + } while(count < 1000); + long ms = end-start; + double cps = (count*1000)/ms; + double rate = (len*cps)/(1024.0*1024.0); + System.out.println(len+" string) "+(count*len)+" bytes in "+ms+"ms (in "+count+" calls / "+(int)cps+" CPS): "+rate+"MB/s"); + len <<= 1; + } + } else if ("signals".equals(args[0])) { + int count = SIGNAL_OUTER*SIGNAL_INNER; + System.out.print("Sending "+count+" signals"); + ProfileHandler ph = new ProfileHandler(); + conn.addSigHandler(Profiler.ProfileSignal.class, ph); + Log l = new Log(count); + Profiler.ProfileSignal ps = new Profiler.ProfileSignal("/"); + long t = System.currentTimeMillis(); + for (int i = 0; i < SIGNAL_OUTER; i++) { + for (int j = 0; j < SIGNAL_INNER; j++) { + l.start(); + conn.sendSignal(ps); + l.stop(); + } + System.out.print("."); + } + t = System.currentTimeMillis()-t; + System.out.println(" done."); + System.out.println("min/max/avg (ms): "+l.min()+"/"+l.max()+"/"+l.mean()); + System.out.println("deviation: "+l.stddev()); + System.out.println("Total time: "+t+"ms"); + while (ph.c < count) try { Thread.sleep(100); } + catch (InterruptedException Ie) {}; + } else { + conn.disconnect(); + System.out.println("Invalid profile ``"+args[0]+"''."); + System.out.println("Syntax: profile "); + System.exit(1); + } + conn.disconnect(); + } catch (Exception e) { + e.printStackTrace(); + System.exit(1); + } + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test.java b/app/src/main/java/org/freedesktop/dbus/test/test.java new file mode 100644 index 00000000..4ccd696e --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/test.java @@ -0,0 +1,965 @@ +/* + 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.test; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Vector; + +import java.text.Collator; + +import org.freedesktop.dbus.CallbackHandler; +import org.freedesktop.dbus.DBusAsyncReply; +import org.freedesktop.dbus.DBusCallInfo; +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusInterface; +import org.freedesktop.dbus.DBusSigHandler; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.Marshalling; +import org.freedesktop.dbus.Path; +import org.freedesktop.dbus.UInt16; +import org.freedesktop.dbus.UInt32; +import org.freedesktop.dbus.UInt64; +import org.freedesktop.dbus.Variant; +import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.exceptions.DBusExecutionException; +import org.freedesktop.dbus.exceptions.NotConnected; + +import org.freedesktop.DBus; +import org.freedesktop.DBus.Error.MatchRuleInvalid; +import org.freedesktop.DBus.Error.ServiceUnknown; +import org.freedesktop.DBus.Error.UnknownObject; +import org.freedesktop.DBus.Peer; +import org.freedesktop.DBus.Introspectable; +import org.freedesktop.DBus.Properties; + +class testnewclass implements TestNewInterface +{ + public boolean isRemote() { return false; } + public String getName() + { + return toString(); + } +} + +class testclass implements TestRemoteInterface, TestRemoteInterface2, TestSignalInterface, TestSignalInterface2, Properties +{ + private DBusConnection conn; + public testclass(DBusConnection conn) + { + this.conn = conn; + } + public String Introspect() + { + return "Not XML"; + } + public int[][] teststructstruct(TestStruct3 in) + { + List> lli = in.b; + int[][] out = new int[lli.size()][]; + for (int j = 0; j < out.length; j++) { + out[j] = new int[lli.get(j).size()]; + for (int k = 0; k < out[j].length; k++) + out[j][k] = lli.get(j).get(k); + } + return out; + } + public float testfloat(float[] f) + { + if (f.length < 4 || + f[0] != 17.093f || + f[1] != -23f || + f[2] != 0.0f || + f[3] != 31.42f) + test.fail("testfloat got incorrect array"); + return f[0]; + } + public void newpathtest(Path p) + { + if (!p.toString().equals("/new/path/test")) + test.fail("new path test got wrong path"); + } + public void waitawhile() + { + System.out.println("Sleeping."); + try { + Thread.sleep(1000); + } catch (InterruptedException Ie) {} + System.out.println("Done sleeping."); + } + public TestTuple, Boolean> show(A in) + { + System.out.println("Showing Stuff: "+in.getClass()+"("+in+")"); + if (!(in instanceof Integer) || ((Integer) in).intValue() != 234) + test.fail("show received the wrong arguments"); + DBusCallInfo info = DBusConnection.getCallInfo(); + List l = new Vector(); + l.add(1953); + return new TestTuple, Boolean>(info.getSource(), l, true); + } + @SuppressWarnings("unchecked") + public T dostuff(TestStruct foo) + { + System.out.println("Doing Stuff "+foo); + System.out.println(" -- ("+foo.a.getClass()+", "+foo.b.getClass()+", "+foo.c.getClass()+")"); + if (!(foo instanceof TestStruct) || + !(foo.a instanceof String) || + !(foo.b instanceof UInt32) || + !(foo.c instanceof Variant) || + !"bar".equals(foo.a) || + foo.b.intValue() != 52 || + !(foo.c.getValue() instanceof Boolean) || + ((Boolean) foo.c.getValue()).booleanValue() != true) + test.fail("dostuff received the wrong arguments"); + return (T) foo.c.getValue(); + } + /** Local classes MUST implement this to return false */ + public boolean isRemote() { return false; } + /** The method we are exporting to the Bus. */ + public List sampleArray(List ss, Integer[] is, long[] ls) + { + System.out.println("Got an array:"); + for (String s: ss) + System.out.println("--"+s); + if (ss.size()!= 5 || + !"hi".equals(ss.get(0)) || + !"hello".equals(ss.get(1)) || + !"hej".equals(ss.get(2)) || + !"hey".equals(ss.get(3)) || + !"aloha".equals(ss.get(4))) + test.fail("sampleArray, String array contents incorrect"); + System.out.println("Got an array:"); + for (Integer i: is) + System.out.println("--"+i); + if (is.length != 4 || + is[0].intValue() != 1 || + is[1].intValue() != 5 || + is[2].intValue() != 7 || + is[3].intValue() != 9) + test.fail("sampleArray, Integer array contents incorrect"); + System.out.println("Got an array:"); + for (long l: ls) + System.out.println("--"+l); + if (ls.length != 4 || + ls[0] != 2 || + ls[1] != 6 || + ls[2] != 8 || + ls[3] != 12) + test.fail("sampleArray, Integer array contents incorrect"); + Vector v = new Vector(); + v.add(-1); + v.add(-5); + v.add(-7); + v.add(-12); + v.add(-18); + return v; + } + public String getName() + { + return "This Is A UTF-8 Name: س !!"; + } + public String getNameAndThrow() throws TestException + { + throw new TestException("test"); + } + public boolean check() + { + System.out.println("Being checked"); + return false; + } + public int frobnicate(List n, Map> m, T v) + { + if (null == n) + test.fail("List was null"); + if (n.size() != 3) + test.fail("List was wrong size (expected 3, actual "+n.size()+")"); + if (n.get(0) != 2L || + n.get(1) != 5L || + n.get(2) != 71L) + test.fail("List has wrong contents"); + if (!(v instanceof Integer)) + test.fail("v not an Integer"); + if (((Integer) v) != 13) + test.fail("v is incorrect"); + if (null == m) + test.fail("Map was null"); + if (m.size() != 1) + test.fail("Map was wrong size"); + if (!m.keySet().contains("stuff")) + test.fail("Incorrect key"); + Map mus = m.get("stuff"); + if (null == mus) + test.fail("Sub-Map was null"); + if (mus.size() != 3) + test.fail("Sub-Map was wrong size"); + if (!(new Short((short)5).equals(mus.get(new UInt16(4))))) + test.fail("Sub-Map has wrong contents"); + if (!(new Short((short)6).equals(mus.get(new UInt16(5))))) + test.fail("Sub-Map has wrong contents"); + if (!(new Short((short)7).equals(mus.get(new UInt16(6))))) + test.fail("Sub-Map has wrong contents"); + return -5; + } + public DBusInterface getThis(DBusInterface t) + { + if (!t.equals(this)) + test.fail("Didn't get this properly"); + return this; + } + public void throwme() throws TestException + { + throw new TestException("test"); + } + public TestSerializable testSerializable(byte b, TestSerializable s, int i) + { + System.out.println("Recieving TestSerializable: "+s); + if ( b != 12 + || i != 13 + || !(s.getInt() == 1) + || !(s.getString().equals("woo")) + || !(s.getVector().size() == 3) + || !(s.getVector().get(0) == 1) + || !(s.getVector().get(1) == 2) + || !(s.getVector().get(2) == 3) ) + test.fail("Error in recieving custom synchronisation"); + return s; + } + public String recursionTest() + { + try { + TestRemoteInterface tri = conn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class); + return tri.getName(); + } catch (DBusException DBe) { + test.fail("Failed with error: "+DBe); + return ""; + } + } + public int overload(String s) + { + return 1; + } + public int overload(byte b) + { + return 2; + } + public int overload() + { + DBusCallInfo info = DBusConnection.getCallInfo(); + if ("org.freedesktop.dbus.test.AlternateTestInterface".equals(info.getInterface())) + return 3; + else if ("org.freedesktop.dbus.test.TestRemoteInterface".equals(info.getInterface())) + return 4; + else + return -1; + } + public List> checklist(List> lli) + { + return lli; + } + public TestNewInterface getNew() + { + testnewclass n = new testnewclass(); + try { + conn.exportObject("/new", n); + } catch (DBusException DBe) + { throw new DBusExecutionException(DBe.getMessage()); } + return n; + } + public void sig(Type[] s) + { + if (s.length != 2 + || !s[0].equals(Byte.class) + || ! (s[1] instanceof ParameterizedType) + || ! Map.class.equals(((ParameterizedType) s[1]).getRawType()) + || ((ParameterizedType) s[1]).getActualTypeArguments().length != 2 + || ! String.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[0]) + || ! Integer.class.equals(((ParameterizedType) s[1]).getActualTypeArguments()[1])) + test.fail("Didn't send types correctly"); + } + @SuppressWarnings("unchecked") + public void complexv(Variant v) + { + if (!"a{ss}".equals(v.getSig()) + || ! (v.getValue() instanceof Map) + || ((Map) v.getValue()).size() != 1 + || !"moo".equals(((Map) v.getValue()).get("cow"))) + test.fail("Didn't send variant correctly"); + } + public void reg13291(byte[] as, byte[] bs) + { + if (as.length != bs.length) test.fail("didn't receive identical byte arrays"); + for (int i = 0; i < as.length; i++) + if (as[i] != bs[i]) test.fail("didn't receive identical byte arrays"); + } + @SuppressWarnings("unchecked") + public A Get (String interface_name, String property_name) + { + return (A) new Path("/nonexistant/path"); + } + public void Set (String interface_name, String property_name, A value) {} + public Map GetAll (String interface_name) { return new HashMap(); } + public Path pathrv(Path a) { return a; } + public List pathlistrv(List a) { return a; } + public Map pathmaprv(Map a) { return a; } +} + +/** + * Typed signal handler for renamed signal + */ +class renamedsignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface2.TestRenamedSignal t) + { + if (false == test.done5) { + test.done5 = true; + } else { + test.fail("SignalHandler R has been run too many times"); + } + System.out.println("SignalHandler R Running"); + System.out.println("string("+t.value+") int("+t.number+")"); + if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number)) + test.fail("Incorrect TestRenamedSignal parameters"); + } +} + +/** + * Empty signal handler + */ +class emptysignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface.EmptySignal t) + { + if (false == test.done7) { + test.done7 = true; + } else { + test.fail("SignalHandler E has been run too many times"); + } + System.out.println("SignalHandler E Running"); + } +} +/** + * Disconnect handler + */ +class disconnecthandler implements DBusSigHandler +{ + private DBusConnection conn; + private renamedsignalhandler sh; + public disconnecthandler(DBusConnection conn, renamedsignalhandler sh) + { + this.conn = conn; + this.sh = sh; + } + /** Handling a signal */ + public void handle(DBus.Local.Disconnected t) + { + if (false == test.done6) { + test.done6 = true; + System.out.println("Handling disconnect, unregistering handler"); + try { + conn.removeSigHandler(TestSignalInterface2.TestRenamedSignal.class, sh); + } catch (DBusException DBe) { + DBe.printStackTrace(); + test.fail("Disconnect handler threw an exception: "+DBe); + } + } + } +} + + +/** + * Typed signal handler + */ +class pathsignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface.TestPathSignal t) + { + System.out.println("Path sighandler: "+t); + } +} + +/** + * Typed signal handler + */ +class signalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface.TestSignal t) + { + if (false == test.done1) { + test.done1 = true; + } else { + test.fail("SignalHandler 1 has been run too many times"); + } + System.out.println("SignalHandler 1 Running"); + System.out.println("string("+t.value+") int("+t.number+")"); + if (!"Bar".equals(t.value) || !(new UInt32(42)).equals(t.number)) + test.fail("Incorrect TestSignal parameters"); + } +} + +/** + * Untyped signal handler + */ +class arraysignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(TestSignalInterface.TestArraySignal t) + { + try { + if (false == test.done2) { + test.done2 = true; + } else { + test.fail("SignalHandler 2 has been run too many times"); + } + System.out.println("SignalHandler 2 Running"); + if (t.v.size() != 1) test.fail("Incorrect TestArraySignal array length: should be 1, actually "+t.v.size()); + System.out.println("Got a test array signal with Parameters: "); + for (String str: t.v.get(0).a) + System.out.println("--"+str); + System.out.println(t.v.get(0).b.getType()); + System.out.println(t.v.get(0).b.getValue()); + if (!(t.v.get(0).b.getValue() instanceof UInt64) || + 567L != ((UInt64) t.v.get(0).b.getValue()).longValue() || + t.v.get(0).a.size() != 5 || + !"hi".equals(t.v.get(0).a.get(0)) || + !"hello".equals(t.v.get(0).a.get(1)) || + !"hej".equals(t.v.get(0).a.get(2)) || + !"hey".equals(t.v.get(0).a.get(3)) || + !"aloha".equals(t.v.get(0).a.get(4))) + test.fail("Incorrect TestArraySignal parameters"); + + if (t.m.keySet().size() != 2) test.fail("Incorrect TestArraySignal map size: should be 2, actually "+t.m.keySet().size()); + if (!(t.m.get(new UInt32(1)).b.getValue() instanceof UInt64) || + 678L != ((UInt64) t.m.get(new UInt32(1)).b.getValue()).longValue() || + !(t.m.get(new UInt32(42)).b.getValue() instanceof UInt64) || + 789L != ((UInt64) t.m.get(new UInt32(42)).b.getValue()).longValue()) + test.fail("Incorrect TestArraySignal parameters"); + + } catch (Exception e) { + e.printStackTrace(); + test.fail("SignalHandler 2 threw an exception: "+e); + } + } +} + +/** + * Object path signal handler + */ +class objectsignalhandler implements DBusSigHandler +{ + public void handle(TestSignalInterface.TestObjectSignal s) + { + if (false == test.done3) { + test.done3 = true; + } else { + test.fail("SignalHandler 3 has been run too many times"); + } + System.out.println(s.otherpath); + } +} + +/** + * handler which should never be called + */ +class badarraysignalhandler implements DBusSigHandler +{ + /** Handling a signal */ + public void handle(T s) + { + test.fail("This signal handler shouldn't be called"); + } +} + +/** + * Callback handler + */ +class callbackhandler implements CallbackHandler +{ + public void handle(String r) + { + System.out.println("Handling callback: "+r); + Collator col = Collator.getInstance(); + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", r)) + test.fail("call with callback, wrong return value"); + if (test.done4) test.fail("Already ran callback handler"); + test.done4 = true; + } + public void handleError(DBusExecutionException e) + { + System.out.println("Handling error callback: "+e+" message = '"+e.getMessage()+"'"); + if (!(e instanceof TestException)) test.fail("Exception is of the wrong sort"); + Collator col = Collator.getInstance(); + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + if (0 != col.compare("test", e.getMessage())) + test.fail("Exception has the wrong message"); + if (test.done8) test.fail("Already ran callback error handler"); + test.done8=true; + } +} + +/** + * This is a test program which sends and recieves a signal, implements, exports and calls a remote method. + */ +public class test +{ + public static boolean done1 = false; + public static boolean done2 = false; + public static boolean done3 = false; + public static boolean done4 = false; + public static boolean done5 = false; + public static boolean done6 = false; + public static boolean done7 = false; + public static boolean done8 = false; + public static void fail(String message) + { + System.out.println("Test Failed: "+message); + System.err.println("Test Failed: "+message); + if (null != serverconn) serverconn.disconnect(); + if (null != clientconn) clientconn.disconnect(); + System.exit(1); + } + static DBusConnection serverconn = null; + static DBusConnection clientconn = null; + @SuppressWarnings("unchecked") + public static void main(String[] args) + { try { + System.out.println("Creating Connection"); + serverconn = DBusConnection.getConnection(DBusConnection.SESSION); + clientconn = DBusConnection.getConnection(DBusConnection.SESSION); + serverconn.setWeakReferences(true); + clientconn.setWeakReferences(true); + + System.out.println("Registering Name"); + serverconn.requestBusName("foo.bar.Test"); + + /** This gets a remote object matching our bus name and exported object path. */ + Peer peer = clientconn.getRemoteObject("foo.bar.Test", "/Test", Peer.class); + DBus dbus = clientconn.getRemoteObject("org.freedesktop.DBus", "/org/freedesktop/DBus", DBus.class); + + System.out.print("Listening for signals..."); + signalhandler sigh = new signalhandler(); + renamedsignalhandler rsh = new renamedsignalhandler(); + try { + /** This registers an instance of the test class as the signal handler for the TestSignal class. */ + clientconn.addSigHandler(TestSignalInterface.EmptySignal.class, new emptysignalhandler()); + clientconn.addSigHandler(TestSignalInterface.TestSignal.class, sigh); + clientconn.addSigHandler(TestSignalInterface2.TestRenamedSignal.class, rsh); + clientconn.addSigHandler(DBus.Local.Disconnected.class, new disconnecthandler(clientconn, rsh)); + String source = dbus.GetNameOwner("foo.bar.Test"); + clientconn.addSigHandler(TestSignalInterface.TestArraySignal.class, source, peer, new arraysignalhandler()); + clientconn.addSigHandler(TestSignalInterface.TestObjectSignal.class, new objectsignalhandler()); + clientconn.addSigHandler(TestSignalInterface.TestPathSignal.class, new pathsignalhandler()); + badarraysignalhandler bash = new badarraysignalhandler(); + clientconn.addSigHandler(TestSignalInterface.TestSignal.class, bash); + clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, bash); + System.out.println("done"); + } catch (MatchRuleInvalid MRI) { + test.fail("Failed to add handlers: "+MRI.getMessage()); + } catch (DBusException DBe) { + test.fail("Failed to add handlers: "+DBe.getMessage()); + } + + System.out.println("Listening for Method Calls"); + testclass tclass = new testclass(serverconn); + testclass tclass2 = new testclass(serverconn); + /** This exports an instance of the test class as the object /Test. */ + serverconn.exportObject("/Test", tclass); + serverconn.exportObject("/BadTest", tclass); + serverconn.exportObject("/BadTest2", tclass2); + serverconn.addFallback("/FallbackTest", tclass); + + // explicitly unexport object + serverconn.unExportObject("/BadTest"); + // implicitly unexport object + tclass2 = null; + System.gc(); + System.runFinalization(); + System.gc(); + System.runFinalization(); + System.gc(); + System.runFinalization(); + + System.out.println("Sending Signal"); + /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */ + serverconn.sendSignal(new TestSignalInterface.TestSignal("/foo/bar/Wibble", "Bar", new UInt32(42))); + serverconn.sendSignal(new TestSignalInterface.EmptySignal("/foo/bar/Wibble")); + serverconn.sendSignal(new TestSignalInterface2.TestRenamedSignal("/foo/bar/Wibble", "Bar", new UInt32(42))); + + System.out.println("These things are on the bus:"); + String[] names = dbus.ListNames(); + for (String name: names) + System.out.println("\t"+name); + + System.out.println("Getting our introspection data"); + /** This gets a remote object matching our bus name and exported object path. */ + Introspectable intro = clientconn.getRemoteObject("foo.bar.Test", "/", Introspectable.class); + /** Get introspection data */ + String data;/* = intro.Introspect(); + if (null == data || !data.startsWith(" peers = serverconn.new PeerSet(); + peers.add("org.freedesktop.DBus"); + clientconn.requestBusName("test.testclient"); + peers.add("test.testclient"); + clientconn.releaseBusName("test.testclient"); + + System.out.println("Pinging ourselves"); + /** Call ping. */ + for (int i = 0; i < 10; i++) { + long then = System.currentTimeMillis(); + peer.Ping(); + long now = System.currentTimeMillis(); + System.out.println("Ping returned in "+(now-then)+"ms."); + } + + System.out.println("Calling Method0/1"); + /** This gets a remote object matching our bus name and exported object path. */ + TestRemoteInterface tri = (TestRemoteInterface) clientconn.getPeerRemoteObject("foo.bar.Test", "/Test"); + System.out.println("Got Remote Object: "+tri); + /** Call the remote object and get a response. */ + String rname = tri.getName(); + System.out.println("Got Remote Name: "+rname); + + Path path = new Path("/nonexistantwooooooo"); + Path p = tri.pathrv(path); + System.out.println(path.toString()+" => "+p.toString()); + if (!path.equals(p)) fail("pathrv incorrect"); + List paths = new Vector(); + paths.add(path); + List ps = tri.pathlistrv(paths); + System.out.println(paths.toString()+" => "+ps.toString()); + if (!paths.equals(ps)) fail("pathlistrv incorrect"); + Map pathm = new HashMap(); + pathm.put(path, path); + Map pm = tri.pathmaprv(pathm); + System.out.println(pathm.toString()+" => "+pm.toString()); + System.out.println(pm.containsKey(path)+" "+pm.get(path)+" "+path.equals(pm.get(path))); + System.out.println(pm.containsKey(p)+" "+pm.get(p)+" "+p.equals(pm.get(p))); + for (Path q: pm.keySet()) { + System.out.println(q); + System.out.println(pm.get(q)); + } + if (!pm.containsKey(path) || !path.equals(pm.get(path))) fail("pathmaprv incorrect"); + + serverconn.sendSignal(new TestSignalInterface.TestPathSignal("/Test", path, paths, pathm)); + + Collator col = Collator.getInstance(); + col.setDecomposition(Collator.FULL_DECOMPOSITION); + col.setStrength(Collator.PRIMARY); + if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!", rname)) + fail("getName return value incorrect"); + System.out.println("sending it to sleep"); + tri.waitawhile(); + System.out.println("testing floats"); + if (17.093f != tri.testfloat(new float[] { 17.093f, -23f, 0.0f, 31.42f })) + fail("testfloat returned the wrong thing"); + System.out.println("Structs of Structs"); + List> lli = new Vector>(); + List li = new Vector(); + li.add(1); + li.add(2); + li.add(3); + lli.add(li); + lli.add(li); + lli.add(li); + TestStruct3 ts3 = new TestStruct3(new TestStruct2(new Vector(), new Variant(0)), lli); + int[][] out = tri.teststructstruct(ts3); + if (out.length != 3) fail("teststructstruct returned the wrong thing: "+Arrays.deepToString(out)); + for (int[] o: out) + if (o.length != 3 + ||o[0] != 1 + ||o[1] != 2 + ||o[2] != 3) fail("teststructstruct returned the wrong thing: "+Arrays.deepToString(out)); + + System.out.println("frobnicating"); + List ls = new Vector(); + ls.add(2L); + ls.add(5L); + ls.add(71L); + Map mus = new HashMap(); + mus.put(new UInt16(4), (short) 5); + mus.put(new UInt16(5), (short) 6); + mus.put(new UInt16(6), (short) 7); + Map> msmus = new HashMap>(); + msmus.put("stuff", mus); + int rint = tri.frobnicate(ls, msmus, 13); + if (-5 != rint) + fail("frobnicate return value incorrect"); + + System.out.println("Doing stuff asynchronously with callback"); + clientconn.callWithCallback(tri, "getName", new callbackhandler()); + System.out.println("Doing stuff asynchronously with callback, which throws an error"); + clientconn.callWithCallback(tri, "getNameAndThrow", new callbackhandler()); + + /** call something that throws */ + try { + System.out.println("Throwing stuff"); + tri.throwme(); + test.fail("Method Execution should have failed"); + } catch (TestException Te) { + System.out.println("Remote Method Failed with: "+Te.getClass().getName()+" "+Te.getMessage()); + if (!Te.getMessage().equals("test")) + test.fail("Error message was not correct"); + } + + /* Test type signatures */ + Vector ts = new Vector(); + Marshalling.getJavaType("ya{si}", ts, -1); + tri.sig(ts.toArray(new Type[0])); + + tri.newpathtest(new Path("/new/path/test")); + + /** Try and call an invalid remote object */ + try { + System.out.println("Calling Method2"); + tri = clientconn.getRemoteObject("foo.bar.NotATest", "/Moofle", TestRemoteInterface.class); + System.out.println("Got Remote Name: "+tri.getName()); + test.fail("Method Execution should have failed"); + } catch (ServiceUnknown SU) { + System.out.println("Remote Method Failed with: "+SU.getClass().getName()+" "+SU.getMessage()); + } + + /** Try and call an invalid remote object */ + try { + System.out.println("Calling Method3"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/Moofle", TestRemoteInterface.class); + System.out.println("Got Remote Name: "+tri.getName()); + test.fail("Method Execution should have failed"); + } catch (UnknownObject UO) { + System.out.println("Remote Method Failed with: "+UO.getClass().getName()+" "+UO.getMessage()); + } + + /** Try and call an explicitly unexported object */ + try { + System.out.println("Calling Method4"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest", TestRemoteInterface.class); + System.out.println("Got Remote Name: "+tri.getName()); + test.fail("Method Execution should have failed"); + } catch (UnknownObject UO) { + System.out.println("Remote Method Failed with: "+UO.getClass().getName()+" "+UO.getMessage()); + } + + /** Try and call an implicitly unexported object */ + try { + System.out.println("Calling Method5"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/BadTest2", TestRemoteInterface.class); + System.out.println("Got Remote Name: "+tri.getName()); + test.fail("Method Execution should have failed"); + } catch (UnknownObject UO) { + System.out.println("Remote Method Failed with: "+UO.getClass().getName()+" "+UO.getMessage()); + } + + System.out.println("Calling Method6"); + tri = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/1", TestRemoteInterface.class); + intro = clientconn.getRemoteObject("foo.bar.Test", "/FallbackTest/0/4", Introspectable.class); + System.out.println("Got Fallback Name: "+tri.getName()); + System.out.println("Fallback Introspection Data: \n"+intro.Introspect()); + + System.out.println("Testing Properties returning Paths"); + Properties prop = clientconn.getRemoteObject("foo.bar.Test", "/Test", Properties.class); + Path prv = (Path) prop.Get("foo.bar", "foo"); + System.out.println("Got path "+prv); + System.out.println("Calling Method7--9"); + /** This gets a remote object matching our bus name and exported object path. */ + TestRemoteInterface2 tri2 = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface2.class); + System.out.print("Calling the other introspect method: "); + String intro2 = tri2.Introspect(); + System.out.println(intro2); + if (0 != col.compare("Not XML", intro2)) + fail("Introspect return value incorrect"); + + /** Call the remote object and get a response. */ + TestTuple,Boolean> rv = tri2.show(234); + System.out.println("Show returned: "+rv); + if (!serverconn.getUniqueName().equals(rv.a) || + 1 != rv.b.size() || + 1953 != rv.b.get(0) || + true != rv.c.booleanValue()) + fail("show return value incorrect ("+rv.a+","+rv.b+","+rv.c+")"); + + System.out.println("Doing stuff asynchronously"); + DBusAsyncReply stuffreply = (DBusAsyncReply) clientconn.callMethodAsync(tri2, "dostuff", new TestStruct("bar", new UInt32(52), new Variant(new Boolean(true)))); + + System.out.println("Checking bools"); + if (tri2.check()) fail("bools are broken"); + + List l = new Vector(); + l.add("hi"); + l.add("hello"); + l.add("hej"); + l.add("hey"); + l.add("aloha"); + System.out.println("Sampling Arrays:"); + List is = tri2.sampleArray(l, new Integer[] { 1, 5, 7, 9 }, new long[] { 2, 6, 8, 12 }); + System.out.println("sampleArray returned an array:"); + for (Integer i: is) + System.out.println("--"+i); + if (is.size() != 5 || + is.get(0).intValue() != -1 || + is.get(1).intValue() != -5 || + is.get(2).intValue() != -7 || + is.get(3).intValue() != -12 || + is.get(4).intValue() != -18) + fail("sampleArray return value incorrect"); + + System.out.println("Get This"); + if (!tclass.equals(tri2.getThis(tri2))) + fail("Didn't get the correct this"); + + Boolean b = stuffreply.getReply(); + System.out.println("Do stuff replied "+b); + if (true != b.booleanValue()) + fail("dostuff return value incorrect"); + + System.out.print("Sending Array Signal..."); + /** This creates an instance of the Test Signal, with the given object path, signal name and parameters, and broadcasts in on the Bus. */ + List tsl = new Vector(); + tsl.add(new TestStruct2(l, new Variant(new UInt64(567)))); + Map tsm = new HashMap(); + tsm.put(new UInt32(1), new TestStruct2(l, new Variant(new UInt64(678)))); + tsm.put(new UInt32(42), new TestStruct2(l, new Variant(new UInt64(789)))); + serverconn.sendSignal(new TestSignalInterface.TestArraySignal("/Test", tsl, tsm)); + + System.out.println("done"); + + System.out.print("testing custom serialization..."); + Vector v = new Vector(); + v.add(1); + v.add(2); + v.add(3); + TestSerializable s = new TestSerializable(1, "woo", v); + s = tri2.testSerializable((byte) 12, s, 13); + System.out.print("returned: "+s); + if (s.getInt() != 1 || + ! s.getString().equals("woo") || + s.getVector().size() != 3 || + s.getVector().get(0) != 1 || + s.getVector().get(1) != 2 || + s.getVector().get(2) != 3) + fail("Didn't get back the same TestSerializable"); + + System.out.println("done"); + + System.out.print("testing complex variants..."); + Map m = new HashMap(); + m.put("cow", "moo"); + tri2.complexv(new Variant(m, "a{ss}")); + System.out.println("done"); + + System.out.print("testing recursion..."); + + if (0 != col.compare("This Is A UTF-8 Name: ﺱ !!",tri2.recursionTest())) fail("recursion test failed"); + + System.out.println("done"); + + System.out.print("testing method overloading..."); + tri = clientconn.getRemoteObject("foo.bar.Test", "/Test", TestRemoteInterface.class); + if (1 != tri2.overload("foo")) test.fail("wrong overloaded method called"); + if (2 != tri2.overload((byte) 0)) test.fail("wrong overloaded method called"); + if (3 != tri2.overload()) test.fail("wrong overloaded method called"); + if (4 != tri.overload()) test.fail("wrong overloaded method called"); + System.out.println("done"); + + System.out.print("reg13291..."); + byte[] as = new byte[10]; + for (int i = 0; i < 10; i++) + as[i] = (byte) (100-i); + tri.reg13291(as, as); + System.out.println("done"); + + System.out.print("Testing nested lists..."); + lli = new Vector>(); + li = new Vector(); + li.add(1); + lli.add(li); + List> reti = tri2.checklist(lli); + if (reti.size() != 1 || + reti.get(0).size() != 1 || + reti.get(0).get(0) != 1) + test.fail("Failed to check nested lists"); + System.out.println("done"); + + System.out.print("Testing dynamic object creation..."); + TestNewInterface tni = tri2.getNew(); + System.out.print(tni.getName()+" "); + System.out.println("done"); + + /* send an object in a signal */ + serverconn.sendSignal(new TestSignalInterface.TestObjectSignal("/foo/bar/Wibble", tclass)); + + /** Pause while we wait for the DBus messages to go back and forth. */ + Thread.sleep(1000); + + // check that bus name set has been trimmed + if (peers.size() != 1) fail("peers hasn't been trimmed"); + if (!peers.contains("org.freedesktop.DBus")) fail ("peers contains the wrong name"); + + System.out.println("Checking for outstanding errors"); + DBusExecutionException DBEe = serverconn.getError(); + if (null != DBEe) throw DBEe; + DBEe = clientconn.getError(); + if (null != DBEe) throw DBEe; + + System.out.println("Disconnecting"); + /** Disconnect from the bus. */ + clientconn.disconnect(); + serverconn.disconnect(); + + System.out.println("Trying to do things after disconnection"); + + /** Remove sig handler */ + clientconn.removeSigHandler(TestSignalInterface.TestSignal.class, sigh); + + /** Call a method when disconnected */ + try { + System.out.println("getName() suceeded and returned: "+tri.getName()); + fail("Should not succeed when disconnected"); + } catch (NotConnected NC) { + System.out.println("getName() failed with exception "+NC); + } + clientconn = null; + serverconn = null; + + if (!done1) fail("Signal handler 1 failed to be run"); + if (!done2) fail("Signal handler 2 failed to be run"); + if (!done3) fail("Signal handler 3 failed to be run"); + if (!done4) fail("Callback handler failed to be run"); + if (!done5) fail("Signal handler R failed to be run"); + if (!done6) fail("Disconnect handler failed to be run"); + if (!done7) fail("Signal handler E failed to be run"); + if (!done8) fail("Error callback handler failed to be run"); + + } catch (Exception e) { + e.printStackTrace(); + fail("Unexpected Exception Occurred: "+e); + }} +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test_low_level.java b/app/src/main/java/org/freedesktop/dbus/test/test_low_level.java new file mode 100644 index 00000000..e65956d8 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/test_low_level.java @@ -0,0 +1,56 @@ +/* + 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.test; +import cx.ath.matthew.debug.Debug; +import org.freedesktop.dbus.BusAddress; +import org.freedesktop.dbus.DBusSignal; +import org.freedesktop.dbus.Message; +import org.freedesktop.dbus.MethodCall; +import org.freedesktop.dbus.Transport; + +public class test_low_level +{ + public static void main(String[] args) throws Exception + { + Debug.setHexDump(true); + String addr = System.getenv("DBUS_SESSION_BUS_ADDRESS"); + Debug.print(addr); + BusAddress address = new BusAddress(addr); + Debug.print(address); + + Transport conn = new Transport(address); + + Message m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "Hello", (byte) 0, null); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m.getClass()); + Debug.print(m); + m = conn.min.readMessage(); + Debug.print(m.getClass()); + Debug.print(m); + m = conn.min.readMessage(); + Debug.print(""+m); + m = new MethodCall("org.freedesktop.DBus", "/", null, "Hello", (byte) 0, null); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m); + + m = new MethodCall("org.freedesktop.DBus", "/org/freedesktop/DBus", "org.freedesktop.DBus", "RequestName", (byte) 0, "su", "org.testname", 0); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m); + m = new DBusSignal(null, "/foo", "org.foo", "Foo", null); + conn.mout.writeMessage(m); + m = conn.min.readMessage(); + Debug.print(m); + conn.disconnect(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java new file mode 100644 index 00000000..aafbca4f --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_client.java @@ -0,0 +1,42 @@ +/* + 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.test; + +import java.io.BufferedReader; +import java.io.FileInputStream; +import java.io.InputStreamReader; + +import org.freedesktop.DBus; +import org.freedesktop.dbus.DirectConnection; + +public class test_p2p_client +{ + public static void main(String[] args) throws Exception + { + BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream("address"))); + String address = r.readLine(); + DirectConnection dc = new DirectConnection(address); + System.out.println("Connected"); + TestRemoteInterface tri = (TestRemoteInterface) dc.getRemoteObject("/Test"); + System.out.println(tri.getName()); + System.out.println(tri.testfloat(new float[] { 17.093f, -23f, 0.0f, 31.42f })); + + try { + tri.throwme(); + } catch (TestException Te) { + System.out.println("Caught TestException"); + } + ((DBus.Peer) tri).Ping(); + System.out.println(((DBus.Introspectable) tri).Introspect()); + dc.disconnect(); + System.out.println("Disconnected"); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java new file mode 100644 index 00000000..68406941 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/test_p2p_server.java @@ -0,0 +1,94 @@ +/* + 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.test; + +import java.lang.reflect.Type; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.freedesktop.dbus.DirectConnection; +import org.freedesktop.dbus.Path; +import org.freedesktop.dbus.UInt16; + +public class test_p2p_server implements TestRemoteInterface +{ + public int[][] teststructstruct(TestStruct3 in) + { + List> lli = in.b; + int[][] out = new int[lli.size()][]; + for (int j = 0; j < out.length; j++) { + out[j] = new int[lli.get(j).size()]; + for (int k = 0; k < out[j].length; k++) + out[j][k] = lli.get(j).get(k); + } + return out; + } + public String getNameAndThrow() + { + return getName(); + } + public String getName() + { + System.out.println("getName called"); + return "Peer2Peer Server"; + } + public int frobnicate(List n, Map> m, T v) + { + return 3; + } + public void throwme() throws TestException + { + System.out.println("throwme called"); + throw new TestException("BOO"); + } + public void waitawhile() + { + return; + } + public int overload() + { + return 1; + } + public void sig(Type[] s) + { + } + public void newpathtest(Path p) + { + } + public void reg13291(byte[] as, byte[] bs) + { + } + public Path pathrv(Path a) { return a; } + public List pathlistrv(List a) { return a; } + public Map pathmaprv(Map a) { return a; } + public boolean isRemote() { return false; } + public float testfloat(float[] f) + { + System.out.println("got float: "+Arrays.toString(f)); + return f[0]; + } + + public static void main(String[] args) throws Exception + { + String address = DirectConnection.createDynamicSession(); + //String address = "tcp:host=localhost,port=12344,guid="+Transport.genGUID(); + PrintWriter w = new PrintWriter(new FileOutputStream("address")); + w.println(address); + w.flush(); + w.close(); + DirectConnection dc = new DirectConnection(address+",listen=true"); + System.out.println("Connected"); + dc.exportObject("/Test", new test_p2p_server()); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java new file mode 100644 index 00000000..f37e955b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_client.java @@ -0,0 +1,42 @@ +/* + 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.test; + +import org.freedesktop.dbus.DBusConnection; + +public class two_part_test_client +{ + public static class two_part_test_object implements TwoPartObject + { + public boolean isRemote() { return false; } + public String getName() + { + System.out.println("client name"); + return toString(); + } + } + public static void main(String[] args) throws Exception + { + System.out.println("get conn"); + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + System.out.println("get remote"); + TwoPartInterface remote = conn.getRemoteObject("org.freedesktop.dbus.test.two_part_server", "/", TwoPartInterface.class); + System.out.println("get object"); + TwoPartObject o = remote.getNew(); + System.out.println("get name"); + System.out.println(o.getName()); + two_part_test_object tpto = new two_part_test_object(); + conn.exportObject("/TestObject", tpto); + conn.sendSignal(new TwoPartInterface.TwoPartSignal("/FromObject", tpto)); + try { Thread.sleep(1000); } catch (InterruptedException Ie) {} + conn.disconnect(); + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java new file mode 100644 index 00000000..7452303b --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/test/two_part_test_server.java @@ -0,0 +1,55 @@ +/* + 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.test; + +import org.freedesktop.dbus.DBusConnection; +import org.freedesktop.dbus.DBusSigHandler; + +public class two_part_test_server implements TwoPartInterface, DBusSigHandler +{ + public class two_part_test_object implements TwoPartObject + { + public boolean isRemote() { return false; } + public String getName() + { + System.out.println("give name"); + return toString(); + } + } + private DBusConnection conn; + public two_part_test_server(DBusConnection conn) + { + this.conn = conn; + } + public boolean isRemote() { return false; } + public TwoPartObject getNew() + { + TwoPartObject o = new two_part_test_object(); + System.out.println("export new"); + try { conn.exportObject("/12345", o); } catch (Exception e) {} + System.out.println("give new"); + return o; + } + public void handle(TwoPartInterface.TwoPartSignal s) + { + System.out.println("Got: "+s.o); + } + public static void main(String[] args) throws Exception + { + DBusConnection conn = DBusConnection.getConnection(DBusConnection.SESSION); + conn.requestBusName("org.freedesktop.dbus.test.two_part_server"); + two_part_test_server server = new two_part_test_server(conn); + conn.exportObject("/", server); + conn.addSigHandler(TwoPartInterface.TwoPartSignal.class, server); + while (true) try { Thread.sleep(10000); } catch (InterruptedException Ie) {} + } +} + diff --git a/app/src/main/java/org/freedesktop/dbus/types/DBusListType.java b/app/src/main/java/org/freedesktop/dbus/types/DBusListType.java new file mode 100644 index 00000000..f7711dc8 --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/types/DBusListType.java @@ -0,0 +1,44 @@ +/* + 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.types; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.List; + +/** + * The type of a list. + * Should be used whenever you need a Type variable for a list. + */ +public class DBusListType implements ParameterizedType +{ + private Type v; + /** + * Create a List type. + * @param v Type of the list contents. + */ + public DBusListType(Type v) + { + this.v = v; + } + public Type[] getActualTypeArguments() + { + return new Type[] { v }; + } + public Type getRawType() + { + return List.class; + } + public Type getOwnerType() + { + return null; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java b/app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java new file mode 100644 index 00000000..825df7db --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/types/DBusMapType.java @@ -0,0 +1,47 @@ +/* + 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.types; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * The type of a map. + * Should be used whenever you need a Type variable for a map. + */ +public class DBusMapType implements ParameterizedType +{ + private Type k; + private Type v; + /** + * Create a map type. + * @param k The type of the keys. + * @param v The type of the values. + */ + public DBusMapType(Type k, Type v) + { + this.k = k; + this.v = v; + } + public Type[] getActualTypeArguments() + { + return new Type[] { k, v }; + } + public Type getRawType() + { + return Map.class; + } + public Type getOwnerType() + { + return null; + } +} diff --git a/app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java b/app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java new file mode 100644 index 00000000..86d7533f --- /dev/null +++ b/app/src/main/java/org/freedesktop/dbus/types/DBusStructType.java @@ -0,0 +1,44 @@ +/* + 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.types; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import org.freedesktop.dbus.Struct; + +/** + * The type of a struct. + * Should be used whenever you need a Type variable for a struct. + */ +public class DBusStructType implements ParameterizedType +{ + private Type[] contents; + /** + * Create a struct type. + * @param contents The types contained in this struct. + */ + public DBusStructType(Type... contents) + { + this.contents = contents; + } + public Type[] getActualTypeArguments() + { + return contents; + } + public Type getRawType() + { + return Struct.class; + } + public Type getOwnerType() + { + return null; + } +}