Completely reworked Java viewer (contributed by Brian Hinz)


git-svn-id: svn://svn.code.sf.net/p/tigervnc/code/trunk@4413 3789f03b-4d11-0410-bbf8-ca57d06f2519
diff --git a/java/src/com/tigervnc/rdr/EndOfStream.java b/java/src/com/tigervnc/rdr/EndOfStream.java
new file mode 100644
index 0000000..bdcf7c2
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/EndOfStream.java
@@ -0,0 +1,25 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.rdr;
+
+public class EndOfStream extends Exception {
+  public EndOfStream() {
+    super("EndOfStream");
+  }
+}
diff --git a/java/src/com/tigervnc/rdr/Exception.java b/java/src/com/tigervnc/rdr/Exception.java
new file mode 100644
index 0000000..a5fe938
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/Exception.java
@@ -0,0 +1,25 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.rdr;
+
+public class Exception extends RuntimeException {
+  public Exception(String s) {
+    super(s);
+  }
+}
diff --git a/java/src/com/tigervnc/rdr/IOException.java b/java/src/com/tigervnc/rdr/IOException.java
new file mode 100644
index 0000000..6343d7a
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/IOException.java
@@ -0,0 +1,27 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.rdr;
+
+class IOException extends Exception {
+  public IOException(java.io.IOException ex_) {
+    super(ex_.toString());
+    ex = ex_;
+  }
+  java.io.IOException ex;
+}
diff --git a/java/src/com/tigervnc/rdr/InStream.java b/java/src/com/tigervnc/rdr/InStream.java
new file mode 100644
index 0000000..ec2d6d7
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/InStream.java
@@ -0,0 +1,196 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// rdr::InStream marshalls data from a buffer stored in RDR (RFB Data
+// Representation).
+//
+
+package com.tigervnc.rdr;
+
+abstract public class InStream {
+
+  // check() ensures there is buffer data for at least one item of size
+  // itemSize bytes.  Returns the number of items in the buffer (up to a
+  // maximum of nItems).
+
+  public int check(int itemSize, int nItems) {
+    if (ptr + itemSize * nItems > end) {
+      if (ptr + itemSize > end)
+        return overrun(itemSize, nItems);
+
+      nItems = (end - ptr) / itemSize;
+    }
+    return nItems;
+  }
+
+  public final int check(int itemSize) {
+    if (ptr + itemSize > end)
+      return overrun(itemSize, 1);
+    return 1;
+  }
+
+  // checkNoWait() tries to make sure that the given number of bytes can
+  // be read without blocking.  It returns true if this is the case, false
+  // otherwise.  The length must be "small" (less than the buffer size).
+
+  public final boolean checkNoWait(int length) { return check(length, 1)!=0; }
+
+  // readU/SN() methods read unsigned and signed N-bit integers.
+
+  public final int readS8()  { check(1); return b[ptr++]; }
+  public final int readS16() { check(2); int b0 = b[ptr++];
+                               int b1 = b[ptr++] & 0xff; return b0 << 8 | b1; }
+  public final int readS32() { check(4); int b0 = b[ptr++];
+                               int b1 = b[ptr++] & 0xff;
+                               int b2 = b[ptr++] & 0xff;
+                               int b3 = b[ptr++] & 0xff;
+                               return b0 << 24 | b1 << 16 | b2 << 8 | b3; }
+
+  public final int readU8()  { return readS8()  & 0xff;  }
+  public final int readU16() { return readS16() & 0xffff; }
+  public final int readU32() { return readS32() & 0xffffffff; }
+
+  // readString() reads a string - a U32 length followed by the data.
+
+  public final String readString() {
+    int len = readU32();
+    if (len > maxStringLength)
+      throw new Exception("InStream max string length exceeded");
+
+    char[] str = new char[len];
+    int i = 0;
+    while (i < len) {
+      int j = i + check(1, len - i);
+      while (i < j) {
+	str[i++] = (char)b[ptr++];
+      }
+    }
+
+    return new String(str);
+  }
+
+  // maxStringLength protects against allocating a huge buffer.  Set it
+  // higher if you need longer strings.
+
+  public static int maxStringLength = 65535;
+
+  public final void skip(int bytes) {
+    while (bytes > 0) {
+      int n = check(1, bytes);
+      ptr += n;
+      bytes -= n;
+    }
+  }
+
+  // readBytes() reads an exact number of bytes into an array at an offset.
+
+  public void readBytes(byte[] data, int offset, int length) {
+    int offsetEnd = offset + length;
+    while (offset < offsetEnd) {
+      int n = check(1, offsetEnd - offset);
+      System.arraycopy(b, ptr, data, offset, n);
+      ptr += n;
+      offset += n;
+    }
+  }
+
+  public void readBytes(int[] data, int offset, int length) {
+    int offsetEnd = offset + length;
+    while (offset < offsetEnd) {
+      int n = check(1, offsetEnd - offset);
+      System.arraycopy(b, ptr, data, offset, n);
+      ptr += n;
+      offset += n;
+    }
+  }
+
+  // readOpaqueN() reads a quantity "without byte-swapping".  Because java has
+  // no byte-ordering, we just use big-endian.
+
+  public final int readOpaque8()  { return readU8(); }
+  public final int readOpaque16() { return readU16(); }
+  public final int readOpaque32() { return readU32(); }
+  public final int readOpaque24A() { check(3); int b0 = b[ptr++];
+                                     int b1 = b[ptr++]; int b2 = b[ptr++];
+                                     return b0 << 24 | b1 << 16 | b2 << 8; }
+  public final int readOpaque24B() { check(3); int b0 = b[ptr++];
+                                     int b1 = b[ptr++]; int b2 = b[ptr++];
+                                     return b0 << 16 | b1 << 8 | b2; }
+
+  public final int readPixel(int bytesPerPixel, boolean e) {
+    int[] pix = new int[4];
+    for (int i=0; i < bytesPerPixel; i++)
+      pix[i] = readU8();
+    if (e) {
+      return pix[0] << 16 | pix[1] << 8 | pix[2] | (0xff << 24);
+    } else {
+      return pix[2] << 16 | pix[1] << 8 | pix[0] | (0xff << 24);
+    }
+  }
+
+  public final void readPixels(int[] buf, int length, int bytesPerPixel, boolean e) {
+    for (int i = 0; i < length; i++)
+      buf[i] = readPixel(bytesPerPixel, e);
+  }
+
+  public final int readCompactLength() {
+    int b = readU8();
+    int result = b & 0x7F;
+    if ((b & 0x80) != 0) {
+      b = readU8();
+      result |= (b & 0x7F) << 7;
+      if ((b & 0x80) != 0) {
+        b = readU8();
+        result |= (b & 0xFF) << 14;
+      }
+    }
+    return result;
+  }
+  
+  // pos() returns the position in the stream.
+
+  abstract public int pos();
+
+  // bytesAvailable() returns true if at least one byte can be read from the
+  // stream without blocking.  i.e. if false is returned then readU8() would
+  // block.
+
+  public boolean bytesAvailable() { return end != ptr; }
+
+  // getbuf(), getptr(), getend() and setptr() are "dirty" methods which allow
+  // you to manipulate the buffer directly.  This is useful for a stream which
+  // is a wrapper around an underlying stream.
+
+  public final byte[] getbuf() { return b; }
+  public final int getptr() { return ptr; }
+  public final int getend() { return end; }
+  public final void setptr(int p) { ptr = p; }
+
+  // overrun() is implemented by a derived class to cope with buffer overrun.
+  // It ensures there are at least itemSize bytes of buffer data.  Returns
+  // the number of items in the buffer (up to a maximum of nItems).  itemSize
+  // is supposed to be "small" (a few bytes).
+
+  abstract protected int overrun(int itemSize, int nItems);
+
+  protected InStream() {}
+  protected byte[] b;
+  protected int ptr;
+  protected int end;
+}
diff --git a/java/src/com/tigervnc/rdr/JavaInStream.java b/java/src/com/tigervnc/rdr/JavaInStream.java
new file mode 100644
index 0000000..426a0e7
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/JavaInStream.java
@@ -0,0 +1,147 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// A JavaInStream reads from a java.io.InputStream
+//
+
+package com.tigervnc.rdr;
+
+public class JavaInStream extends InStream {
+
+  static final int defaultBufSize = 8192;
+  static final int minBulkSize = 1024;
+
+  public JavaInStream(java.io.InputStream jis_, int bufSize_) {
+    jis = jis_;
+    bufSize = bufSize_;
+    b = new byte[bufSize];
+    ptr = end = ptrOffset = 0;
+    timeWaitedIn100us = 5;
+    timedKbits = 0;
+  }
+
+  public JavaInStream(java.io.InputStream jis_) { this(jis_, defaultBufSize); }
+
+  public void readBytes(byte[] data, int offset, int length) {
+    if (length < minBulkSize) {
+      super.readBytes(data, offset, length);
+      return;
+    }
+
+    int n = end - ptr;
+    if (n > length) n = length;
+
+    System.arraycopy(b, ptr, data, offset, n);
+    offset += n;
+    length -= n;
+    ptr += n;
+
+    while (length > 0) {
+      n = read(data, offset, length);
+      offset += n;
+      length -= n;
+      ptrOffset += n;
+    }
+  }
+
+  public int pos() { return ptrOffset + ptr; }
+
+  public void startTiming() {
+    timing = true;
+
+    // Carry over up to 1s worth of previous rate for smoothing.
+
+    if (timeWaitedIn100us > 10000) {
+      timedKbits = timedKbits * 10000 / timeWaitedIn100us;
+      timeWaitedIn100us = 10000;
+    }
+  }
+
+  public void stopTiming() {
+    timing = false; 
+    if (timeWaitedIn100us < timedKbits/2)
+      timeWaitedIn100us = timedKbits/2; // upper limit 20Mbit/s
+  }
+
+  public long kbitsPerSecond() {
+    return timedKbits * 10000 / timeWaitedIn100us;
+  }
+
+  public long timeWaited() { return timeWaitedIn100us; }
+
+  protected int overrun(int itemSize, int nItems) {
+    if (itemSize > bufSize)
+      throw new Exception("JavaInStream overrun: max itemSize exceeded");
+
+    if (end - ptr != 0)
+      System.arraycopy(b, ptr, b, 0, end - ptr);
+
+    ptrOffset += ptr;
+    end -= ptr;
+    ptr = 0;
+
+    while (end < itemSize) {
+      int n = read(b, end, bufSize - end);
+      end += n;
+    }
+
+    if (itemSize * nItems > end)
+      nItems = end / itemSize;
+
+    return nItems;
+  }
+
+  private int read(byte[] buf, int offset, int len) {
+    try {
+      long before = 0;
+      if (timing)
+        before = System.currentTimeMillis();
+
+      int n = jis.read(buf, offset, len);
+      if (n < 0) throw new EndOfStream();
+
+      if (timing) {
+        long after = System.currentTimeMillis();
+        long newTimeWaited = (after - before) * 10;
+        int newKbits = n * 8 / 1000;
+
+        // limit rate to between 10kbit/s and 40Mbit/s
+
+        if (newTimeWaited > newKbits*1000) newTimeWaited = newKbits*1000;
+        if (newTimeWaited < newKbits/4)    newTimeWaited = newKbits/4;
+
+        timeWaitedIn100us += newTimeWaited;
+        timedKbits += newKbits;
+      }
+
+      return n;
+
+    } catch (java.io.IOException e) {
+      throw new IOException(e);
+    }
+  }
+
+  private java.io.InputStream jis;
+  private int ptrOffset;
+  private int bufSize;
+
+  boolean timing;
+  long timeWaitedIn100us;
+  long timedKbits;
+}
diff --git a/java/src/com/tigervnc/rdr/JavaOutStream.java b/java/src/com/tigervnc/rdr/JavaOutStream.java
new file mode 100644
index 0000000..94791b9
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/JavaOutStream.java
@@ -0,0 +1,82 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// A JavaOutStream writes to a java.io.OutputStream
+//
+
+package com.tigervnc.rdr;
+
+public class JavaOutStream extends OutStream {
+
+  static final int defaultBufSize = 16384;
+  static final int minBulkSize = 1024;
+
+  public JavaOutStream(java.io.OutputStream jos_, int bufSize_) {
+    jos = jos_;
+    bufSize = bufSize_;
+    b = new byte[bufSize];
+    ptr = 0;
+    end = bufSize;
+  }
+
+  public JavaOutStream(java.io.OutputStream jos) { this(jos, defaultBufSize); }
+
+  public void writeBytes(byte[] data, int offset, int length) {
+    if (length < minBulkSize) {
+      super.writeBytes(data, offset, length);
+      return;
+    }
+
+    flush();
+    try {
+      jos.write(data, offset, length);
+    } catch (java.io.IOException e) {
+      throw new IOException(e);
+    }
+    ptrOffset += length;
+  }
+
+  public void flush() {
+    try {
+      jos.write(b, 0, ptr);
+    } catch (java.io.IOException e) {
+      throw new IOException(e);
+    }
+    ptrOffset += ptr;
+    ptr = 0;
+  }
+
+  public int length() { return ptrOffset + ptr; }
+
+  protected int overrun(int itemSize, int nItems) {
+    if (itemSize > bufSize)
+      throw new Exception("JavaOutStream overrun: max itemSize exceeded");
+
+    flush();
+
+    if (itemSize * nItems > end)
+      nItems = end / itemSize;
+
+    return nItems;
+  }
+
+  private java.io.OutputStream jos;
+  private int ptrOffset;
+  private int bufSize;
+}
diff --git a/java/src/com/tigervnc/rdr/MemInStream.java b/java/src/com/tigervnc/rdr/MemInStream.java
new file mode 100644
index 0000000..ce4f91e
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/MemInStream.java
@@ -0,0 +1,34 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+package com.tigervnc.rdr;
+
+public class MemInStream extends InStream {
+
+  public MemInStream(byte[] data, int offset, int len) {
+    b = data;
+    ptr = offset;
+    end = offset + len;
+  }
+
+  public int pos() { return ptr; }
+
+  protected int overrun(int itemSize, int nItems) {
+    throw new EndOfStream();
+  }
+}
diff --git a/java/src/com/tigervnc/rdr/MemOutStream.java b/java/src/com/tigervnc/rdr/MemOutStream.java
new file mode 100644
index 0000000..b304079
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/MemOutStream.java
@@ -0,0 +1,53 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// A MemOutStream grows as needed when data is written to it.
+//
+
+package com.tigervnc.rdr;
+
+public class MemOutStream extends OutStream {
+
+  public MemOutStream(int len) {
+    b = new byte[len];
+    ptr = 0;
+    end = len;
+  }
+  public MemOutStream() { this(1024); }
+
+  public int length() { return ptr; }
+  public void clear() { ptr = 0; };
+  public void reposition(int pos) { ptr = pos; }
+
+  // overrun() either doubles the buffer or adds enough space for nItems of
+  // size itemSize bytes.
+
+  protected int overrun(int itemSize, int nItems) {
+    int len = ptr + itemSize * nItems;
+    if (len < end * 2)
+      len = end * 2;
+
+    byte[] newBuf = new byte[len];
+    System.arraycopy(b, 0, newBuf, 0, ptr);
+    b = newBuf;
+    end = len;
+
+    return nItems;
+  }
+}
diff --git a/java/src/com/tigervnc/rdr/OutStream.java b/java/src/com/tigervnc/rdr/OutStream.java
new file mode 100644
index 0000000..7b4869e
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/OutStream.java
@@ -0,0 +1,141 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// rdr::OutStream marshalls data into a buffer stored in RDR (RFB Data
+// Representation).
+//
+
+package com.tigervnc.rdr;
+
+abstract public class OutStream {
+
+  // check() ensures there is buffer space for at least one item of size
+  // itemSize bytes.  Returns the number of items which fit (up to a maximum
+  // of nItems).
+
+  public final int check(int itemSize, int nItems) {
+    if (ptr + itemSize * nItems > end) {
+      if (ptr + itemSize > end)
+        return overrun(itemSize, nItems);
+
+      nItems = (end - ptr) / itemSize;
+    }
+    return nItems;
+  }
+
+  public final void check(int itemSize) {
+    if (ptr + itemSize > end)
+      overrun(itemSize, 1);
+  }
+
+  // writeU/SN() methods write unsigned and signed N-bit integers.
+
+  public final void writeU8( int u) { check(1); b[ptr++] = (byte)u; }
+  public final void writeU16(int u) { check(2); b[ptr++] = (byte)(u >> 8);
+                                      b[ptr++] = (byte)u; }
+  public final void writeU32(int u) { check(4); b[ptr++] = (byte)(u >> 24);
+                                      b[ptr++] = (byte)(u >> 16);
+                                      b[ptr++] = (byte)(u >> 8);
+                                      b[ptr++] = (byte)u; }
+
+  public final void writeS8( int s) { writeU8( s); }
+  public final void writeS16(int s) { writeU16(s); }
+  public final void writeS32(int s) { writeU32(s); }
+
+  // writeString() writes a string - a U32 length followed by the data.
+
+  public final void writeString(String str) {
+    int len = str.length();
+    writeU32(len);
+    for (int i = 0; i < len;) {
+      int j = i + check(1, len - i);
+      while (i < j) {
+	b[ptr++] = (byte)str.charAt(i++);
+      }
+    }
+  }
+
+  public final void pad(int bytes) {
+    while (bytes-- > 0) writeU8(0);
+  }
+
+  public final void skip(int bytes) {
+    while (bytes > 0) {
+      int n = check(1, bytes);
+      ptr += n;
+      bytes -= n;
+    }
+  }
+
+  // writeBytes() writes an exact number of bytes from an array at an offset.
+
+  public void writeBytes(byte[] data, int offset, int length) {
+    int offsetEnd = offset + length;
+    while (offset < offsetEnd) {
+      int n = check(1, offsetEnd - offset);
+      System.arraycopy(data, offset, b, ptr, n);
+      ptr += n;
+      offset += n;
+    }
+  }
+
+  // writeOpaqueN() writes a quantity without byte-swapping.  Because java has
+  // no byte-ordering, we just use big-endian.
+
+  public final void writeOpaque8( int u) { writeU8( u); }
+  public final void writeOpaque16(int u) { writeU16(u); }
+  public final void writeOpaque32(int u) { writeU32(u); }
+  public final void writeOpaque24A(int u) { check(3);
+                                            b[ptr++] = (byte)(u >> 24);
+                                            b[ptr++] = (byte)(u >> 16);
+                                            b[ptr++] = (byte)(u >> 8); }
+  public final void writeOpaque24B(int u) { check(3);
+                                            b[ptr++] = (byte)(u >> 16);
+                                            b[ptr++] = (byte)(u >> 8);
+                                            b[ptr++] = (byte)u; }
+
+  // length() returns the length of the stream.
+
+  abstract public int length();
+
+  // flush() requests that the stream be flushed.
+
+  public void flush() {}
+
+  // getptr(), getend() and setptr() are "dirty" methods which allow you to
+  // manipulate the buffer directly.  This is useful for a stream which is a
+  // wrapper around an underlying stream.
+
+  public final byte[] getbuf() { return b; }
+  public final int getptr() { return ptr; }
+  public final int getend() { return end; }
+  public final void setptr(int p) { ptr = p; }
+
+  // overrun() is implemented by a derived class to cope with buffer overrun.
+  // It ensures there are at least itemSize bytes of buffer space.  Returns
+  // the number of items which fit (up to a maximum of nItems).  itemSize is
+  // supposed to be "small" (a few bytes).
+
+  abstract protected int overrun(int itemSize, int nItems);
+
+  protected OutStream() {}
+  protected byte[] b;
+  protected int ptr;
+  protected int end;
+}
diff --git a/java/src/com/tigervnc/rdr/ZlibInStream.java b/java/src/com/tigervnc/rdr/ZlibInStream.java
new file mode 100644
index 0000000..64de00a
--- /dev/null
+++ b/java/src/com/tigervnc/rdr/ZlibInStream.java
@@ -0,0 +1,113 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd.  All Rights Reserved.
+ * 
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ * 
+ * This software 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 software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
+ * USA.
+ */
+
+//
+// A ZlibInStream reads from a zlib.io.InputStream
+//
+
+package com.tigervnc.rdr;
+
+public class ZlibInStream extends InStream {
+
+  static final int defaultBufSize = 16384;
+
+  public ZlibInStream(int bufSize_) {
+    bufSize = bufSize_;
+    b = new byte[bufSize];
+    ptr = end = ptrOffset = 0;
+    inflater = new java.util.zip.Inflater();
+  }
+
+  public ZlibInStream() { this(defaultBufSize); }
+
+  public void setUnderlying(InStream is, int bytesIn_) {
+    underlying = is;
+    bytesIn = bytesIn_;
+    ptr = end = 0;
+  }
+
+  public void reset() {
+    ptr = end = 0;
+    if (underlying == null) return;
+
+    while (bytesIn > 0) {
+      decompress();
+      end = 0; // throw away any data
+    }
+    underlying = null;
+  }
+
+  public int pos() { return ptrOffset + ptr; }
+
+  protected int overrun(int itemSize, int nItems) {
+    if (itemSize > bufSize)
+      throw new Exception("ZlibInStream overrun: max itemSize exceeded");
+    if (underlying == null)
+      throw new Exception("ZlibInStream overrun: no underlying stream");
+
+    if (end - ptr != 0)
+      System.arraycopy(b, ptr, b, 0, end - ptr);
+
+    ptrOffset += ptr;
+    end -= ptr;
+    ptr = 0;
+
+    while (end < itemSize) {
+      decompress();
+    }
+
+    if (itemSize * nItems > end)
+      nItems = end / itemSize;
+
+    return nItems;
+  }
+
+  // decompress() calls the decompressor once.  Note that this won't
+  // necessarily generate any output data - it may just consume some input
+  // data.  Returns false if wait is false and we would block on the underlying
+  // stream.
+
+  private void decompress() {
+    try {
+      underlying.check(1);
+      int avail_in = underlying.getend() - underlying.getptr();
+      if (avail_in > bytesIn)
+        avail_in = bytesIn;
+
+      if (inflater.needsInput()) {
+        inflater.setInput(underlying.getbuf(), underlying.getptr(), avail_in);
+      }
+
+      int n = inflater.inflate(b, end, bufSize - end); 
+
+      end += n;
+      if (inflater.needsInput()) {
+        bytesIn -= avail_in;
+        underlying.setptr(underlying.getptr() + avail_in);
+      }
+    } catch (java.util.zip.DataFormatException e) {
+      throw new Exception("ZlibInStream: inflate failed");
+    }
+  }
+
+  private InStream underlying;
+  private int bufSize;
+  private int ptrOffset;
+  private java.util.zip.Inflater inflater;
+  private int bytesIn;
+}