checkpoint
authoradam <adam@megacz.com>
Mon, 27 Feb 2006 09:38:56 +0000 (10:38 +0100)
committeradam <adam@megacz.com>
Mon, 27 Feb 2006 09:38:56 +0000 (10:38 +0100)
src/edu/berkeley/obits/AtmelSerial.java
src/edu/berkeley/obits/Bits.java [new file with mode: 0644]
src/edu/berkeley/obits/device/atmel/Wires.java [new file with mode: 0644]
src/org/ibex/util/Basket.java [new file with mode: 0644]
src/org/ibex/util/Encode.java [new file with mode: 0644]
src/org/ibex/util/Log.java [new file with mode: 0644]

index 464f0c0..6c3ee4f 100644 (file)
@@ -2,7 +2,6 @@ package edu.berkeley.obits;
 
 import edu.berkeley.obits.device.atmel.*;
 import org.ibex.util.*;
-import org.ibex.graphics.Picture;
 import java.io.*;
 import java.util.*;
 import gnu.io.*;
diff --git a/src/edu/berkeley/obits/Bits.java b/src/edu/berkeley/obits/Bits.java
new file mode 100644 (file)
index 0000000..ee2c6b5
--- /dev/null
@@ -0,0 +1,68 @@
+package edu.berkeley.obits;
+
+public abstract class Bits {
+    /*
+    public abstract boolean get(long bit);
+    public abstract void    set(long bit, boolean val);
+
+    public int get(long bit, int len) {
+        int ret = 0;
+        for(long i=bit; i<bit+len; i++) ret = (ret << 1) | get(i);
+        return ret;
+    }
+    public void set(long bit, int len, int val) {
+        for(long i=bit+len-1; i>=bit; i--) {
+            set(i, (val & 1) != 0);
+            val >>= 1;
+        }
+    }
+
+    public final boolean get(int offset, int bit) { return get(offset*8 + bit); }
+    public final int     get(int offset, int bit, int len) { return get(offset*8 + bit, num); }
+    public final void    set(int offset, int bit, boolean b) { set(offset*8 + bit, b); }
+    public final void    set(int offset, int bit, int len, int val) { set(offset*8 + bit, num, val); }
+
+    public static class Offset extends Bits {
+        private final Bits bits;
+        private final long off;
+
+        public Offset(Bits bits, long offset) { this.off = offset; this.bits = bits; }
+        public Offset(Bits bits, int offset, int bit) { this(bits, offset*8+bit); }
+
+        public boolean get(long bit) { return bits.get(bit+off); }
+        public int     get(long bit, int len) { return bits.get(bit+off, len); }
+        public void    set(long bit, boolean val) { bits.set(bit+off, val); }
+        public void    set(long bit, int len, int val) { bits.set(bit+off, len, val); }
+    }
+
+    public static class Arr extends Bits {
+        private byte[] bits;
+        public Bits(int capacity) { this.bits = new byte[(capacity / 8) + (capacity%8 == 0 ? 0 : 1)]; }
+
+        public boolean get(long bit) {
+            if (bit / 8 >= bits.length) return false;
+            int ret = bits[bit/8];
+            ret >> 8-(bit-((bit/8)*8));
+            return (ret & 1) != 0;
+        }
+
+        public void set(long bit, boolean b) {
+            if (bit / 8 >= bits.length) {
+                if (!b) return;
+                byte[] bits2 = new byte[Math.max((bit/8)+1, (bits.length * 2))];
+                System.arraycopy(bits, 0, bits2, 0, bits.length);
+                bits = bits2;
+                set(bit, b);
+                return;
+            }
+            byte mask = (byte)(1 << (8-(bit-((bit/8)*8))));
+            if (b) {
+                bits[bit/8] |=  mask;
+            } else {
+                bits[bit/8] &= ~mask;
+            }
+        }
+        
+    }
+    */
+}
diff --git a/src/edu/berkeley/obits/device/atmel/Wires.java b/src/edu/berkeley/obits/device/atmel/Wires.java
new file mode 100644 (file)
index 0000000..283d929
--- /dev/null
@@ -0,0 +1,24 @@
+package edu.berkeley.obits.device.atmel;
+
+import edu.berkeley.obits.*;
+
+public enum Wires {
+
+    NW, SW, NE, SE,
+
+    N, S, E, W,
+
+    CM,
+
+    XL, YL,
+
+    C, R,
+
+    F,
+    BO,
+
+    XO, YO,
+
+    L0, L1, L2, L3, L4;
+
+}
diff --git a/src/org/ibex/util/Basket.java b/src/org/ibex/util/Basket.java
new file mode 100644 (file)
index 0000000..baee97c
--- /dev/null
@@ -0,0 +1,359 @@
+// Copyright 2000-2005 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
+
+package org.ibex.util;
+
+import java.io.Serializable;
+
+public interface Basket extends Serializable {
+    public boolean containsValue(Object object);
+    public void clear();
+    public int size();
+    public void remove(Object object);
+
+    public interface List extends Basket {
+        public void add(Object object);
+        public void add(int index, Object object);
+        public Object set(int index, Object object);
+        public Object get(int index);
+        public Object remove(int index);
+        public int indexOf(Object object);
+        public void reverse();
+        public void sort(CompareFunc c);
+    }
+
+    public interface RandomAccess extends List { }
+
+    public interface Queue extends Basket {
+        public void   enqueue(Object o);
+        public Object dequeue();
+    }
+
+    public interface Stack extends Basket {
+        public Object pop();
+        public Object peek();
+        public void push(Object object);
+    }
+
+    public interface Map extends Basket {
+        public boolean containsKey(Object key);
+        public Object get(Object key);
+        public Object put(Object key, Object value);
+    }
+
+    public interface CompareFunc {
+        public int compare(Object a, Object b);
+    }
+
+
+    // Implementations ////////////////////////////////////////////////////////
+
+    public class Array implements RandomAccess, Stack, Queue {
+        private static final long serialVersionUID = 1233428092L;
+
+        private Object[] o;
+        private int size = 0;
+
+        public Array() { this(10); }
+        public Array(int initialCapacity) { o = new Object[initialCapacity]; }
+        public Array(Object entry) { this(1); add(entry); }
+
+        public void   enqueue(Object o) { add(o); }
+
+        // FEATURE: make this more efficient with general wraparound
+        public Object dequeue() {
+            if (size==0) return null;
+            Object ret = o[0];
+            for(int i=1; i<size; i++) o[i-1]=o[i];
+            return ret;
+        }
+
+        public void add(Object obj) { add(size, obj); }
+        public void add(int i, Object obj) {
+            size(size + 1);
+            if (size - 1 > i) System.arraycopy(o, i, o, size, size - i - 1);
+            o[i] = obj; size++;
+        }
+        public Object set(int i, Object obj) {
+            if (i >= o.length) throw new IndexOutOfBoundsException(
+                "index "+i+" is beyond list boundary "+size);
+            Object old = o[i]; o[i] = obj;
+           size = Math.max(i+1, size);
+           return old;
+        }
+        public Object get(int i) {
+            if (i >= size) throw new IndexOutOfBoundsException(
+                "index "+i+" is beyond list boundary "+size);
+            return o[i];
+        }
+        public Object remove(int i) {
+            if (i >= size || i < 0) throw new IndexOutOfBoundsException(
+                "index "+i+" is beyond list boundary "+size);
+            Object old = o[i]; o[i] = null;
+            if (size - 1 > i) System.arraycopy(o, i + 1, o, i, size - i - 1);
+            size--; return old;
+        }
+        public void remove(Object obj) { remove(indexOf(obj)); }
+
+        public int indexOf(Object obj) {
+            for (int i=0; i < size; i++)
+                if ((obj == null && o[i] == null) || obj.equals(o[i])) return i;
+            return -1;
+        }
+
+        public boolean containsValue(Object obj) {
+            for (int i=0; i < size; i++)
+                if ((obj == null && o[i] == null) || obj.equals(o[i])) return true;
+            return false;
+        }
+        public void clear() { for (int i=0; i < size; i++) o[i] = null; size = 0; }
+        public int size() { return size; }
+        public void size(int s) {
+            if (o.length >= s) return;
+            Object[] newo = new Object[s];
+            System.arraycopy(o, 0, newo, 0, size);
+            o = newo;
+        }
+
+        public void reverse() {
+            Object tmp; int max = (int)Math.floor((double)size / 2);
+            for (int i=0; i < size; i++) { tmp = o[i]; o[i] = o[size - i]; o[size - i] = tmp; }
+        }
+
+        public void sort(CompareFunc c) { sort(this, null, c, 0, size); }
+
+        public static void sort(Array a, Array b, CompareFunc c, int start, int end) {
+            Object tmpa, tmpb = null;
+            if(start >= end) return;
+            if(end-start <= 6) {
+                for(int i=start+1;i<=end;i++) {
+                    tmpa = a.o[i];
+                    if (b != null) tmpb = b.o[i];
+                    int j;
+                    for(j=i-1;j>=start;j--) {
+                        if(c.compare(a.o[j],tmpa) <= 0) break;
+                        a.o[j+1] = a.o[j];
+                        if (b != null) b.o[j+1] = b.o[j];
+                    }
+                    a.o[j+1] = tmpa;
+                    if (b != null) b.o[j+1] = tmpb;
+                }
+                return;
+            }
+            Object pivot = a.o[end];
+            int lo = start - 1;
+            int hi = end;
+            do {
+                while(c.compare(a.o[++lo],pivot) < 0) { }
+                while((hi > lo) && c.compare(a.o[--hi],pivot) > 0) { }
+                swap(a, lo,hi);
+                if (b != null) swap(b, lo,hi);
+            } while(lo < hi);
+
+            swap(a, lo,end);
+            if (b != null) swap(b, lo,end);
+            sort(a, b, c, start, lo-1);
+            sort(a, b, c, lo+1, end);
+        }
+
+        private static final void swap(Array vec, int a, int b) {
+            if(a != b) {
+                Object tmp = vec.o[a];
+                vec.o[a] = vec.o[b];
+                vec.o[b] = tmp;
+            }
+        }
+
+        public Object peek() {
+            if (size < 1) throw new IndexOutOfBoundsException("array is empty");
+            return o[size - 1];
+        }
+        public Object pop() { return remove(size - 1); }
+        public void push(Object o) { add(o); }
+    }
+
+    /** Implementation of a hash table using Radke's quadratic residue
+     *  linear probing. Uses a single array to store all entries.
+     *
+     * <p>See C. Radke, Communications of the ACM, 1970, 103-105</p>
+     *
+     * @author adam@ibex.org, crawshaw@ibex.org
+     */
+    public class Hash implements Basket, Map {
+        static final long serialVersionUID = 3948384093L;
+
+        /** Used internally to record used slots. */
+        final Object placeholder = new java.io.Serializable() { private static final long serialVersionUID = 1331L; };
+
+        /** When <tt>loadFactor * usedslots > num_slots</tt>, call
+         *  <tt>rehash()</tt>. */
+        final float loadFactor;
+
+        /** Used to determine the number of array slots required by each
+         *  mapping entry. */
+        final int indexmultiple;
+
+        /** Number of currently active entries. */
+        private int size = 0;
+
+        /** Number of placeholders in entries[]. */
+        private int placeholders = 0;
+
+        /** Array of mappings.
+         *
+         *  <p>Each map requires multiple slots in the array, and subclasses
+         *  can vary the number of required slots without reimplementing all
+         *  the functions of this class by changing the value of
+         *  <tt>indexmultiple</tt>.</p>
+         *
+         *  Default implementation uses <tt>indexmultiple == 1</tt>, and
+         *  stores only the keys in <tt>entries</tt>.
+         */
+        private Object[] entries = null;
+
+        public Hash() { this(16, 0.75F); }
+        public Hash(int cap, float load) { this(2, cap, load); }
+        public Hash(int indexmultiple, int initialCapacity, float loadFactor) {
+            // using a pseudoprime in the form 4x+3 ensures full coverage
+            initialCapacity = (initialCapacity / 4) * 4 + 3;
+            this.loadFactor = loadFactor;
+            this.indexmultiple = indexmultiple;
+            this.entries = new Object[initialCapacity * indexmultiple];
+        }
+
+        public int size() { return size; }
+        public void clear() { for (int i = 0; i<entries.length; i++) entries[i] = null; size = 0; }
+
+        public boolean containsKey(Object k) { return indexOf(k) >= 0; }
+
+        /** <b>Warning:</b> This function is equivalent here to
+         *  <tt>containsKey()</tt>. For a value map, use Basket.HashMap. */
+        public boolean containsValue(Object k) { return containsKey(k); }
+
+
+        // UGLY
+        public Object[] dumpkeys() {
+            Object[] ret = new Object[size];
+            int pos = 0;
+            for(int i=0; i<entries.length; i+=indexmultiple)
+                if (placeholder!=entries[i] && entries[i]!=null) {
+                    ret[pos++] = entries[i];
+                }
+            return ret;
+        }
+
+        public void remove(Object k) { remove(indexOf(k)); }
+        public void remove(int dest) {
+            if (dest < 0) return;
+            // instead of removing, insert a placeholder
+            entries[dest] = placeholder;
+            for (int inc=1; inc < indexmultiple; inc++) entries[dest + inc] = null;
+            size--;
+            placeholders++;
+        }
+
+        public Object get(Object key) { return get(key, 0); }
+        public Object get(Object key, int whichval) {
+            int i = indexOf(key);
+            return i >= 0 ? entries[i + 1 + whichval] : null;
+        }
+
+        public Object put(Object key, Object value) { return put(key, value, 0); }
+        public Object put(Object key, Object value, int whichval) {
+            if (loadFactor * (size + placeholders) > entries.length) rehash();
+            int dest = indexOf(key);
+            Object old = null;
+            if (dest < 0) {
+                dest = -1 * (dest + 1);
+                if (entries[dest] != placeholder) size++;
+                entries[dest] = key;
+                for(int i=1; i<indexmultiple; i++) entries[dest+i] = null;
+            } else {
+                old = entries[dest + 1 + whichval];
+            }
+            entries[dest + 1 + whichval] = value;
+            return old;
+        }
+
+        /*
+        public boolean containsKey(Object k) { return super.containsValue(k); }
+        public boolean containsValue(Object v) {
+            for (int i=0; i < entries.length/indexmultiple; i++)
+                if ((i == 0 || entries[i * indexmultiple] != null) && // exception for null key
+                    !equals(placeholder, entries[i * indexmultiple]) &&
+                    v == null ? entries[i + 1] == null : v.equals(entries[i + 1]))
+                        return true;
+            return false;
+        }
+        */
+
+        /** Compares two keys for equality. <tt>userKey</tt> is the key
+         *  passed to the map, <tt>storedKey</tt> is from the map.
+         *
+         * <p>Default implementation provides standard Java equality
+         * testing, <tt>k1 == null ? k2 == null : k1.equals(k2)</tt>.</p>
+         */
+        protected boolean equals(Object userKey, Object storedKey) {
+            return userKey == null ? storedKey == null : userKey.equals(storedKey);
+        }
+
+        /** Returns the array position in <tt>entries</tt>, adjusted for
+         *  <tt>indexmultiple</tt>, where <tt>k</tt> is/should be stored
+         *  using Radke's quadratic residue linear probing. 
+         *
+         *  <p><tt>entries[0]</tt> is a hard coded exception for the null
+         *  key.</p>
+         *  
+         * <p>If the key is not found, this function returns
+         * <tt>(-1 * indexPosition) - 1</tt>, where <tt>indexPosition</tt>
+         * is the array position where the mapping should be stored.</p>
+         *
+         * <p>Uses <tt>placeholder</tt> as a placeholder object, and
+         * compares keys using <tt>equals(Object, Object)</tt>.</p>
+         */
+        private int indexOf(Object k) {
+            // special case null key
+            if (k == null) return equals(placeholder, entries[0]) ? -1 : 0;
+
+            int hash = k == null ? 0 : k.hashCode();
+            final int orig = Math.abs(hash) % (entries.length / indexmultiple);
+            int dest = orig * indexmultiple;
+            int tries = 1;
+            boolean plus = true;
+
+            while (entries[dest] != null) {
+                if (equals(k, entries[dest])) return dest;
+                dest = Math.abs((orig + (plus ? 1 : -1) * tries * tries) % (entries.length / indexmultiple)) * indexmultiple;
+                if (plus) tries++;
+                plus = !plus;
+            }
+            return -1 * dest - 1;
+        }
+
+        /** Doubles the available entry space, first by packing the data
+         *  set (removes <tt>placeholder</tt> references) and if necessary
+         *  by increasing the size of the <tt>entries</tt> array.
+         */
+        private void rehash() {
+            Object[] oldentries = entries;
+            entries = new Object[oldentries.length * indexmultiple];
+
+            for (int i=0; i < (oldentries.length/indexmultiple); i++) {
+                int pos = i * indexmultiple;
+                if (pos > 0 && oldentries[pos] == null) continue;
+                if (oldentries[pos] == placeholder) continue;
+
+                // dont adjust any of the support entries
+                int dest = indexOf(oldentries[pos]);
+                dest = -1 * dest - 1; size++;  // always new entry
+                for (int inc=0; inc < indexmultiple; inc++)
+                    entries[dest + inc] = oldentries[pos + inc];
+            }
+            placeholders = 0;
+        }
+    }
+
+    // FIXME, BalancedTree goes here
+
+}
diff --git a/src/org/ibex/util/Encode.java b/src/org/ibex/util/Encode.java
new file mode 100644 (file)
index 0000000..9d38f73
--- /dev/null
@@ -0,0 +1,469 @@
+// Copyright 2000-2005 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
+
+package org.ibex.util;
+
+import java.io.*;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+/** General <tt>String</tt> and <tt>byte[]</tt> processing functions,
+ *  including Base64 and a safe filename transform.
+ *
+ *  @author adam@ibex.org
+ */
+public final class Encode {
+
+    public static class QuotedPrintable {
+        public static String decode(String s, boolean lax) {
+        //
+        //   =XX  -> hex representation, must be uppercase
+        //   9, 32, 33-60, 62-126 can be literal
+        //   9, 32 at end-of-line must get encoded
+        //   trailing whitespace must be deleted when decoding
+        //   =\n = soft line break
+        //   lines cannot be more than 76 chars long
+        //
+
+            // lax is used for RFC2047 headers; removes restrictions on which chars you can encode
+            return s;
+        }
+    }
+
+
+    public static class RFC2047 {
+        public static String decode(String s) {
+            /*
+            try { while (s.indexOf("=?") != -1) {
+                String pre = s.substring(0, s.indexOf("=?"));
+                s = s.substring(s.indexOf("=?") + 2);
+
+                // MIME charset; FIXME use this
+                String charset = s.substring(0, s.indexOf('?')).toLowerCase();
+                s = s.substring(s.indexOf('?') + 1);
+
+                String encoding = s.substring(0, s.indexOf('?')).toLowerCase();
+                s = s.substring(s.indexOf('?') + 1);
+
+                String encodedText = s.substring(0, s.indexOf("?="));
+
+                if (encoding.equals("b"))      encodedText = new String(Base64.decode(encodedText));
+
+                // except that ANY char can be endoed (unlike real qp)
+                else if (encoding.equals("q")) encodedText = MIME.QuotedPrintable.decode(encodedText, true);
+                else Log.warn(MIME.class, "unknown RFC2047 encoding \""+encoding+"\"");
+
+                String post = s.substring(s.indexOf("?=") + 2);
+                s = pre + encodedText + post;
+
+                // FIXME re-encode when transmitting
+
+            } } catch (Exception e) {
+                Log.warn(MIME.class, "error trying to decode RFC2047 encoded-word: \""+s+"\"");
+                Log.warn(MIME.class, e);
+            }
+            */
+            return s;
+        }
+    }
+
+
+    public static long twoFloatsToLong(float a, float b) {
+        return ((Float.floatToIntBits(a) & 0xffffffffL) << 32) | (Float.floatToIntBits(b) & 0xffffffffL); }
+    public static float longToFloat1(long l) { return Float.intBitsToFloat((int)((l >> 32) & 0xffffffff)); }
+    public static float longToFloat2(long l) { return Float.intBitsToFloat((int)(l & 0xffffffff)); }
+
+    private static final char[] fn =
+        new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+    public static String toFilename(String s) {
+        StringBuffer sb = new StringBuffer();
+        try {
+            byte[] b = s.getBytes("UTF-8");
+            for(int i=0; i<b.length; i++) {
+                char c = (char)(b[i] & 0xff);
+                if (c == File.separatorChar || c < 32 || c > 126 || c == '%' || (i == 0 && c == '.'))
+                    sb.append("%" + fn[(b[i] & 0xf0) >> 8] + fn[b[i] & 0xf]);
+                else sb.append(c);
+            }
+            return sb.toString();
+        } catch (UnsupportedEncodingException uee) {
+            throw new Error("this should never happen; Java spec mandates UTF-8 support");
+        }
+    }
+
+    public static String fromFilename(String s) {
+        StringBuffer sb = new StringBuffer();
+        byte[] b = new byte[s.length() * 2];
+        int bytes = 0;
+        for(int i=0; i<s.length(); i++) {
+            char c = s.charAt(i);
+            if (c == '%') b[bytes++] = (byte)Integer.parseInt(("" + s.charAt(++i) + s.charAt(++i)), 16);
+            else b[bytes++] = (byte)c;
+        }
+        try {
+            return new String(b, 0, bytes, "UTF-8");
+        } catch (UnsupportedEncodingException uee) {
+            throw new Error("this should never happen; Java spec mandates UTF-8 support");
+        }
+    }
+
+    public static class Ascii {
+        public static class In extends InputStream {
+            public  final int radix;
+            private final Reader reader;
+            private int blen = 0;
+            private byte[] bbuf = new byte[1024 * 16];
+            private int clen = 0;
+            private char[] cbuf = new char[1024 * 16];
+            public In(int radix, InputStream is) {
+                // FIXME: radix must be a power of 2
+                this.radix = radix;
+                this.reader = new InputStreamReader(is);
+            }
+            public int read() throws IOException {
+                byte[] buf = new byte[1];
+                while(true) {
+                    int numread = read(buf, 0, 1);
+                    if (numread<0) return -1;
+                    if (numread>0) return (buf[0] & 0xff);
+                }
+            }
+            public long skip(long n) throws IOException {
+                while(blen<=0) if (!fillb()) return -1;
+                int numskip = Math.min((int)n, blen);
+                if (blen > numskip) System.arraycopy(bbuf, numskip, bbuf, 0, blen-numskip);
+                blen -= numskip;
+                return numskip;
+            }                
+            public int read(byte[] b) throws IOException { return read(b, 0, b.length); }
+            public int available() { return blen; }
+            public boolean markSupported() { return false; }
+            public void close() throws IOException { reader.close(); }
+            public int read(byte[] buf, int off, int len) throws IOException {
+                while(blen<=0) if (!fillb()) return -1;
+                int numread = Math.min(len, blen);
+                System.arraycopy(bbuf, 0, buf, off, numread);
+                if (numread < blen) System.arraycopy(bbuf, numread, bbuf, 0, blen-numread);
+                blen -= numread;
+                return numread;
+            }
+            public boolean fillc() throws IOException {
+                int numread = reader.read(cbuf, clen, cbuf.length - clen);
+                if (numread == -1) return false;
+                int j = 0;
+                for(int i=0; i<numread; i++) {
+                    if (!Character.isWhitespace(cbuf[clen+i]))
+                        cbuf[clen+(j++)] = cbuf[clen+i];
+                }
+                clen += j;
+                return true;
+            }
+            public boolean fillb() throws IOException {
+                int minChars;
+                int bytesPerMinChars;
+                switch(radix) {
+                    case 2: { minChars = 8; bytesPerMinChars = 1; break; }
+                    case 16: { minChars = 2; bytesPerMinChars = 1; break; }
+                    default: throw new Error("unsupported");
+                }
+                while(clen < minChars) if (!fillc()) return false;
+                int pos = 0;
+                while(pos <= clen - minChars) {
+                    bbuf[blen++] = (byte)Integer.parseInt(new String(cbuf, pos, minChars), radix);
+                    pos += minChars;
+                }
+                System.arraycopy(cbuf, pos, cbuf, 0, clen-pos);
+                clen -= pos;
+                return true;
+            }
+        }
+    }
+
+    private static final byte[] encB64 = {
+        (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
+        (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
+        (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U',
+        (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
+        (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
+        (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
+        (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u',
+        (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', (byte)'0', (byte)'1',
+        (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', (byte)'7', (byte)'8',
+        (byte)'9', (byte)'+', (byte)'/'
+    };
+
+    // FIXME could be far more efficient
+    public static class Base64InputStream extends ByteArrayInputStream {
+        public Base64InputStream(String s) { super(fromBase64(s.getBytes())); }
+    }
+
+    public static byte[] toBase64(String data) { return toBase64(data.getBytes()); }
+
+    /** Encode the input data producong a base 64 encoded byte array.
+     *  @return A byte array containing the base 64 encoded data. */
+    public static byte[] toBase64(byte[] data) {
+        byte[]  bytes;
+                
+        int modulus = data.length % 3;
+        if (modulus == 0) {
+            bytes = new byte[4 * data.length / 3];
+        } else {
+            bytes = new byte[4 * ((data.length / 3) + 1)];
+        }
+
+        int dataLength = (data.length - modulus);
+        int a1, a2, a3;
+        for (int i = 0, j = 0; i < dataLength; i += 3, j += 4) {
+            a1 = data[i] & 0xff;
+            a2 = data[i + 1] & 0xff;
+            a3 = data[i + 2] & 0xff;
+            
+            bytes[j] = encB64[(a1 >>> 2) & 0x3f];
+            bytes[j + 1] = encB64[((a1 << 4) | (a2 >>> 4)) & 0x3f];
+            bytes[j + 2] = encB64[((a2 << 2) | (a3 >>> 6)) & 0x3f];
+            bytes[j + 3] = encB64[a3 & 0x3f];
+        }
+
+        int b1, b2, b3;
+        int d1, d2;
+        switch (modulus) {
+                case 0:         /* nothing left to do */
+                    break;
+                case 1:
+                    d1 = data[data.length - 1] & 0xff;
+                    b1 = (d1 >>> 2) & 0x3f;
+                    b2 = (d1 << 4) & 0x3f;
+
+                    bytes[bytes.length - 4] = encB64[b1];
+                    bytes[bytes.length - 3] = encB64[b2];
+                    bytes[bytes.length - 2] = (byte)'=';
+                    bytes[bytes.length - 1] = (byte)'=';
+                    break;
+                case 2:
+                    d1 = data[data.length - 2] & 0xff;
+                    d2 = data[data.length - 1] & 0xff;
+
+                    b1 = (d1 >>> 2) & 0x3f;
+                    b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f;
+                    b3 = (d2 << 2) & 0x3f;
+
+                    bytes[bytes.length - 4] = encB64[b1];
+                    bytes[bytes.length - 3] = encB64[b2];
+                    bytes[bytes.length - 2] = encB64[b3];
+                    bytes[bytes.length - 1] = (byte)'=';
+                    break;
+            }
+
+        return bytes;
+    }
+
+
+    private static final byte[] decB64 = new byte[128];
+    static {
+        for (int i = 'A'; i <= 'Z'; i++) decB64[i] = (byte)(i - 'A');
+        for (int i = 'a'; i <= 'z'; i++) decB64[i] = (byte)(i - 'a' + 26);
+        for (int i = '0'; i <= '9'; i++) decB64[i] = (byte)(i - '0' + 52);
+        decB64['+'] = 62;
+        decB64['/'] = 63;
+    }
+
+    /** Decode base 64 encoded input data.
+     *  @return A byte array representing the decoded data. */
+    public static byte[] fromBase64(byte[] data) {
+        byte[]  bytes;
+        byte    b1, b2, b3, b4;
+
+        if (data[data.length - 2] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 1];
+        else if (data[data.length - 1] == '=') bytes = new byte[(((data.length / 4) - 1) * 3) + 2];
+        else bytes = new byte[((data.length / 4) * 3)];
+
+        for (int i = 0, j = 0; i < data.length - 4; i += 4, j += 3) {
+            b1 = decB64[data[i]];
+            b2 = decB64[data[i + 1]];
+            b3 = decB64[data[i + 2]];
+            b4 = decB64[data[i + 3]];
+            
+            bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
+            bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
+            bytes[j + 2] = (byte)((b3 << 6) | b4);
+        }
+
+        if (data[data.length - 2] == '=') {
+            b1 = decB64[data[data.length - 4]];
+            b2 = decB64[data[data.length - 3]];
+            bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
+        } else if (data[data.length - 1] == '=') {
+            b1 = decB64[data[data.length - 4]];
+            b2 = decB64[data[data.length - 3]];
+            b3 = decB64[data[data.length - 2]];
+            bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
+            bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
+        } else {
+            b1 = decB64[data[data.length - 4]];
+            b2 = decB64[data[data.length - 3]];
+            b3 = decB64[data[data.length - 2]];
+            b4 = decB64[data[data.length - 1]];
+            bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
+            bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
+            bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
+        }
+        return bytes;
+    }
+
+    /** Decode a base 64 encoded String.
+     *  @return A byte array representing the decoded data. */
+    public static byte[] fromBase64(String data) {
+        byte[]  bytes;
+        byte    b1, b2, b3, b4;
+
+        if (data.charAt(data.length() - 2) == '=')
+            bytes = new byte[(((data.length() / 4) - 1) * 3) + 1];
+        else if (data.charAt(data.length() - 1) == '=')
+            bytes = new byte[(((data.length() / 4) - 1) * 3) + 2];
+        else
+            bytes = new byte[((data.length() / 4) * 3)];
+
+        for (int i = 0, j = 0; i < data.length() - 4; i += 4, j += 3) {
+            b1 = decB64[data.charAt(i)];
+            b2 = decB64[data.charAt(i + 1)];
+            b3 = decB64[data.charAt(i + 2)];
+            b4 = decB64[data.charAt(i + 3)];
+            
+            bytes[j] = (byte)((b1 << 2) | (b2 >> 4));
+            bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2));
+            bytes[j + 2] = (byte)((b3 << 6) | b4);
+        }
+
+        if (data.charAt(data.length() - 2) == '=') {
+            b1 = decB64[data.charAt(data.length() - 4)];
+            b2 = decB64[data.charAt(data.length() - 3)];
+            bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4));
+        } else if (data.charAt(data.length() - 1) == '=') {
+            b1 = decB64[data.charAt(data.length() - 4)];
+            b2 = decB64[data.charAt(data.length() - 3)];
+            b3 = decB64[data.charAt(data.length() - 2)];
+            bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4));
+            bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2));
+        } else {
+            b1 = decB64[data.charAt(data.length() - 4)];
+            b2 = decB64[data.charAt(data.length() - 3)];
+            b3 = decB64[data.charAt(data.length() - 2)];
+            b4 = decB64[data.charAt(data.length() - 1)];
+            bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4));
+            bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2));
+            bytes[bytes.length - 1] = (byte)((b3 << 6) | b4);
+        }
+        return bytes;
+    }
+
+
+    /** Packs 8-bit bytes into a String of 7-bit chars.
+     *  @throws IllegalArgumentException when <tt>len</tt> is not a multiple of 7.
+     *  @return A String representing the processed bytes. */
+    public static String toStringFrom8bit(byte[] b, int off, int len) throws IllegalArgumentException {
+        if (len % 7 != 0) throw new IllegalArgumentException("len must be a multiple of 7");
+        StringBuffer ret = new StringBuffer();
+        for(int i=off; i<off+len; i += 7) {
+            long l = 0;
+            for(int j=6; j>=0; j--) {
+                l <<= 8;
+                l |= (b[i + j] & 0xff);
+            }
+            for(int j=0; j<8; j++) {
+                ret.append((char)(l & 0x7f));
+                l >>= 7;
+            }
+        }
+        return ret.toString();
+    }
+
+    /** Packs a String of 7-bit chars into 8-bit bytes.
+     *  @throws IllegalArgumentException when <tt>s.length()</tt> is not a multiple of 8.
+     *  @return A byte array representing the processed String. */
+    public static byte[] fromStringTo8bit(String s) throws IllegalArgumentException {
+        if (s.length() % 8 != 0) throw new IllegalArgumentException("string length must be a multiple of 8");
+        byte[] ret = new byte[(s.length() / 8) * 7];
+        for(int i=0; i<s.length(); i += 8) {
+            long l = 0;
+            for(int j=7; j>=0; j--) {
+                l <<= 7;
+                l |= (s.charAt(i + j) & 0x7fL);
+            }
+            for(int j=0; j<7; j++) {
+                ret[(i / 8) * 7 + j] = (byte)(l & 0xff);
+                l >>= 8;
+            }
+        }
+        return ret;
+    }
+
+    public static class JavaSourceCode {
+
+        public static final int LINE_LENGTH = 80 / 4;
+        public static void main(String[] s) throws Exception { System.out.println(encode(s[0], s[1], System.in)); }
+
+        public static InputStream decode(String s) throws IOException {
+            return new GZIPInputStream(new StringInputStream(s)); }
+        
+        private static class StringInputStream extends InputStream {
+            private final String s;
+            private final int length;
+            private int pos = 0;
+            public StringInputStream(String s) { this.s = s; this.length = s.length(); }
+            public int read() {
+                byte[] b = new byte[1];
+                int numread = read(b, 0, 1);
+                if (numread == -1) return -1;
+                if (numread == 0) throw new Error();
+                return b[0] & 0xff;
+            }
+            public int read(byte[] b, int off, int len) {
+                for(int i=off; i<off+len; i++) {
+                    if (pos>=length) return i-off;
+                    //int out = s.charAt(pos++);
+                    b[i] = (byte)s.charAt(pos++);//(byte)(out > 127 ? 127-out : out);
+                }
+                return len;
+            }
+        }
+
+        public static String encode(String packageName, String className, InputStream is) throws IOException {
+
+            // compress first, since the encoded form has more entropy
+            ByteArrayOutputStream baos;
+            OutputStream os = new GZIPOutputStream(baos = new ByteArrayOutputStream());
+
+            byte[] buf = new byte[1024];
+            while(true) {
+                int numread = is.read(buf, 0, buf.length);
+                if (numread == -1) break;
+                os.write(buf, 0, numread);
+            }
+            os.close();
+            buf = baos.toByteArray();
+            
+            StringBuffer ret = new StringBuffer();
+            ret.append("// generated by " + Encode.class.getName() + "\n\n");
+            ret.append("package " + packageName + ";\n\n");
+            ret.append("public class " + className + " {\n");
+            ret.append("    public static final String data = \n");
+            for(int pos = 0; pos<buf.length;) {
+                ret.append("        \"");
+                for(int i=0; i<LINE_LENGTH && pos<buf.length; i++) {
+                    String cs = Integer.toOctalString(buf[pos++] & 0xff);
+                    while(cs.length() < 3) cs = "0" + cs;
+                    ret.append("\\" + cs);
+                }
+                ret.append("\" +\n");
+            }
+            ret.append("    \"\";\n");
+            ret.append("}\n");
+            return ret.toString();
+        }
+
+    }
+
+}
+
diff --git a/src/org/ibex/util/Log.java b/src/org/ibex/util/Log.java
new file mode 100644 (file)
index 0000000..ebc8351
--- /dev/null
@@ -0,0 +1,244 @@
+// Copyright 2000-2005 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
+
+package org.ibex.util;
+
+import java.util.*;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.InetAddress;
+import java.text.SimpleDateFormat;
+
+// FEATURE: logging exceptions should automatically unwrap exceptions
+
+/** Easy to use logger.
+ * 
+ * @author adam@ibex.org
+ */
+public class Log {
+    private static final SimpleDateFormat formatDate = new SimpleDateFormat("EEE dd MMM yyyy");
+    private static final SimpleDateFormat formatTime = new SimpleDateFormat("[EEE HH:mm:ss] ");
+    private static final Hashtable threadAnnotations = new Hashtable();
+
+    public static boolean on            = System.getProperty("ibex.log.on", "true").equals("true");
+    public static boolean color         = System.getProperty("ibex.log.color", "true").equals("true");
+    public static boolean verbose       = System.getProperty("ibex.log.verbose", "false").equals("true");
+    public static boolean logDates      = System.getProperty("ibex.log.dates", "false").equals("true");
+    public static boolean notes         = System.getProperty("ibex.log.notes.on", "true").equals("true");
+    public static boolean stackTraces   = System.getProperty("ibex.log.stackTraces", "true").equals("true");
+    public static int maximumNoteLength = Integer.parseInt(System.getProperty("ibex.log.notes.maximumLength", (1024 * 32)+""));
+    public static boolean rpc           = false;
+    public static int lastDay = -1;
+
+    public static PrintStream logstream = System.err;
+
+    public static void flush() { logstream.flush(); }
+    public static void email(String address) { throw new Error("FIXME not supported"); }
+    public static void file(String filename) throws IOException {
+        // FIXME security
+        logstream = new PrintStream(new FileOutputStream(filename));
+    }
+    public static void tcp(String host, int port) throws IOException {
+        // FIXME security
+        logstream = new PrintStream(new Socket(InetAddress.getByName(host), port).getOutputStream());
+    }
+
+    public static void setThreadAnnotation(String s) { threadAnnotations.put(Thread.currentThread(), s); }
+
+    /** 
+     *  Notes can be used to attach log messages to the current thread
+     *  if you're not sure you want them in the log just yet.
+     *  Originally designed for retroactively logging socket-level
+     *  conversations only if an error is encountered
+     */
+    public static void note(String s) {
+        if (!notes) return;
+        StringBuffer notebuf = notebuf();
+        notebuf.append(s);
+        if (notebuf.length() > maximumNoteLength) {
+            notebuf.reverse();
+            notebuf.setLength(maximumNoteLength * 3 / 4);
+            notebuf.reverse();
+        }
+    }
+    public static void clearnotes() { if (!notes) return; notebuf().setLength(0); }
+
+    private static final Basket.Map notebufs = new Basket.Hash();
+    public static StringBuffer notebuf() {
+        StringBuffer ret = (StringBuffer)notebufs.get(Thread.currentThread());
+        if (ret == null) {
+            ret = new StringBuffer(16 * 1024);
+            notebufs.put(Thread.currentThread(), ret);
+        }
+        return ret;
+    }
+
+    /** true iff nothing has yet been logged */
+    public static boolean firstMessage = true;
+
+    /** message can be a String or a Throwable */
+    public static synchronized void echo(Object o, Object message) { log(o, message, ECHO); }
+    public static synchronized void diag(Object o, Object message) { log(o, message, DIAGNOSTIC); }
+    public static synchronized void debug(Object o, Object message) { log(o, message, DEBUG); }
+    public static synchronized void info(Object o, Object message) { log(o, message, INFO); }
+    public static synchronized void warn(Object o, Object message) { log(o, message, WARN); }
+    public static synchronized void error(Object o, Object message) { log(o, message, ERROR); }
+
+    // these two logging levels serve ONLY to change the color; semantically they are the same as DEBUG
+    private static final int DIAGNOSTIC = -2;
+    private static final int ECHO = -1;
+
+    // the usual log4j levels, minus FAIL (we just throw an Error in that case)
+    public static final int DEBUG = 0;
+    public static final int INFO = 1;
+    public static final int WARN = 2;
+    public static final int ERROR = 3;
+    public static final int SILENT = Integer.MAX_VALUE;
+    public static int level = INFO;
+
+    private static final int BLUE = 34;
+    private static final int GREEN = 32;
+    private static final int CYAN = 36;
+    private static final int RED = 31;
+    private static final int PURPLE = 35;
+    private static final int BROWN = 33;
+    private static final int GRAY = 37;
+    
+    private static String colorize(int color, boolean bright, String s) {
+        if (!Log.color) return s;
+        return
+            "\033[40;" + (bright?"1;":"") + color + "m" +
+            s +
+            "\033[0m";
+    }
+
+    private static String lastClassName = null;
+    private static synchronized void log(Object o, Object message, int level) {
+        if (level < Log.level) return;
+        if (firstMessage && !logDates) {
+            firstMessage = false;
+            logstream.println(colorize(GREEN, false, "==========================================================================="));
+
+            // FIXME later: causes problems with method pruning
+            //diag(Log.class, "Logging enabled at " + new java.util.Date());
+
+            if (color) diag(Log.class, "logging messages in " +
+                colorize(BLUE, true, "c") +
+                colorize(RED, true, "o") +
+                colorize(CYAN, true, "l") +
+                colorize(GREEN, true, "o") +
+                colorize(PURPLE, true, "r"));
+        }
+
+        String classname;
+        if (o instanceof Class) {
+            classname = ((Class)o).getName();
+            if (classname.indexOf('.') != -1) classname = classname.substring(classname.lastIndexOf('.') + 1);
+        }
+        else if (o instanceof String) classname = (String)o;
+        else classname = o.getClass().getName();
+
+        if (classname.equals(lastClassName)) classname = "";
+        else lastClassName = classname;
+        
+        if (classname.length() > (logDates ? 14 : 20)) classname = classname.substring(0, (logDates ? 14 : 20));
+        while (classname.length() < (logDates ? 14 : 20)) classname = " " + classname;
+        classname = classname + (classname.trim().length() == 0 ? "  " : ": ");
+        classname = colorize(GRAY, true, classname);
+        classname = classname.replace('$', '.');
+
+        if (logDates) {
+            Calendar cal = Calendar.getInstance();
+            if (lastDay < 0 || lastDay != cal.get(Calendar.DAY_OF_YEAR)) {
+                lastDay = cal.get(Calendar.DAY_OF_YEAR);
+                String now = formatDate.format(cal.getTime());
+                logstream.println();
+                logstream.println(colorize(GREEN, false, "=== " + now + " =========================================================="));
+            }
+            classname = formatTime.format(cal.getTime()) + classname;
+        }
+
+        String annot = (String)threadAnnotations.get(Thread.currentThread());
+        if (annot != null) classname += annot;
+
+        if (message instanceof Throwable) {
+            if (level < ERROR) level = WARN;
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ((Throwable)message).printStackTrace(new PrintStream(baos));
+            if (notes && notebuf().length() > 0) {
+                PrintWriter pw = new PrintWriter(baos);
+                pw.println();
+                pw.println("Thread notes:");
+                pw.println(notebuf().toString());
+                clearnotes();
+                pw.flush();
+            }
+            byte[] b = baos.toByteArray();
+            BufferedReader br = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(b)));
+            try {
+                if (stackTraces) {
+                    String s = null;
+                    String m = "";
+                    while((s = br.readLine()) != null) m += s + "\n";
+                    if (m.length() > 0) log(o, m.substring(0, m.length() - 1), level);
+                } else {
+                    String m = br.readLine();
+                    int ok = 0;
+                    do {
+                        String s = br.readLine();
+                        if (s == null) break;
+                        if (s.indexOf('(') != -1) {
+                            String shortened = s.substring(s.indexOf('(')+1);
+                            shortened = shortened.substring(0, shortened.indexOf(')'));
+                            m += " " + shortened;
+                            if (ok > 1) m = m.substring(0, Math.min(m.length(), 78));
+                            ok++;
+                        }
+                    } while (m.length() < 78);
+                    log(o, m, level);
+                }
+                lastClassName = "";
+            } catch (IOException e) {
+                // FEATURE: use org.ibex.io.Stream's here
+                logstream.println(colorize(RED, true, "Logger: exception thrown by ByteArrayInputStream;" +
+                                           " this should not happen"));
+            }
+            return;
+        }
+
+        String str = message.toString();
+        if (str.indexOf('\n') != -1) lastClassName = "";
+        while(str.indexOf('\t') != -1)
+            str = str.substring(0, str.indexOf('\t')) + "    " + str.substring(str.indexOf('\t') + 1);
+
+        classname = colorize(GRAY, false, classname);
+        int levelcolor = GRAY;
+        boolean bright = true;
+        switch (level) {
+            case DIAGNOSTIC:  levelcolor = GREEN; bright = false; break;
+            case ECHO:        levelcolor = BLUE;  bright = true;  break;
+            case DEBUG:       levelcolor = BROWN; bright = true;  break;
+            case INFO:        levelcolor = GRAY;  bright = false; break;
+            case WARN:        levelcolor = BROWN; bright = false; break;
+            case ERROR:       levelcolor = RED;   bright = true;  break;
+        }
+
+        while(str.indexOf('\n') != -1) {
+            logstream.println(classname + colorize(levelcolor, bright, str.substring(0, str.indexOf('\n'))));
+            classname = logDates ? "                " : "                      ";
+            classname = colorize(GRAY,false,classname);
+            str = str.substring(str.indexOf('\n') + 1);
+        }
+        logstream.println(classname + colorize(levelcolor, bright, str));
+    }
+
+}