/*
   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<Byte, Object> 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<Byte, Object>();
      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<Byte, Object>();
      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<Object>) hs[0]) {
         this.headers.put((Byte) ((Object[])o)[0], ((Variant<Object>)((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);
                  if (i == diff) {
                     // advance the type parser even on 0-size arrays.
                     Vector<Type> temp = new Vector<Type>();
                     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 - 1;
                  }
                  i = diff;
               } else if (data instanceof Map) {
                  int diff = i;
                  ensureBuffers(((Map) data).size()*6);
                  for (Map.Entry<Object,Object> o: ((Map<Object,Object>) data).entrySet())
                     diff = appendone(sigb, i, o);
                  if (i == diff) {
                     // advance the type parser even on 0-size arrays.
                     Vector<Type> temp = new Vector<Type>();
                     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 && j < data.length; i++, j++) {
         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<Type> temp = new Vector<Type>();
                     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<Object[]> entries = new Vector<Object[]>();
                  while (ofs[1] < end) {
                     ofs[0] = ofssave;
                     entries.add((Object[]) extractone(sigb, buf, ofs, true));
                  }
                  rv = new DBusMap<Object, Object>(entries.toArray(new Object[0][]));
                  break;
               default:
                  if (0 == size) {
                     // advance the type parser even on 0-size arrays.
                     Vector<Type> temp = new Vector<Type>();
                     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<Object> contents = new Vector<Object>();
                  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<Object> contents = new Vector<Object>();
            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<Object>(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<Object> rv = new Vector<Object>();
      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);
      }
   }
}
