some util classes stolen from webfunds
authorIang <iang@cacert.org>
Thu, 9 Sep 2010 00:47:50 +0000 (10:47 +1000)
committerIang <iang@cacert.org>
Thu, 9 Sep 2010 00:47:50 +0000 (10:47 +1000)
Business Logic/trunk/src/org/cacert/birdshack/util/Equals.java [new file with mode: 0644]
Business Logic/trunk/src/org/cacert/birdshack/util/Hex.java [new file with mode: 0755]
Business Logic/trunk/src/org/cacert/birdshack/util/Junk.java [new file with mode: 0644]
Business Logic/trunk/src/org/cacert/birdshack/util/Panic.java [new file with mode: 0755]

diff --git a/Business Logic/trunk/src/org/cacert/birdshack/util/Equals.java b/Business Logic/trunk/src/org/cacert/birdshack/util/Equals.java
new file mode 100644 (file)
index 0000000..2442f99
--- /dev/null
@@ -0,0 +1,251 @@
+/*
+ * Was webfunds.util.Example, contributed.
+ */
+package org.cacert.birdshack.util;
+
+import java.util.HashMap;
+
+/**
+ *  A set of equality classes, for use in equals() methods.
+ *
+ *  Similar role to org.apache.commons.lang.builder.EqualsBuilder ?
+ */
+public class Equals
+{
+    private Equals() {}
+
+    /**
+     *  Only set this if you are running tests.
+     *  Causes diags on "false" results.
+     */
+    public static void setNoisy()        { _noisy = true; }
+    public static void resetNoisy()      { _noisy = false; }
+    private static boolean _noisy = false;
+
+    /**
+     *  @return True if they are the same object
+     *          True if both null, false if only one is null.
+     */
+    public static boolean sameClass(Object one, Object two)
+    {
+        if (one == null && two == null)
+                    return true;
+        if (one == null)
+            return noisy("Obj!=Obj 1st is <null>, 2nd: ", two);
+        if (two == null)
+            return noisy("Obj!=Obj 2nd is <null>, 1st: ", one);
+
+        // both != null
+        String classone = one.getClass().getName();
+        String classtwo = two.getClass().getName();
+        if( !classone.equals(classtwo) )
+        {
+            return noisy("Class!=Class  " + classone + "!=" + classtwo);
+        }
+
+        return true;
+    }
+
+    /**
+     *  @return true if the byte arrays are equal, false if not
+     */
+    public static boolean equals(byte[] b1, byte[] b2)
+    {
+        if (b1 == null && b2 == null)
+            return true ;
+        if (b1 == null)
+            return noisy("byte[] 1st is <null>");
+        if (b2 == null)
+            return noisy("byte[] 2nd is <null>");
+        int len1 = b1.length;
+        int len2 = b2.length;
+        if (len1 != len2)
+            return noisy("byte[] len "+len1+" (1st) != "+len2+" (2nd)");
+
+        // check if the data components are equivalent
+        String s1 = new String(b1);
+        String s2 = new String(b2);
+
+        if (! s1.equals(s2))
+        {
+            return noisy("byte[] contents differ (len " + len1 + ")" +
+                         ONE + "\n" +
+                          Hex.printable(b1) + "\n" +
+                          TWO + "\n" +
+                          Hex.printable(b2) + "\n" +
+                          END);
+        }
+
+        return true ;
+    }
+
+    /**
+     *  Equals for parent objects (handles null case).
+     *  Works as long as you know at least one is the right Class
+     *  (for example, was already type-checked).
+     *  @param one, two are objects 
+     *  @return true if both null, false if one null,
+     *          false if different class, else returns one.equals(two)
+     */
+    public static boolean equals(Object one, Object two)
+    {
+        if (one == null && two == null)
+            return true;
+        if (!Equals.sameClass(one, two))   // catches one == null
+            return noisy("Obj,Obj Not Equals(classes)!");
+         
+        if (!one.equals(two))
+            return noisy("Obj,Obj Not Equals!", one, two);
+
+        return true;
+    }
+
+    /**
+     *  Equals (list) for parent objects (handles null case).
+     *
+     *  @param one, two are lists of Id
+     *  @return true if both null, false if one null,
+     *          else returns AND(one[].equals(two[]))
+     */
+    public static boolean equals(Object one[], Object two[])
+    {
+        if (one == null && two == null)
+            return true;
+        if (one == null)
+            return noisy("Obj[] 1st is <null> ( 2nd" + two.length + ")");
+        if (one == null)
+            return noisy("Obj[] 2nd is <null> ( 1st" + one.length + ")");
+        int len1 = one.length;
+        int len2 = two.length;
+        if (len1 != len2)
+            return noisy("Obj[] len "+len1+" (1st) != "+len2+" (2nd)");
+
+        for (int i = 0; i < len1; i++)
+        {
+            if (!equals(one[i], two[i]))
+                return noisy("Obj[" + i + "] not Equals!"); // dumped already
+        }
+
+        return true;
+    }
+
+
+    /**
+     *  Equals (list) for HashMaps of objects (handles null case).
+     *
+     *  @param one, two are HashMaps
+     *  @return true if both null, false if one null,
+     *          else returns AND(one[].equals(two[]))
+     */
+    public static boolean equalsHashMap(HashMap one, HashMap two)
+    {
+        if (one == null && two == null)
+            return true;
+        if (one == null)
+            return noisy("HashMap first map is <Null>");
+        if (two == null)
+            return noisy("HashMap second map is <Null>");
+
+        java.util.Set x = one.keySet();
+        int len = x.size();
+        java.util.Set y = two.keySet();
+        if (len != y.size())
+            return noisy("HashMap sizes "+len+" (1st) != "+y.size()+" (2nd)");
+
+        java.util.Iterator keys = x.iterator();
+        while (keys.hasNext())
+        {
+            Object key = keys.next();
+            Object oneObj = one.get(key);
+            Object twoObj = two.get(key);
+            /*
+             *  With HashMaps, have to check special case of
+             *  value == null versus key not present.
+             */
+            if (oneObj == null && twoObj == null)
+            {
+                if (one.containsKey(key) != two.containsKey(key))
+                    return noisy("HashMap has unmatched ref-><null>: " + key);
+            }
+            else if (oneObj == null)
+            {
+                return noisy("HashMap ref: " + key + " 1st null ", twoObj);
+            }
+            else if (twoObj == null)
+            {
+                return noisy("HashMap ref: " + key + " 2st null ", oneObj);
+            }
+            else
+            {
+                if (!oneObj.equals(twoObj))
+                {
+                    return noisy("HashMap ref: " + key, oneObj, twoObj);
+                }
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     *  Display a diag on the screen if noise is turned on.
+     */
+    private static boolean noisy(String s)
+    {
+        if (_noisy)
+        {
+            System.err.println(s);
+        }
+        return false;
+    }
+
+    /**
+     *  Display a diag on the screen if noise is turned on.
+     */
+    private static boolean noisy(String s, Object o)
+    {
+        if (_noisy)
+        {
+            System.err.println(s);
+            dump(o);
+        }
+        return false;
+    }
+
+    /**
+     *  Display a diag on the screen if noise is turned on.
+     */
+    private static void dump(Object o)
+    {
+        if (o == null)
+        {
+            System.err.println("Object: <null>");
+            return ;
+        } 
+        System.err.println("Object: " + o.getClass().getName());
+        System.err.println(o.toString());
+    }
+
+    private static final String
+        ONE = "1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ",
+        TWO = "2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 ",
+        END = "^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ";
+
+    /**
+     *  Display a diag on the screen if noise is turned on.
+     */
+    private static boolean noisy(String s, Object one, Object two)
+    {
+        if (_noisy)
+        {
+            System.err.println(s + "\n");
+            System.err.println(ONE);
+            dump(one);
+            System.err.println(TWO);
+            dump(two);
+            System.err.println(END);
+        }
+        return false;
+    }
+}
diff --git a/Business Logic/trunk/src/org/cacert/birdshack/util/Hex.java b/Business Logic/trunk/src/org/cacert/birdshack/util/Hex.java
new file mode 100755 (executable)
index 0000000..b3ef02e
--- /dev/null
@@ -0,0 +1,624 @@
+/*
+ * Was webfunds.util.Hex, contributed.
+ */
+package org.cacert.birdshack.util;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * <p>Common hex-conversion and digest routines.</p>
+ *
+ * <p>These functions returns lower-case characters when converting to
+ * hexadecimal. Functions parsing hexadecimal can accept either case.</p>.
+ *
+ * <p>byte[] methods must not change the bytes as many Immutable
+ *    classes rely on no changes being made.</p>
+ */
+public class Hex
+{
+    private static final char[] HEX_DIGITS = {
+        '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
+    };
+
+
+    /** Static methods only. */
+    private Hex() {}
+
+
+    /**
+     *  <p>
+     *  Checks whether a String is made of Hex digits.
+     *  </p><p>
+     *  A null or empty argument is ok and returns false,
+     *  as the intention is that all hex strings should
+     *  be populated.
+     *  </p>
+     */
+    public static boolean isHexAscii(String s)
+    {
+        if (null == s)   // not logically correct, but is the intention
+            return false ;
+
+        if (s.length() == 0)
+            return false ;
+
+        /*
+         *  For Hex, both upper and lower are ok.
+         */
+        for (int i = 0; i < s.length(); i++)
+        {
+            if (toDataNibble(s.charAt(i)) < 0)
+                return false;
+        }
+        return true ;
+    }
+
+
+    /**
+     *  Must not change the bytes, many Immutable classes rely on this.
+     *  Note that null is returned as the string NULL == "<NULL>".
+     */
+    public static String data2hex(byte[] data)
+    {
+        if (data == null)
+            return NULL;
+
+        int len = data.length;
+        StringBuffer buf = new StringBuffer(len*2);
+        for (int pos = 0; pos < len; pos++) {
+            buf.append(toHexDigit((data[pos]>>>4)&0x0F));
+            buf.append(toHexDigit((data[pos]    )&0x0F));
+        }
+        return buf.toString();
+    }
+    
+    public static String data2spacedhex(byte[] data)
+    {
+        return data2spacedhex(data, 1, " ");
+    }
+    
+    /**
+     *  Create a blocked hexadecimal string suitable for diagnostics
+     *  bytes=2 and space=" " will give OpenPGP fingerprint style.
+     *
+     *  @data  some binary data to turn into a printable hex string
+     *  @param bytes How many bytes in each block (1 gives 2 hex chars)
+     *  @param space is the string between each block
+     */
+    public static String data2spacedhex(byte[] data, int bytes, String space)
+    {
+        if (data == null)
+            return NULL;
+        if (data.length == 0)
+            return "";
+        if (bytes < 1)
+            bytes = 1;
+        if (space == null)
+            space = " ";
+
+        int len = data.length;
+        StringBuffer buf = new StringBuffer(len * (space.length()+bytes*2) - 1);
+        int pos = 0;
+        while (pos < len)
+        {
+            if (pos != 0) buf.append(space);
+            
+            int i = 0;
+            while (i < bytes && pos < len)
+            {
+                buf.append(toHexDigit((data[pos]>>>4)&0x0F));
+                buf.append(toHexDigit((data[pos]    )&0x0F));
+                pos++; i++;
+            }
+        }
+        return buf.toString();
+    }
+    
+    /*
+     *  <p>
+     *  Create a binary byte array with the hex characters converted.
+     *  </p><p>
+     *  Unchecked, GIGO.
+     *  </p>
+     *
+     *  @return always returns a byte array with the binary conversion,
+     *          never returns null (empty on null argument)
+     *  @see hex2dataOrNull for a checked method
+     */
+    public static byte[] hex2data(String str)
+    {
+        if (str == null)
+            return new byte[0] ;
+
+        int len = str.length();    // probably should check length
+        char hex[] = str.toCharArray();
+        byte[] buf = new byte[len/2];
+
+        for (int pos = 0; pos < len / 2; pos++)
+        {
+            byte hi = toDataNibble(hex[2*pos]);
+            byte lo = toDataNibble(hex[2*pos + 1]);
+
+            buf[pos] = (byte)( ((hi << 4) & 0xF0) | ( lo & 0x0F) );
+        }
+
+        return buf;
+    }
+
+    /**
+     *  <p>
+     *  Create a binary byte array with the hex characters converted.
+     *  </p><p>
+     *  Will fail (return null) if any non-hex chars are encountered,
+     *  or if the string is empty or null.
+     *  </p>
+     *
+     *  @return the binary converted from the hex, else null if any bads found
+     *  @see hex2dataOrNull for a unchecked method
+     */
+    public static byte[] hex2dataOrNull(String str)
+    {
+        if (str == null || str.length() == 0)
+            return null ;
+
+        int len = str.length();
+        char hex[] = str.toCharArray();
+        int buflen = len/2;
+        if (buflen * 2 != len)   // whoops, need to check odd-length strings
+            return null;
+        byte[] buf = new byte[buflen];
+
+        for (int pos = 0; pos < buflen; pos++)
+        {
+            byte hi = toDataNibble(hex[2*pos]);
+            if (hi < 0) return null;
+            byte lo = toDataNibble(hex[2*pos + 1]);
+            if (lo < 0) return null;
+
+            buf[pos] = (byte)( ((hi << 4) & 0xF0) | ( lo & 0x0F) );
+        }
+
+        return buf;
+    }
+
+    /**
+     * Returns the the hex digit corresponding to the argument.
+     *
+     * @throws ArrayIndexOutOfBoundsException
+     *         If the argument is not in range [0,15];
+     */
+    private static char toHexDigit(int i)
+    {
+        return HEX_DIGITS[i];
+    }
+
+    private static byte toDataNibble(char c)
+    {
+        if (('0' <= c) && (c <= '9' ))
+            return (byte)((byte)c - (byte)'0');
+        else if (('a' <= c) && (c <= 'f' ))
+            return (byte)((byte)c - (byte)'a' + 10);
+        else if (('A' <= c) && (c <= 'F' ))
+            return (byte)((byte)c - (byte)'A' + 10);
+        else
+            return -1;
+    }
+
+    static final String NULL = "<null>";
+
+    /**
+     * @return a byte array converted to hex, and truncated beyond some
+     *         reasonable length, long enough to avoid debugging collisions.
+     */
+    public static String quick(byte[] b, int len)
+    {
+        if (b == null)
+            return NULL;
+        String middle = "..";
+        byte[] end = new byte[3];
+
+        if (b.length < (len + (middle.length() / 2) + end.length))
+            return data2hex(b);
+
+        byte[] start = new byte[len];
+        int i, j;
+        for (j = 0; j < start.length; j++)
+            start[j] = b[j];
+
+        for (j = 0, i = b.length - end.length; i < b.length; j++, i++)
+            end[j] = b[i];
+
+        return data2hex(start) + middle + data2hex(end);
+    }
+
+    /**
+     * @return a byte array converted to hex, and truncated beyond some
+     *         reasonable length, long enough to avoid debugging collisions.
+     */
+    public static String quick(byte[] b)
+    {
+        return quick(b, 8);
+    }
+
+
+
+    /**
+     *  Test if this is basic Ascii and printable.
+     *
+     *  @return true if the string is printable
+     */
+    public static boolean isPrintable(byte[] data)
+    {
+        if (data == null)
+            return true;
+
+        int len = data.length;
+        for (int pos = 0; pos < len; pos++)
+        {
+            if (0x20 <= data[pos] && data[pos] <= 0xEF)
+                continue ;
+            if (data[pos] == '\r' || data[pos] == '\n' || data[pos] == '\t')
+                continue ;
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     *  Useful for printing descriptions, which are generally messages
+     *  to the user.  Sometimes they could be binary.
+     *
+     *  @return a string that is printable, either the original, or a hex
+     *          conversion of the original if funny chars found.
+     */
+    public static String description(byte[] data)
+    {
+        if (data == null)
+            return NULL;
+
+        int len = data.length;
+        if (isPrintable(data))
+            return new String(data);
+        else
+            return "[" + len + "] <<" + data2hex(data) + ">>";
+    }
+
+
+    /**
+     *  Useful for printing printable strings, which may be binary,
+     *  but most likely are not.
+     *
+     *  @return a string that is printable, either the original, or a hex
+     *          conversion of the original if funny chars found.
+     */
+    public static String printable(byte[] data)
+    {
+        if (data == null)
+            return NULL;
+
+        int len = data.length;
+        if (isPrintable(data))
+            return "{" + len + "} \"" + (new String(data)) + "\"";
+        else
+            return "[" + len + "] <<" + data2hex(data) + ">>";
+    }
+
+    /**
+     *  Useful for printing descriptions, which can include binary.
+     *  Does not change data ... but it used to !
+     *  @return a string that is printable, with binaries dotted out.
+     */
+    public static String dotable(byte[] data)
+    {
+        if (data == null)
+            return NULL;
+
+        int len = data.length;
+        byte[] copy = new byte[len];
+        for (int pos = 0; pos < len; pos++)
+        {
+            if (0x20 <= data[pos] && data[pos] <= 0xEF)
+                copy[pos] = data[pos];
+            else if (data[pos] == '\n' || data[pos] == '\t')
+                copy[pos] = data[pos];
+            else if (data[pos] == '\r')
+                copy[pos] = (byte)'\\';
+            else
+                copy[pos] = (byte)'.';
+        }
+        return new String(copy);
+    }
+
+    private static final byte[] dots = "...".getBytes();
+
+    /**
+     *  Useful for printing errors, which can include binary.
+     *  Turns any wierdness into hex numbers, leaves US-ASCII
+     *  printable.
+     *  Kind to estimated callers, corrects bounds on from, to.
+     *
+     *  @param data the ascii text with some dodgy chars
+     *  @param from the start position, made 0 if negative
+     *  @param to the end position, made the end if greater than length
+     *  @return a string that is printable, with binary hexed.
+     */
+    public static String hexable(byte[] data, int from, int to)
+    {
+        if (data == null)
+            return NULL;
+        if (from < 0) from = 0;
+        if (to > data.length) to = data.length;
+
+        ByteArrayOutputStream bais = new ByteArrayOutputStream();
+
+        try {
+            if (from > 0) bais.write(dots);
+
+            int len = to;
+            for (int pos = from; pos < len; pos++)
+            {
+                int b = data[pos] & 0xFF;
+                if (0x20 <= b && b < 0x7F)
+                    bais.write(b) ;
+                else if (b == '\n')
+                    bais.write("\\n\n".getBytes());
+                else if (b == '\r')
+                    bais.write("\\r\n".getBytes());
+                else if (b < 0x20)
+                {
+                    String s = "\0x" + toString((byte)b);
+                    bais.write(s.getBytes());
+                }
+                else
+                {
+                    String s = "<" + toString((byte)b) + ">";
+                    bais.write(s.getBytes());
+                }
+            }
+            if (to < data.length) bais.write(dots);
+        } catch (Exception ex) {
+           throw new Error(ex);
+        }
+        return new String( bais.toByteArray() );
+    }
+
+    /**
+     *  Useful for printing errors, which can include binary.
+     *  Turns any wierdness into hex numbers, leaves US-ASCII
+     *  printable.
+     *
+     *  @return a string that is printable, with binary hexed.
+     */
+    public static String hexable(byte[] data)
+    {
+        if (data == null)
+            return NULL;
+
+        return hexable(data, 0, data.length);
+    }
+
+    /**
+     *  Useful for printing errors, which can include binary.
+     *  Turns any wierdness into hex numbers, leaves US-ASCII
+     *  printable.
+     *
+     *  @return a string that is printable, with binary hexed.
+     */
+    public static String hexable(String s)
+    {
+        if (s == null)
+            return NULL;
+
+        try {
+            byte[] data = s.getBytes("UTF-8");
+            return hexable(data);
+        } catch (Exception ex) {
+            throw new Error(ex);
+        }
+    }
+
+
+    /**
+     * Converts the given byte to an unsigned 2-character hex string.
+     */
+    public static String toString(byte x) {
+        char[] cs = new char[2];
+        cs[0] = toHexDigit((x >>> 4) & 0xF);
+        cs[1] = toHexDigit((x      ) & 0xF);
+        return new String(cs);
+    }
+
+
+    /**
+     * Converts the given int to an unsigned 8-character hex string.
+     */
+    public static String toString(int x) {
+        char[] cs = new char[8];
+        for(int i=7; i>=0; i--, x>>>=4)
+            cs[i] = toHexDigit(x & 0xF);
+        return new String(cs);
+    }
+
+
+    /**
+     * Converts the given long to an unsigned 16-character hex string.
+     */
+    public static String toString(long x) {
+        char[] cs = new char[16];
+        for(int i=15; i>=0; i--, x>>>=4)
+            cs[i] = toHexDigit((int)x & 0xF);
+        return new String(cs);
+    }
+
+
+
+    /**
+     *  Make a copy of the raw bytes, for an immutable Id get method.
+     *  (This is similar to clone(), but it handles null, and there
+     *  is some change/bug where byte[].clone() has become protected.
+     *  Also, byte[].clone() doesn't work for 1.4.1 ?)
+     *
+     *  @param the original bytes (can be null)
+     *  @return the bytes copied (null is returned if null passed)
+     */
+    public static byte[] copy(byte[] buf)
+    {
+        if (buf == null)
+            return null;
+        int len = buf.length;
+        byte[] temp = new byte[len];
+        System.arraycopy(buf, 0, temp, 0, len);
+        return temp;
+    }
+
+
+    /**
+     *  <p>
+     *  Convert this string representation into the bytes.
+     *  Strips any hint strings that might be present in
+     *  debug outputs, etc.  The hint strings are of the
+     *  the form "SHA1#abc123..." or perhaps "contractid:123abc..."
+     *  </p><p>
+     *  The resultant bytes should be the underlying message digest
+     *  binary bytes, and suitable for reconstruction into another
+     *  form of Id.  This is essential for crossing package boundaries
+     *  where the packages themselves cannot agree on dependencies.
+     *  </p><p>
+     *  The strings come from many different higher level applications
+     *  that use strings (Lucaya, servers, etc).  Users tend to
+     *  pass this format across to things like payment dialogs.
+     *  </p>
+     *
+     *  @return a byte array of indeterminate length, or null if no
+     *          hex string was found, or there were garbage chars in it
+     */
+    public static byte[] getBytesFromIdString(String s)
+    {
+        if (s == null)
+            return null;
+        int start = s.indexOf('#');
+        if (start == -1)
+            start = s.indexOf(':');
+        if (start >= 0)
+            s = s.substring(start + 1);
+        byte[] bytes = hex2dataOrNull(s);
+        return bytes;
+    }
+
+    /**
+     *  Boring utility that saves writing it all the time.
+     *  Turn 4 bytes into a hashcode, no pertubations.
+     *  @throws Boom if not 4 bytes available in b or null
+     */
+    public static int bytes2hashCode(byte[] b)
+    {
+        return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
+    }
+
+    /**
+     *  A quick function for diags, long enough to
+     *  avoid collisions, but short enough to print out.
+     *  Does not change the bytes.
+     *
+     *  @param buf is the bytes to convert to hex
+     *  @param number is minimum of bytes (chars is 2*(bytes+1))
+     */
+    public static String diag(byte[] buf, int number)
+    {
+        if (buf.length <= (number+1))
+            return Hex.data2hex(buf);
+
+        byte[] bbb = new byte[number];
+        for (int j = 0; j < bbb.length; j++)
+            bbb[j] = buf[j];
+
+        return Hex.data2hex(bbb) + "..";
+    }
+
+
+
+
+
+
+        ////////////////////////////////
+       //                            //
+      //     T E S T    C O D E     //
+     //                            //
+    ////////////////////////////////
+
+
+
+    public static void main(String args[])
+        throws Exception
+    {
+        args = null;
+        madeUpTest();
+        dataTest();
+        System.err.println("Tests Passed.");
+        System.exit(0);
+    }
+
+    /**
+     *  Test some made up strings for explicit conversion to bytes.
+     */
+    public static void madeUpTest()
+        throws Exception
+    {
+        String s = "c001d00d";
+        byte b[] = { (byte)0xC0, (byte)0x01, (byte)0xD0, (byte)0x0D };
+
+        System.err.println("bytes:  " + data2spacedhex(b));
+        String test = data2hex(b).toLowerCase();
+        if (!test.equals(s))
+            throw new Exception("data2hex " + s + " failed: " + test);
+        System.err.println("string: " + s);
+
+        byte ttt[] = hex2data(s);
+        byte uuu[] = hex2dataOrNull(s);
+        if (uuu == null)
+                throw new Exception("hex2dataOrNull " + s + " failed: null");
+        // ttt[2] = (byte)0xFA;  // test
+        for (int i = 0; i < b.length; i++)
+        {
+            if (ttt[i] != b[i])
+                throw new Exception("hex2data " + s + " failed: "
+                                + data2hex(ttt) + " (byte " + i + ")");
+            if (uuu[i] != b[i])
+                throw new Exception("hex2dataOrNull " + s + " failed: "
+                                + data2hex(ttt) + " (byte " + i + ")");
+        }
+
+        s += "-0";
+        if (hex2dataOrNull(s) != null)
+            throw new Exception("hex2dataOrNull did not return null on " + s);
+        if (hex2data(s) == null)
+            throw new Exception("hex2data returned null on " + s + "???");
+
+        System.err.println("made up data tested.");
+    }
+
+    public static void dataTest()
+    {
+        for (int i = 0; i < 10; i++)
+        {
+            String s = Junk.string();
+            System.err.println(i + ": " + s);
+            System.err.println(data2spacedhex(s.getBytes(), i, ".."));
+            cycle(s);
+        }
+    }
+
+    public static void cycle(String s)
+    {
+        byte[] b = s.getBytes();
+        String test = data2hex(b).toLowerCase();
+        byte[] back = hex2data(test);
+        String result = new String(back);
+        if (!result.equals(s))
+            throw new RuntimeException("Failed!" +
+                                       "\nstart: " + s +
+                                       "\n hex : " + test +
+                                       "\n back: " + result);
+    }
+
+}
diff --git a/Business Logic/trunk/src/org/cacert/birdshack/util/Junk.java b/Business Logic/trunk/src/org/cacert/birdshack/util/Junk.java
new file mode 100644 (file)
index 0000000..708fb2c
--- /dev/null
@@ -0,0 +1,532 @@
+/*
+ * Was webfunds.util.Example, contributed.
+ */
+package org.cacert.birdshack.util;
+
+import java.math.BigInteger;
+import java.io.InputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationTargetException;
+
+/**
+ *  A set of example, junk data fields, used for example() methods.
+ *
+ *  NONE of these routines are cryptographically secure,
+ *  in fact they are not even vaguely random, and only
+ *  intended for class unit tests.
+ *
+ *  Also, a set of reflection methods for constructing
+ *  unit tests that read, write and compare examples.
+ */
+public class Junk
+{
+    private Junk() {}
+
+
+    private static long seed = 0;
+
+    /**
+     *  Return data byte(s) that is rubbish.
+     *
+     *  All Junk routines are built upon exampleByte().
+     *
+     *  @return an example byte
+     */
+    public static byte exampleByte()
+    {
+        if (seed == 0)
+            seed = System.currentTimeMillis();
+
+        seed = (seed * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1);
+        return (byte) (seed >>> (48 - 8));
+
+        /*
+         *  Another one, apparently called a TYPE_0 generator:
+         *  i' = (i * 1103515245 + 12345) & 0x7fffffff
+         */
+    }
+
+    /** @return example boolean, true or false */
+    public static boolean exampleBool()
+    {
+        if (seed == 0)
+            seed = System.currentTimeMillis();
+
+        return (exampleByte() & 0x01) == 0x01;
+    }
+
+    /** @return example int, between min and max */
+    public static int exampleInt(int min, int max)
+    {
+        if (min == max)
+            return min;
+
+        if (min > max)  // swap
+        {
+            int x = min;
+            min = max;
+            max = x;
+        }
+        long min2 = min;
+        long max2 = max;
+        long offset = 0;
+        if (min2 < 0)   // move numbers to 0..n
+        {
+            offset = (-min2);
+            max2 += offset;
+            min2 = 0;
+        }
+        long l = exampleLong();
+        if (l < 0)
+            l = -l;
+        // whoops this was limited it to max-1
+        int ex = (int) ( ( l % (max2 - min2 + 1) ) + min2 - offset );
+        if (ex < min || ex > max)
+            throw new RuntimeException("ex out of min,max: "+
+              ex+" "+min+","+max+ "  ["+min2+","+max2+"] ("+l+","+offset+")" );
+        return ex;
+    }
+
+    /**
+     *  This returns a long that is biased to small numbers.
+     *  @see exampleTimeMillis() for a time biased example long,
+     *  @see exampleBigLong() for an unbiased long.
+     *  @return example long, made of 8 example bytes, biased to small
+     */
+    public static long exampleLong()
+    {
+        long l = 0;
+        for (int i = (exampleByte() & 0x07); i >=0; i--)
+        {            // kilted towards low numbers
+            l <<= 8;
+            l |= exampleByte() & 0xFF;
+        }
+        return l ;
+    }
+
+    /**
+     *  This returns a long that is biased to time.
+    public static long exampleTimeMillis()
+    {
+        return BaseTime.ex();
+    }
+     */
+
+    /** @return junk char, made of 2 example bytes */
+    public static char junkChar()
+    {
+        int i = ((exampleByte() & 0xFF) << 8) | (exampleByte() & 0xFF);
+        return (char)i ;
+    }
+
+//    static java.nio.charset.Charset utf8charset;
+//    static java.nio.charset.CharsetDecoder utf8decoder;
+//    static java.nio.charset.CharsetEncoder utf8encoder;
+//
+//    /** @return example char, made of 2 example bytes, should be good UTF-8 */
+//    static
+//    {
+//        try {
+//            utf8charset = java.nio.charset.Charset.forName("UTF-8");
+//            utf8encoder = utf8charset.newEncoder();
+//            // utf8encoder.substitute(null);   // throw exceptions on duds.
+//
+//            if (!utf8charset.canEncode())
+//                throw new Panic("can't encode?");
+//
+//            int c;
+//            char[] chars = new char[1];
+//            for (int i = 0; i < 10 ; i++)
+//            {
+//                 c = i << 8;
+//                 System.err.print("" + c);
+//                 for (int j = 20; j < 90 ; j += 10)
+//                 {
+//                     char c2 = (char)(c | j);
+//                     chars[0] = c2;
+//                     String s = new String(chars);
+//                     dumpUTF(s);
+//                 }
+//                 System.err.println("!");
+//            }
+//
+//        } catch (Exception ex) {
+//            throw new Panic(ex);
+//        }
+//    }
+
+    /*
+     *  This returns a hopefully printable UTF-8 char.
+     *  Unprintables in US-ASCII are eliminated,
+     *  as are illegals in UTF-8 ('\uFFFE', '\uFFFF').
+     */
+    public static char exampleChar()
+    {
+        int i = 0;
+        char c;
+        while (true)
+        {
+             c = junkChar();
+             if (c < '\u0020')              // no, \r doesn't count :)
+                 continue;
+             if (c == '\u00FF')             // nor del
+                 continue;
+             if (c == '\uFFFE' || c == '\uFFFF')   // illegal in UTF-8
+                 continue;
+
+             break;
+        }
+        return c;
+    }
+
+    /** @return example long, made of 8 example bytes, unbiased */
+    public static long exampleBigLong()
+    {
+        long l = 0;
+        for (int i = 0; i < 8; i++)
+        {            // no bias
+            l <<= 8;
+            l |= exampleByte() & 0xFF;
+        }
+        return l ;
+    }
+
+    /** @return byte array with len example bytes in it, never null */
+    public static byte[] data(int len)
+    {
+        byte[] b = new byte[len];
+
+        for (int i = len - 1; i >=0; i--)
+        {
+            b[i] = exampleByte();
+        }
+        return b ;
+    }
+
+    /** @return unprintable byte array from 0 to 32 bytes long, never null */
+    public static byte[] data()
+    {
+        int len = exampleByte() & 0x1F;
+        return data(len);
+    }
+
+    /** @return unprintable byte array from 0 to 16 bytes long, may be null */
+    public static byte[] dataWithNull()
+    {
+        int b = exampleByte();
+        if ((b & 0xF0) == 0)
+            return null;
+        int len = b & 0x0F;
+        return data(len);
+    }
+
+    /** @return unprintable byte array from min to max bytes long */
+    public static byte[] data(int min, int max)
+    {
+        int len = exampleInt(min, max);
+        return data(len);
+    }
+
+
+
+    /** @return printable string with some chars made into funny characters */
+    public static void dumpUTF(String s)
+    {
+        try {
+            byte[] bytes = s.getBytes("UTF-8");
+            System.err.print(" " + s + " ");
+            System.err.write(bytes, 0, bytes.length);
+        } catch (Exception ex) {
+            throw new Error("UTFF");
+        }
+    }
+
+    /** @return printable string with some chars made into funny characters */
+    public static String makeUTF(String s)
+    {
+        char[] chars = s.toCharArray();
+        int len = chars.length;
+        for (int i = 0; i < len; i++)
+        {
+            if ((exampleByte() & 0x03) == 0)
+                chars[i] = exampleChar();
+        }
+        String s2 = new String(chars);
+        // dumpUTF(s2);
+        return s2;
+    }
+
+    /** @return printable string len bytes long, never null */
+    public static String string(int len)
+    {
+        byte[] data = data(len);
+        return Hex.data2hex(data);
+    }
+
+    /** @return printable string from min to max bytes long */
+    public static String string(int min, int max)
+    {
+        int len = exampleInt(min, max);
+        return string(len);
+    }
+
+    /** @return valid Identifier for the Resources */
+    public static String identifier()
+    {
+        int len = exampleInt(1, 7);
+        return string(len);
+    }
+
+    /** @return printable string from 0 to 32 bytes long, never null */
+    public static String string()
+    {
+        int len = exampleByte() & 0x1F;
+        return string(len);
+    }
+
+    /** @return printable string from 0 to 8 words long, never null */
+    public static String words()
+    {
+        int b = exampleByte() & 0x07;
+        String s = "";
+        for (int i = 0; i < b; i++)
+        {
+            if (!s.equals("") || exampleBool())
+                s += whitespace();
+            s += string(1, 4);
+        }
+        if (exampleBool())
+            s += whitespace();
+        return s;
+    }
+
+    /** @return printable string from 0 to 3 lines of words(), never null */
+    public static String lines()
+    {
+        int b = exampleByte() & 0x03;
+        String s = "";
+        for (int i = 0; i < b; i++)
+        {
+            if (!s.equals("") || (exampleByte() & 0x03) == 0x03)
+                s += "\n";
+            s += words();
+        }
+        if ((exampleByte() & 0x03) == 0x03)
+            s += "\n";
+        return s;
+    }
+
+
+    /**
+     *  0, 1, 2 chars (weighted to 1) of spaces (common) and tabs (rare)
+     *  @return some optional white-space
+     */
+    public static String whitespace()
+    {
+        int b = exampleByte();
+        int len = b & 0x03;
+        if (len == 0x03)
+            len = 1;
+        b >>= 2;
+        String s = "";
+        while (len-- > 0)
+        {
+            if ((b & 0x03) == 0x03)
+                s += "        ";   // tab
+            else
+                s += " ";
+            b >>= 2;
+        }
+        return s;
+    }
+
+
+
+    /**
+     *  @return a BigInteger that is positive and made of len junk bytes
+     *  @param len bytes, where len >= 1
+     */
+    public static BigInteger examplePosBigInt(int len)
+    {
+        byte[] data = data(len);
+        data[0] &= 0x7F;                     // so always positive
+        return new BigInteger(data);
+    }
+
+    /**
+     *  @return something from 8 bytes to 16 bytes worth of big int.
+     */
+    public static BigInteger examplePosBigInt()
+    {
+        int len = exampleByte() & 0x07;
+        len |= 0x08;
+        return examplePosBigInt(len);
+    }
+
+
+
+/****************************************************
+ *
+ *  These methods are used in conjunction with
+ *  above example code to construct encode-decode
+ *  tests.  They may be better off in another class.
+ *
+ */
+
+    /**
+     *  Return the object decoded from the byte stream,
+     *  which came from an encode of the same object.
+     *
+     *  The class named must follow the WireObject convention
+     *  by including an InputStream constructor.
+     *
+     *  @throws RuntimeException if the method could not be called
+     *  @return an example of the named class.
+     */
+    public static Object decodeFromBytes(String fullclassname, byte[] contents)
+    {
+        Class clazz = getKnownClass(fullclassname);
+        ByteArrayInputStream bais = new ByteArrayInputStream(contents);
+
+        return decodeFromBytes(clazz, bais);
+    }
+
+    /**
+     *  Return the object decoded from the byte stream,
+     *  which came from an encode of the same object.
+     *
+     *  The class supplied must follow the WireObject convention
+     *  by including an InputStream constructor.
+     *
+     *  @throws RuntimeException if the method could not be called
+     *  @return an example of the named class.
+     */
+    public static Object decodeFromBytes(Class clazz, byte[] contents)
+    {
+        ByteArrayInputStream bais = new ByteArrayInputStream(contents);
+
+        return decodeFromBytes(clazz, bais);
+    }
+
+    /**
+     *  Return the object decoded from the byte stream,
+     *  which came from an encode of the same object.
+     *
+     *  The class supplied must follow the WireObject convention
+     *  by including an InputStream constructor.
+     *
+     *  @throws RuntimeException if the method could not be called
+     *  @return an example of the named class.
+     */
+    public static Object decodeFromBytes(Class clazz, InputStream is)
+    {
+// InputStream now being replaced by WireInputStream
+        Class[] arguments = new Class[1];
+        Class inputStream = getKnownClass("java.io.InputStream");
+        arguments[0] = inputStream;
+        String name = clazz.getName();
+
+        // there should be a way to get the byte[] constructor
+        // based on the Byte.TYPE class, but the javadoc doesn't
+        // say how to make an array class....
+
+        Constructor con;
+        try {
+            con = clazz.getConstructor(arguments);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException("no contructor(InputStream) in " + name);
+        }
+
+        Object[] initArgs = new Object[1];
+        initArgs[0] = (Object)is;
+        Object obj;
+        try {
+            obj = con.newInstance(initArgs);
+        } catch (InstantiationException e) {
+            e.printStackTrace();
+            throw new RuntimeException("bad constructor(IS) in "+name+": "+e);
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+            throw new RuntimeException("bad constructor(IS) in "+name+": "+e);
+        } catch (IllegalAccessException e) {
+            e.printStackTrace();
+            throw new RuntimeException("bad constructor(IS) in "+name+": "+e);
+        }
+
+        return obj;
+    }
+
+    /**
+     *  The class named must follow the WebFunds testing convention
+     *  by including the static example() method.
+     *
+     *  @throws RuntimeException if the method could not be called
+     *  @return an example of the named class.
+     */
+    public static Object getExample(String fullclassname)
+    {
+        Class clazz = getKnownClass(fullclassname);
+        Method meth;
+        try {
+            meth = clazz.getMethod("example", new Class[]{}); // zero args
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException("no example() in " + fullclassname);
+        }
+
+        Object obj;
+        try {
+            obj = meth.invoke(null, new Object[]{}); // zero args
+        } catch (InvocationTargetException e) {
+            e.printStackTrace();
+            throw new RuntimeException("bad example() "+fullclassname+":\n"+e);
+        } catch (IllegalAccessException e) {
+            throw new RuntimeException("bad example() "+fullclassname+":\n"+e);
+        } catch (Exception e) { // reflection throws lots of rubbish
+            e.printStackTrace();
+            throw new RuntimeException("bad example() "+fullclassname+":\n"+e);
+        }
+
+        if (obj == null)
+            throw new RuntimeException("null example() from " + fullclassname +
+                                       ": " + clazz);
+
+        return obj;
+    }
+
+    /**
+     *  This method just hides the exception.
+     *  @throws RuntimeException if the class doesn't exist
+     *  @return the Class for this name
+     */
+    public static Class getKnownClass(String fullclassname)
+    {
+        Class byname;
+        try
+        {
+            byname = Class.forName(fullclassname);
+        }
+        catch (ClassNotFoundException e)
+        {
+            throw new RuntimeException("No class for " + fullclassname + "\n" +
+                                       "check CLASSPATH or name or source.");
+        }
+        return byname;
+    }
+
+
+
+/****************************************************
+ *
+ *  Quick Test Method.
+ */
+
+    public static void  main(String[] arg)
+    {
+        System.out.println("string(): " + string());
+        System.out.println("words(): " + words());
+        System.out.println("lines(): -----\n" + lines() + "------");
+    }
+}
diff --git a/Business Logic/trunk/src/org/cacert/birdshack/util/Panic.java b/Business Logic/trunk/src/org/cacert/birdshack/util/Panic.java
new file mode 100755 (executable)
index 0000000..d51a9ba
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Was webfunds.util.Panic, contributed.
+ */
+package org.cacert.birdshack.util;
+
+import org.apache.log4j.Logger;
+
+
+/**
+ * Panic is thrown to force an unclean exit of the JVM when a fatal
+ * error occurs. This exception should not (and can not) be caught.
+ *
+ * Upon construction, Panic logs an message to Log4J at ERROR level. The
+ * message includes a stack trace (either obtained from the passed-in
+ * Throwable or generated by the Panic class itself). Panic then calls
+ * the Log4J Logger.shutdown() method and does a System.exit(1).
+ *
+ * Panic extends Error because it should not be caught. (I think it
+ * should extend Throwable but Jikes does not allow that. --JCvG)
+ *
+ * @author  Jeroen C. van Gelderen (gelderen@webfunds.org)
+ * @version $Id: Panic.java,v 1.15 2005/02/02 19:45:57 iang Exp $
+ */
+public final class Panic extends Error {
+
+    private static final Logger _logger =
+        Logger.getInstance(Panic.class.getName());
+        
+    private static boolean handle = true;
+    
+    /**
+     *  Test classes can call this if they wish to catch
+     *  Panic when they expect it to happen.
+     *  This will disable the internal exit.
+     */
+    public static void disableHandling()
+    {
+        _logger.warn( "Panic.disableHandling() - this must be a test class."); 
+        handle = false;
+    }
+
+    
+    /** Panic with a default message. */
+    public Panic() {
+        this("unknown cause", null);
+    }
+
+
+    /** Panic with a message. A stack trace will be generated. */
+    public Panic(String msg) {
+        this(msg, null);
+    }
+
+
+    /** Panic with just a Throwable. */
+    public Panic(Throwable cause) {
+        this(cause.getMessage(), cause);
+    }
+    
+
+    /** Panic with a message and the Throwable that caused the panic. */
+    public Panic(String msg, Throwable cause) {
+
+        super("PANIC: " + msg + "\nPanic from this cause:\n", cause);
+
+        if (handle)
+        {
+            if( cause != null ) {
+                _logger.error( this.getMessage(), cause );
+            } else {
+                // obtain a StackTrace and log it
+                Throwable t = new Throwable().fillInStackTrace();
+                _logger.error( this.getMessage() +
+                    "\n(Stack trace generated by Panic)", t );
+            }
+           
+            _logger.error("\nGoing to call System.exit(1)...");
+            Logger.shutdown();
+            System.exit(1);
+        }
+    }
+
+    /**
+     *  A stopgap measure until Java 1.5.
+     *  When assert becomes available, change all the lines to
+     *  use the direct call.
+     */
+    public static void assert2(boolean asserted)
+    {
+        if (asserted)
+            return ;
+
+        throw new Panic("assert failed.");
+    }
+}