[Webfunds-commits] java/webfunds/sox/value AccountException.java ValueManager.java AccountStore.java CancelException.java DepositException.java PaymentException.java PendingReceipt.java ReceiptsStore.java SOXServerStore.java StateReceipt.java StoreAccountStore.java StoreReceiptStore.java WalletException.java SOXWallet.java

Ian Grigg iang@cypherpunks.ai
Sat, 24 Mar 2001 19:57:26 -0400 (AST)


iang        01/03/24 19:57:26

  Modified:    webfunds/sox/value AccountStore.java CancelException.java
                        DepositException.java PaymentException.java
                        PendingReceipt.java ReceiptsStore.java
                        SOXServerStore.java StateReceipt.java
                        StoreAccountStore.java StoreReceiptStore.java
                        WalletException.java
  Added:       webfunds/sox/value AccountException.java ValueManager.java
  Removed:     webfunds/sox/value SOXWallet.java
  Log:
  compiles -- after Repository copy and many changes:
  1. repackaging;
  2. rewriting of PendingReceipt to use internal class AccountInfo
  3. moving SOXWallet copy to ValueManager
  4. dropping all WebFunds stuff from ValueManager

Revision  Changes    Path
1.10      +1 -1      java/webfunds/sox/value/AccountStore.java

Index: AccountStore.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/AccountStore.java,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -r1.9 -r1.10
--- AccountStore.java	2001/03/07 00:29:43	1.9
+++ AccountStore.java	2001/03/24 23:57:23	1.10
@@ -1,5 +1,5 @@
 
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 import webfunds.sox.Account;
 import webfunds.sox.AccountId;



1.5       +2 -2      java/webfunds/sox/value/CancelException.java

Index: CancelException.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/CancelException.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- CancelException.java	2000/09/30 18:50:09	1.4
+++ CancelException.java	2001/03/24 23:57:23	1.5
@@ -1,10 +1,10 @@
 /*
- * $Id: CancelException.java,v 1.4 2000/09/30 18:50:09 iang Exp $
+ * $Id: CancelException.java,v 1.5 2001/03/24 23:57:23 iang Exp $
  *
  * Copyright (c) 2000 Systemics Inc on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 /**
  *  This exception class is thrown when a cancel call fails.



1.6       +2 -2      java/webfunds/sox/value/DepositException.java

Index: DepositException.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/DepositException.java,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -r1.5 -r1.6
--- DepositException.java	2000/09/30 18:50:10	1.5
+++ DepositException.java	2001/03/24 23:57:23	1.6
@@ -1,9 +1,9 @@
-/* $Id: DepositException.java,v 1.5 2000/09/30 18:50:10 iang Exp $
+/* $Id: DepositException.java,v 1.6 2001/03/24 23:57:23 iang Exp $
  *
  * Copyright (c) 2000 Systemics Inc. on behalf of
  * The WebFunds Development Team. All rights reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 import webfunds.sox.Errors;
 



1.3       +2 -2      java/webfunds/sox/value/PaymentException.java

Index: PaymentException.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/PaymentException.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- PaymentException.java	2000/09/30 18:50:12	1.2
+++ PaymentException.java	2001/03/24 23:57:23	1.3
@@ -1,10 +1,10 @@
 /*
- * $Id: PaymentException.java,v 1.2 2000/09/30 18:50:12 iang Exp $
+ * $Id: PaymentException.java,v 1.3 2001/03/24 23:57:23 iang Exp $
  *
  * Copyright (c) 2000 Systemics Inc on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 /**
  *  This exception class is thrown when a request for a payment fails.



1.15      +105 -42   java/webfunds/sox/value/PendingReceipt.java

Index: PendingReceipt.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/PendingReceipt.java,v
retrieving revision 1.14
retrieving revision 1.15
diff -u -r1.14 -r1.15
--- PendingReceipt.java	2001/03/23 15:06:02	1.14
+++ PendingReceipt.java	2001/03/24 23:57:23	1.15
@@ -1,15 +1,16 @@
 /*
- * $Id: PendingReceipt.java,v 1.14 2001/03/23 15:06:02 iang Exp $
+ * $Id: PendingReceipt.java,v 1.15 2001/03/24 23:57:23 iang Exp $
  *
  * Copyright (c) Systemics Ltd 1995-1999 on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
-import webfunds.client.Transaction;
-import webfunds.client.AccountInfo;
 import java.io.*;
 import java.util.Date;
+
+import webfunds.utils.Panic;
+
 import webfunds.sox.Encodable;
 import webfunds.sox.ItemId;
 import webfunds.sox.Payment;
@@ -18,7 +19,8 @@
 
 /**
  *  This class is no good.
- *  Serializable, so can't change signature.  Why has it encode/decode?
+ *  This was Serializable, in old SOXWallet, so it may not be
+ *  Compatible where serlized classes need to be recovered.
  */
 public class PendingReceipt
     implements Serializable
@@ -27,14 +29,12 @@
     public String       transid;
     public String       getPaymentId()       { return transid; }
     public ItemId       item;
-    public AccountInfo  source;
-    public AccountId    getSourceAccountId() { return source.getAccountId(); }
-    public AccountInfo  target;
-    public AccountId    getTargetAccountId() { return target.getAccountId(); }
+    public AccountId    src;
+    public AccountId    tgt;
     public long         amount;
     public byte[]       desc;
     public Date         date;
-    public int          status = Transaction.STATUS_PENDING;
+    public int          status = 0;
 
 
     public PendingReceipt()
@@ -65,12 +65,12 @@
     }
 
     public PendingReceipt(String transid, ItemId item,
-                          AccountInfo source, AccountInfo target,
+                          AccountId src, AccountId tgt,
                           long amount, byte[] description, Date date)
     {
         this.transid = transid;
-        this.source = source;
-        this.target = target;
+        this.src = src;
+        this.tgt = tgt;
         this.amount = amount;
         this.desc = description;
         this.date = date;
@@ -81,13 +81,15 @@
         throws IOException
     {
         DataOutputStream dos = new DataOutputStream(os);
-        dos.writeInt(3);
+        dos.writeInt(4);
         Encodable.writeString(dos, transid);
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         item.encode(baos);
         Encodable.writeByteArray(dos, baos.toByteArray() );
-        source.encode(os);
-        target.encode(os);
+
+        src.encode(os);
+        tgt.encode(os);
+
         dos.writeLong(amount);
         Encodable.writeByteArray(dos, desc);
         dos.writeLong(date.getTime() );
@@ -98,29 +100,90 @@
     {
         DataInputStream dis = new DataInputStream(is);
         int version = dis.readInt();
-        if (version == 2)
+        if ( ! (2 <= version && version <= 4) )
+            throw new SOXPacketException("wrong v == " + version);
+
+        transid = Encodable.readString(dis);
+
+        if (version > 2)
+            item = new ItemId(Encodable.readByteArray(dis) );
+
+        if (version < 4)
         {
-            transid = Encodable.readString(dis);
-            source = new AccountInfo(is);
-            target = new AccountInfo(is);
-            amount = dis.readLong();
-            desc = Encodable.readByteArray(dis);
-            date = new Date(dis.readLong() );
+            AccountInfo source = new AccountInfo(is);
+            AccountInfo target = new AccountInfo(is);
+            src = source.getAccountId();
+            tgt = target.getAccountId();
         }
-        if (version == 3)
+        else  // the future
         {
-            transid = Encodable.readString(dis);
-            item = new ItemId(Encodable.readByteArray(dis) );
-            source = new AccountInfo(is);
-            target = new AccountInfo(is);
-            amount = dis.readLong();
-            desc = Encodable.readByteArray(dis);
-            date = new Date(dis.readLong() );
+            src = new AccountId(is);
+            tgt = new AccountId(is);
         }
-        else
-            throw new SOXPacketException("no version number");
+
+        amount = dis.readLong();
+        desc = Encodable.readByteArray(dis);
+        date = new Date(dis.readLong() );
     }
 
+    /*
+     *  (taken from webfunds.client.AccountInfo)
+     *  Recovery class for remaining AccountInfo layouts that might
+     *  be in PendingReceipts.
+     */
+    private class AccountInfo
+        extends Encodable
+    {
+        protected byte[] id;
+    
+        public AccountId getAccountId()
+        {
+            AccountId ai = new AccountId();
+            ai.setByteArray(id);
+            return ai ;
+        }
+    
+        public AccountInfo(byte[] data)
+            throws IOException
+        {
+            ByteArrayInputStream bais = new ByteArrayInputStream(data);
+            decode(bais);
+        }
+    
+        public AccountInfo(InputStream is)
+            throws IOException
+        {
+            decode(is);
+        }
+    
+        public void encode(OutputStream os)
+            throws IOException
+        {
+            DataOutputStream dos = new DataOutputStream(os);
+            dos.writeInt(2);
+            Encodable.writeByteArray(dos, id);
+            Encodable.writeString(dos, "");
+        }
+    
+        public void decode(InputStream is)
+            throws IOException
+        {
+            DataInputStream dis = new DataInputStream(is);
+            int version = dis.readInt();
+            if (version != 2)
+                throw new Panic("not a v2 AccountInfo");
+            id   = Encodable.readByteArray(dis);
+            String name = Encodable.readString(dis);
+        }
+    
+        public String toString()
+        {
+            if (id == null || id.length == 0)
+        	    return AccountId.open();
+            return getAccountId().toString();
+        }
+    
+    }
 
 
 /////////  Self-Test  ///////////////////////////////////////
@@ -128,7 +191,7 @@
     public String toString()
     {
         return "PR: " + transid + " " + amount + " of " + item + "\n" +
-               "from " + source + " to " + target + "\n";
+               "from " + src + " to " + tgt + "\n";
     }
 
     public boolean equals(java.lang.Object obj)
@@ -161,20 +224,20 @@
                 return false;
         }
 
-        AccountInfo ot = other.source;
+        AccountId ot = other.src;
         if (ot == null) {
-            if (source != null)
+            if (src != null)
                 return false ;
         } else {
-            if (!ot.equals(source))
+            if (!ot.equals(src))
                 return false;
         }
-        ot = other.target;
+        ot = other.tgt;
         if (ot.isOpen()) {
-            if (!target.isOpen())
+            if (!tgt.isOpen())
                 return false ;
         } else {
-            if (!ot.equals(target))
+            if (!ot.equals(tgt))
                 return false;
         }
              
@@ -196,8 +259,8 @@
     {
         Payment p = Payment.example();
         Date date = new Date(p.getValidFrom());
-        AccountInfo src = new AccountInfo(p.getSource(), null, null);
-        AccountInfo tgt = new AccountInfo(p.getTarget(), null, null);
+        AccountId src = p.getSource();
+        AccountId tgt = p.getTarget();
         PendingReceipt pr = new PendingReceipt(
                             p.getId(), p.getItem(),
                             src, tgt,



1.16      +2 -2      java/webfunds/sox/value/ReceiptsStore.java

Index: ReceiptsStore.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/ReceiptsStore.java,v
retrieving revision 1.15
retrieving revision 1.16
diff -u -r1.15 -r1.16
--- ReceiptsStore.java	2000/06/05 04:23:40	1.15
+++ ReceiptsStore.java	2001/03/24 23:57:23	1.16
@@ -1,9 +1,9 @@
-/* $Id: ReceiptsStore.java,v 1.15 2000/06/05 04:23:40 gelderen Exp $
+/* $Id: ReceiptsStore.java,v 1.16 2001/03/24 23:57:23 iang Exp $
  *
  * Copyright (c) Systemics Inc. 1999-2000 on behalf of
  * The WebFunds Development Team.  All Rights Reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 
 import webfunds.sox.AccountId;



1.12      +2 -2      java/webfunds/sox/value/SOXServerStore.java

Index: SOXServerStore.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/SOXServerStore.java,v
retrieving revision 1.11
retrieving revision 1.12
diff -u -r1.11 -r1.12
--- SOXServerStore.java	2000/07/14 00:17:59	1.11
+++ SOXServerStore.java	2001/03/24 23:57:23	1.12
@@ -1,10 +1,10 @@
 /*
- * $Id: SOXServerStore.java,v 1.11 2000/07/14 00:17:59 iang Exp $
+ * $Id: SOXServerStore.java,v 1.12 2001/03/24 23:57:23 iang Exp $
  *
  * Copyright (c) 1995-2000 Systemics Inc. on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 
 import java.util.Enumeration;



1.4       +2 -2      java/webfunds/sox/value/StateReceipt.java

Index: StateReceipt.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/StateReceipt.java,v
retrieving revision 1.3
retrieving revision 1.4
diff -u -r1.3 -r1.4
--- StateReceipt.java	2000/07/16 19:27:23	1.3
+++ StateReceipt.java	2001/03/24 23:57:24	1.4
@@ -1,10 +1,10 @@
 /*
- * $Id: StateReceipt.java,v 1.3 2000/07/16 19:27:23 iang Exp $
+ * $Id: StateReceipt.java,v 1.4 2001/03/24 23:57:24 iang Exp $
  *
  * Copyright (c) 2000 Systemics Inc on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 import java.io.*;
 import java.security.*;



1.24      +6 -6      java/webfunds/sox/value/StoreAccountStore.java

Index: StoreAccountStore.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/StoreAccountStore.java,v
retrieving revision 1.23
retrieving revision 1.24
diff -u -r1.23 -r1.24
--- StoreAccountStore.java	2001/03/07 00:29:43	1.23
+++ StoreAccountStore.java	2001/03/24 23:57:24	1.24
@@ -1,10 +1,10 @@
 /*
- * $Id: StoreAccountStore.java,v 1.23 2001/03/07 00:29:43 iang Exp $
+ * $Id: StoreAccountStore.java,v 1.24 2001/03/24 23:57:24 iang Exp $
  *
  * Copyright (c) Systemics Ltd 1995-1999 on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 import java.util.Enumeration;
 import java.io.*;
@@ -22,7 +22,7 @@
 {
     
     Store        store;
-    IssuerFinder finder;
+//    IssuerFinder finder;
     
     /**
      *  Store for accounts.
@@ -33,14 +33,13 @@
         if (store == null)
             throw new IllegalArgumentException("StoreAccountStore store==null");
         this.store = store;        
-        this.finder = null;        
+//        this.finder = null;        
 //logmsg("SAS " + this.getClass().getClassLoader());
     }
     
     /**
      *  Store for accounts.
      *  @option finder is set in all accounts that are requested.
-     */
     public StoreAccountStore(Store store, IssuerFinder finder)
     {
         if (store == null)
@@ -48,6 +47,7 @@
         this.store  = store;        
         this.finder = finder;
     }
+     */
     
         
     public Account[] getAllAccounts()
@@ -153,7 +153,7 @@
              throw new StoreException("Unknown object from " + key + ": " +
                                       obj.getClass().getName());
 
-        ac.setFinder(finder);
+        // ac.setFinder(finder);
         return ac;
     }
     



1.33      +6 -6      java/webfunds/sox/value/StoreReceiptStore.java

Index: StoreReceiptStore.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/StoreReceiptStore.java,v
retrieving revision 1.32
retrieving revision 1.33
diff -u -r1.32 -r1.33
--- StoreReceiptStore.java	2001/03/07 00:29:43	1.32
+++ StoreReceiptStore.java	2001/03/24 23:57:24	1.33
@@ -1,10 +1,10 @@
 /*
- * $Id: StoreReceiptStore.java,v 1.32 2001/03/07 00:29:43 iang Exp $
+ * $Id: StoreReceiptStore.java,v 1.33 2001/03/24 23:57:24 iang Exp $
  *
  * Copyright (c) Systemics Ltd 1999 on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 import java.util.Enumeration;
 import java.util.Hashtable;
@@ -485,8 +485,8 @@
         for (int i = 0; i < pendings.length; i++)
         {
             pending = pendings[i];
-            AccountId src = pending.getSourceAccountId();
-            AccountId tgt = pending.getTargetAccountId();
+            AccountId src = pending.src;
+            AccountId tgt = pending.tgt;
             boolean fromMe = acct.equals(src);
             boolean toMe   = acct.equals(tgt);
 
@@ -693,8 +693,8 @@
         
                             AccountId tgt = new AccountId();
                             AccountId src = new AccountId();
-                            tgt.setByteArray(pend.target.getByteArray());
-                            src.setByteArray(pend.source.getByteArray());
+                            tgt.setByteArray(pend.tgt.getByteArray());
+                            src.setByteArray(pend.src.getByteArray());
                             bug.println("           P" + src + " ==> " + tgt);
         
                             //



1.8       +2 -2      java/webfunds/sox/value/WalletException.java

Index: WalletException.java
===================================================================
RCS file: /home/webfunds/cvsroot/java/webfunds/sox/value/WalletException.java,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -r1.7 -r1.8
--- WalletException.java	2000/11/09 13:28:20	1.7
+++ WalletException.java	2001/03/24 23:57:24	1.8
@@ -1,10 +1,10 @@
 /*
- * $Id: WalletException.java,v 1.7 2000/11/09 13:28:20 iang Exp $
+ * $Id: WalletException.java,v 1.8 2001/03/24 23:57:24 iang Exp $
  *
  * Copyright (c) 2000 Systemics Inc on behalf of
  * the WebFunds Development Team.  All Rights Reserved.
  */
-package webfunds.client.sox;
+package webfunds.sox.value;
 
 import webfunds.sox.Errors;
 import webfunds.sox.SOXException;



1.1                  java/webfunds/sox/value/AccountException.java

Index: AccountException.java
===================================================================
/*
 * $Id: AccountException.java,v 1.1 2001/03/24 23:57:23 iang Exp $
 *
 * Copyright (c) 2000 Systemics Inc on behalf of
 * the WebFunds Development Team.  All Rights Reserved.
 */
package webfunds.sox.value;

/**
 *  This exception class is thrown when a cancel call fails.
 *  For high-level but non-GUI wallet calls.
 */
public class AccountException
    extends WalletException
{

    public AccountException(String msg)               { super(msg); }
    public AccountException(int errno)                { super(errno); }
    public AccountException(int errno, String msg)    { super(errno, msg); }

    public String toString() { return "CanEx: " + super.toString(); }
}



1.1                  java/webfunds/sox/value/ValueManager.java

Index: ValueManager.java
===================================================================
/* $Id: ValueManager.java,v 1.1 2001/03/24 23:57:24 iang Exp $
 *
 * Copyright (c) Systemics Inc. 1995-2000 on behalf of
 * The WebFunds Development Team.  All Rights Reserved.
 */
package webfunds.sox.value;


// JDK
import java.io.*;
import java.net.*;
import java.util.*;

import java.security.SecureRandom;

// SOX toolkit
import webfunds.ricardian.Contract;
import webfunds.ricardian.ContractStore;
import webfunds.ricardian.SOXServerException;

import webfunds.utils.Debug;
import webfunds.utils.Hex;
import webfunds.utils.Panic;

import webfunds.store.Store;
import webfunds.store.StoreException;
import webfunds.store.SepFileStore;

import webfunds.sox.Account;
import webfunds.sox.AccountId;
import webfunds.sox.ArmouredPayment;
import webfunds.sox.Crypto;
import webfunds.sox.IssuerFinder;
import webfunds.sox.ItemId;
import webfunds.sox.MailId;
import webfunds.sox.MailItem;

import webfunds.sox.AbstractPayment;
import webfunds.sox.PaymentFactory;
import webfunds.sox.Payment;
import webfunds.sox.TokenPayment;
import webfunds.sox.Token;
// import webfunds.sox.RandomToken;

import webfunds.sox.Receipt;
import webfunds.sox.SOXAccountException;
import webfunds.sox.SOXArgsException;
import webfunds.sox.SOXDepositException;
import webfunds.sox.SOXException;
import webfunds.sox.SOXLaterException;
import webfunds.sox.SOXPacketException;
import webfunds.sox.SOXSubAccountException;
import webfunds.sox.SubAccount;
import webfunds.sox.ValueAccount;



/**
 *  The SOX Value Manager.
 *
 *  (Was the SOXWallet -- attempt to separate and make simple!)
 *
 * @version $Revision: 1.1 $
 */
public class ValueManager
    extends Debug
{

    /**
     *  Store is the interface to storage in the outside world.
     */
    protected   Store                   store;

    protected   AccountStore            accountStore;
    protected   SOXServerStore          soxes;
    protected   ReceiptsStore           receiptStore;
    protected   Properties              properties;
    protected   IssuerFinder            finder;

    protected static final String name = "SOX Value Manager";
    protected String shortname         = "SOX-VM";
    protected static final String fix  = "  Sv: ";

    /** If we can do things that would normall be illegal or immoral */
    public boolean isTestMode()
    {
        if (properties == null)
            return false;

        String tmode = (String)properties.get("test.mode");
        return (tmode != null) && tmode.equals("true");
    }


/////////  Initialisation  //////////////////////////////

    // bug gets set directly by caller?
    public ValueManager()
    {
        super();
        debug(fix);
    }



    /**
     * Initialise our external interfaces.
     *
     * Construction is complicated by class loader, hence separate init.
     */
    public void init(SecureRandom sr, Store store,
                     ContractStore contracts, Properties properties)
        throws StoreException
    {
        if (Crypto.sr == null)
            Crypto.setSecureRandom(sr);

        this.properties = properties;

        setStore(store, contracts);
    }



    /**
     * Shortname is used for the stores.  Can be set by child.
    public void   setShortName(String name) { shortname = name; }
    public String getShortName()            { return shortname; }
    public String getProtocol()             { return name; }
    public String getVersion()              { return "2.2"; }
     */



    /**
     *  Set the Store.
     *  This is the only access we have to persistant storage.
     *  Unfortunately, it's quite limiting.
     */
    protected void setStore(Store store, ContractStore contracts)
        throws StoreException
    {
        this.store   = store;

        //
        //  The SOX server file Store.
        //
        Store soxstore = null;
        try {
            soxstore = store.getStore("SOXServers", SepFileStore.APPEND);
        } catch (StoreException ex) {  // should delete and start again
            // ex.printStackTrace(err());
            logmsg("Failed to get SOXServers store: " + ex);
            // System.exit(1);
            throw ex ;
        }

        soxes = new SOXServerStore(soxstore, getDebug());
        soxes.setContractStore(contracts);
        finder = (IssuerFinder)soxes;

        //
        //  Account store.
        //  Failures here are unrecoverable.
        //
            logmsg("Trying to get 'Accounts' store");
            Store st = store.getStore("Accounts");
            logmsg(
                "  We got: " + st +
                ", with size: " + st.size() );
            StoreAccountStore accs;
            accs = new StoreAccountStore(st);
            accs.debug(bug);

            accountStore = (AccountStore)accs;

        //
        //  Receipt store.
        //  Failures here are unrecoverable.
        //
            receiptStore = new StoreReceiptStore(store.getStore("Receipts"));
            //receiptStore.debug(bug);

        //
        //  An old store is Issuers.  We should remove that some time.
        //

    }



/////////  Acquire Things  //////////////////////////////

    /**
     *  Lowest level get Account call - here we fix up the account for
     *  actual usage (in the store it is only kept as data).
     */
    protected Account getAccount(AccountId id)
        throws StoreException
    {
        Account ac = accountStore.getAccount(id);
        ac.setFinder(finder);
        return ac;
    }

    /**
     * Call this one if you know the account is there,
     * and you don't want to handle the exception.
     */
    protected Account getAccountOrDie(AccountId id)
    {
        Account acct = null;
        try {
            acct = getAccount(id);
        } catch (StoreException ex) {
            ex.printStackTrace(System.err);
            logmsg("account " + id);
            logmsg("no such account? " + ex);
            System.exit(1);
        }
        return acct ;
    }

    /**
     *  @return true if the account exists, else false
     */
    public boolean existsAccount(AccountId id)
    {
        Account acct = null;
        try {
            acct = getAccount(id);
        } catch (StoreException ex) {
            return false;
        }
        return true;
    }

    /**
     *  @return true if the account and subaccount exists, else false
     */
    public boolean existsSubAccount(AccountId id, ItemId item)
    {
        Account acct = null;
        try {
            acct = getAccount(id);
        } catch (StoreException ex) {
            return false;
        }

        if (acct.subExists(item))
            return true;

        return false;
    }



    /**
     *  Get a list of accounts managed by this wallet.
     *
     *  @return an array of all AccountIds, maybe empty but never null
     */
    public AccountId[] getAccountIds()
    {
        Account[] accounts;
        try {
            accounts = accountStore.getAllAccounts();
        } catch (StoreException ex) {
            ex.printStackTrace();
            return new AccountId[0];
        }

        if (accounts == null)
            return new AccountId[0];
        int len = accounts.length;
logmsg("Retrieved " + len + " accounts");

        AccountId[] ids = new AccountId[len];

        for (int i = 0; i < len; i++)
        {
            ids[i] = accounts[i].getId();
        }
        return ids;
    }


    /**
     *  Get the item identifiers for the stated account.
     *
     */
    public ItemId[] getItemIds(AccountId accountid)
        throws StoreException
    {
        Account acct = getAccount(accountid);

        ItemId[] items = acct.getItemIds();
        logmsg("Ac " + acct + " : " + items.length + " items!");
        for (int i = 0; i < items.length; i++)
            logmsg("      " + items[i]);
        return items;
    }


    /**
     *  Get a known account.
     *  @return the account, never null
     *  @except AccountException if the account is unknown or not available
     */
    protected Account getKnownAccount(AccountId accountid)
        throws AccountException
    {
        Account account;
        try
        {
            account = getAccount(accountid);
        } catch (StoreException ex) {
            ex.printStackTrace();
            throw new AccountException(ex.toString());
        }

        if (account == null)
            throw new AccountException("ac not known");

        return account;
    }

    /**
     *  Get a known sub account (via the parent account).
     *  @return the subaccount, never null
     *  @except AccountException if the account or sub is unknown / not avail
     */
    protected SubAccount getKnownSubAccount(AccountId accountid, ItemId item)
        throws AccountException
    {
        logmsg("AccountId update");
        Account account = getKnownAccount(accountid);

        SubAccount sub = account.getSub(item);
        if (sub == null)
            throw new AccountException("no such sub: " + item);

        return sub;
    }

    /**
     *  Get a known sub account (via the parent account).
     *  @return the subaccount, never null
     *  @except AccountException if the account or sub is unknown / not avail
     */
    protected ValueAccount getKnownValueAccount(AccountId accountid, ItemId item)
        throws AccountException
    {
        SubAccount sub = getKnownSubAccount(accountid, item);
        if (!(sub instanceof ValueAccount))
            throw new AccountException("sub not a ValueAccount: " + item);

        return (ValueAccount)sub;
    }

    public long getValue(AccountId acct, ItemId item, boolean pending)
    {
        // I moved the contents to StoreReceiptStore, which better knows
        // about changes.  There were some logical ???s in the old one.
        try
        {
            if (pending)
                return receiptStore.pendingValue(acct, item) ;
            else
                return receiptStore.confirmedValue(acct, item) ;
        }
        catch (StoreException ex)
        {
            logmsg("getValue: " + ex);
        }

        return Long.MIN_VALUE ;
    }



///////////  Payments  //////////////////////////////////////////


    private boolean gotEnough(AccountId src,
                              ItemId item,
                              long amount)
    {
        long pend = getValue(src, item, true);
        long confirmed = getValue(src, item, false);
 
        logmsg("amount: " + amount + ", pend: " + pend +
                      ", confirmed: " + confirmed);

        if ( (pend + confirmed) < amount )
            return false;
        else
            return true;
    }



    /**
     *  Make a payment.
     *  This should be used by all automatic clients, it does:
     *     * access to subaccounts,
     *     * checks balances, and
     *     * saves the pending payment for later reconciliation.
     *  and delivers the payment back.  It is thread safe.
     *
     * @param tgt the target account, may be open
     * @param src the source account where funds are written from
     * @param item is the name of the item
     * @param amount the (contract) qty of items of which the payment is for
     * @param desc a description of what this payment is for (optional)
     * @param boolean whether or not the payment is a rollover payment
     * @param from the time from which the payment is valid
     * @param till the time at which the payment will expire
     * @param pid a payment identifier, ignored if empty
     */
    public synchronized AbstractPayment makePayment(
                              AccountId src, AccountId tgt,
                              ItemId item, long amount,
                              byte[] desc,
                              long from, long till,
                              String pid)
        throws PaymentException, AccountException
    {

        if (amount < 0)
            throw new IllegalArgumentException("amount " + amount + " < 0 !");

        ValueAccount val = getKnownValueAccount(src, item);

        // it is an error (?) if the user asks for pid when
        // that one is already in play.
        if (pid != null && (pid.length() > 0))
        {
            StateReceipt sr;
            try {
                sr = receiptStore.getReceipt(src, item, pid);
            } catch (StoreException ex) {
                throw new webfunds.utils.Panic("get StateReceipt for " + pid);
            }
            if (sr != null)
            {
                throw new PaymentException(PaymentException.PID_IN_USE, pid);
            }
        }


        /*
         *  Check if there is enough.
         *  (There is always "enough" if the src is equal to the target
         *  or if the amount is zero...  This is a judgement call of course.)
         */
        if (!src.equals(tgt) && (amount > 0) &&
            !gotEnough(src, item, amount))
        {
            throw new PaymentException(PaymentException.NOT_ENUF_FUNDS,
                                       "amount " + amount + " not available");
        }

        Payment payment;
        try {
            payment = val.createPayment(tgt, amount,
                                    desc, false,
                                    from, till,
                                    pid);
        } catch (SOXException ex) {
            ex.printStackTrace();
            throw new PaymentException(PaymentException.UNKNOWN,
                                       "Payment no good: " + ex);
        }

        savePaymentAsPending(src, payment);

        return payment ;
    }


    /**
     *  Save a payment made as Pending.
     *
     * @param pay the Payment written out but not as yet saved
     *  @except PaymentException if the save could not be effected
     */
    protected void savePaymentAsPending(AccountId src, AbstractPayment pay)
        throws PaymentException
    {

        //
        //  Have to create and store the PendingReceipt securely
        //  before returning the payment to the caller.  Once
        //  returned, we have lost control of it, so must have the
        //  PendingReceipt receipt there for matching or cancelling.
        //
        PendingReceipt pending = new PendingReceipt(pay.getId(),
                                          pay.getItem(),
                                          pay.getSource(), pay.getTarget(),
                                          pay.getQty(),
                                          pay.getDesc(),
                                          new Date());
        try {
            receiptStore.addPendingReceipt(pending, src);
        } catch (StoreException ex) {
            ex.printStackTrace();
            throw new PaymentException(PaymentException.UNKNOWN,
                                       "Error saving Pending: " + ex);
        }
    }


    /**
     *  Make a Rollover payment.
     *  This should be used by low level clients.  It checks:
     *     * access to subaccounts,
     *     * saves the pending payment for later reconciliation.
     *  and delivers the payment back.
     *
     *  It does not check balances (by definition).
     *  It is thread safe.  Why?
     *
     * @param tgt the target account, should not be open, where funds go
     * @param src the source account (to be frozen) where funds are 
     * @param item is the name of the contract, but this is only used
     *        to identify the issuer, all contracts at that issuer effected
     * @param desc a description of what this payment is for (optional)
     * @param boolean whether or not the payment is a rollover payment
     * @param from the time from which the payment is valid
     * @param till the time at which the payment will expire
     */
    public synchronized Payment makeRollover(AccountId src, AccountId tgt,
                              ItemId item,
                              byte[] desc,
                              long till)
        throws PaymentException, AccountException
    {
        ValueAccount val = getKnownValueAccount(src, item);

        /*
         *  There is always "enough" for a rollover...
         */

        Payment payment;
        try {
            payment = val.createPayment(tgt,
                                    0,     /* value doesn't matter */
                                    desc,
                                    true,  /* the flag that makes it Rollover */
                                    System.currentTimeMillis(),
                                    till,
                                    null
                                    );
        } catch (SOXException ex) {
            ex.printStackTrace();
            throw new PaymentException(PaymentException.UNKNOWN,
                                       "Payment no good: " + ex);
        }

        savePaymentAsPending(src, payment);

        return payment ;
    }


    /**
     *  Make a Token payment.
     *  This should be used by all automatic clients, it does:
     *     * access to subaccounts,
     *     * checks balances, and
     *     * saves the pending payment for later reconciliation.
     *  and delivers the payment back.  It is thread safe.
     *
     * @param tgt the target account, may be open
     * @param src the source account where funds are written from
     * @param item is the name of the contract
     * @param amount the (contract) qty of items of which the payment is for
     * @param desc a description of what this payment is for (optional)
     * @param boolean whether or not the payment is a rollover payment
     * @param from the time from which the payment is valid
     * @param till the time at which the payment will expire
     * @param type the type of token method from PaymentFactory
     */
    public synchronized TokenPayment makeTokenPayment(
                              AccountId src, AccountId tgt,
                              ItemId item, long amount,
                              byte[] desc,
                              long from, long till,
                              int type)
        throws PaymentException, AccountException
    {
        ValueAccount val = getKnownValueAccount(src, item);

        if (amount < 0)
            throw new IllegalArgumentException("amount " + amount + " < 0 !");

        /*
         *  Check if there is enough.
         *  (There is always "enough" if the src is equal to the target
         *  or if the amount is zero...  This is a judgement call of course.)
         */
        if ((amount > 0) &&
            !gotEnough(src, item, amount))
        {
            throw new PaymentException(PaymentException.NOT_ENUF_FUNDS,
                                       "amount " + amount + " not available");
        }

        if (desc == null)
            desc = new byte[0];

        /*
         *  We need two payments: one is the proto token payment,
         *  the other is the cash to pay for it.  Both need to be
         *  written, then saved.  Then, we can attempt a deposit
         *  of the primary payment in order to pay for the proto.
         *
         *  We need to write a payment to someone who can cash these
         *  payments ...  it has to be open, as we don't otherwise
         *  know the name of the float as yet.
         */

        Payment pay;
        try {
            pay = val.createPayment(new AccountId(), amount,
                                    desc, false,
                                    from, till,
                                    null);
        } catch (SOXException ex) {
            ex.printStackTrace();
            throw new PaymentException(ex.getNumber(), "SOXEx: " + ex);
        }

        Token[] tokens;
        try {
            tokens = PaymentFactory.getProtoTokens(type, amount);
        } catch (SOXArgsException ex) {
            ex.printStackTrace();
            throw new PaymentException(PaymentException.UNKNOWN_TYPE,
                                 "Token Type " + type + " not supported");
        }

//        if (type == PaymentFactory.RANDOM_TOKEN)
//            tokens = RandomToken.getProtoTokens(amount);
//        else
//        {
//            throw new PaymentException(PaymentException.UNKNOWN_TYPE,
//                                 "Token Type " + type + " not supported");
//        }

        TokenPayment proto;
        String pid = pay.getId();
        try {
            proto = val.createTokenPayment(tokens, desc, pid + "+");
        } catch (SOXException ex) {
            ex.printStackTrace();
            throw new PaymentException(ex.getNumber(), "SOXEx: " + ex);
        }

        savePaymentAsPending(src, pay);
// actually, what is the point of saving a proto payment?
//        savePaymentAsPending(src, proto);

        //
        //  Beyond this point, failure leaves a dangling payment,
        //  cancel manually for now.
        //
        MailItem[] mails;
        try {
            mails = val.withdraw(pay, proto, new String(desc), null);
        } catch (SOXLaterException ex) {
            throw new PaymentException(ex.getNumber(), "Later: " + ex);
        } catch (SOXSubAccountException ex) {
            throw new PaymentException(ex.getNumber(), "SOXSAEx: " + ex);
        } catch (SOXDepositException ex) {
            throw new PaymentException(ex.getNumber(), "SOXDepEx: " + ex);
        } catch (SOXException ex) {
            throw new PaymentException(ex.getNumber(), "SOXEx: " + ex);
        }

        logmsg("received mails: " + mails.length);
        
        //
        //  By convention, if mails are returned, 1st one is
        //  the one for this deposit.
        //  Others are for the (sub?) account.
        //  (Actually, as implemented, there is only one mail.)
        //
        if (mails == null)
        {
            throw new PaymentException(PaymentException.UNKNOWN,
                  "Withdraw partial (no mail returned) try an update ?");
        }

        /*
         *  As we have no clue who the payment went to, extract it
         *  out of the receipt so we can ... check it against the
         *  receipt.
         */
        byte[] b = mails[0].getMessage();
        Receipt receipt;
        try {
            receipt = new Receipt(b);
        } catch (SOXPacketException ex) {
            throw new PaymentException(ex.getNumber(),
                              "SOXPEx on receipt: " + ex);
        }
        AccountId ptt = receipt.getTarget();

        String s = checkReceipt(mails[0], src, ptt, item, pay.getQty());
        if (s != null)
        {
            throw new PaymentException(PaymentException.UNKNOWN,
                  "cancel receipt is not good: " + s);
        }

        internalUpdate(val, mails);

        AbstractPayment pp = receipt.getExchangePayment();
        if (pp == null && src.equals(receipt.getTarget()))
        {
            throw new PaymentException(PaymentException.UNKNOWN_TYPE,
                      "no Payment returned in Withdrawal"+
                      "\n\n(Issuer does not support this type of Payment)");
        }
        if ( !(pp instanceof TokenPayment) )
        {
            throw new PaymentException(PaymentException.UNKNOWN,
                      "AB is not TP? : " + pp.getClass() +
                      "\n\n" + pp.toString());
        }
        TokenPayment tp = (TokenPayment)pp;

        if (!tp.isSigned())
        {
            throw new PaymentException(PaymentException.UNKNOWN,
                      "TP not signed? : " + tp.getClass() +
                      "\n\n" + pp.toString());
        }
        return tp;
    }



    /**
    public final static String SOX_MESSAGE = "SOX MESSAGE";

     *  Make an ASCII-armoured message from a payment,
     *  a la PGP armouring.  Filters out exceptions.
     *
     *  Low Level Wallet Interface.
    public static byte[] asciiArmour(AbstractPayment pay)
        throws PaymentException
    {
        if (pay == null)
            throw new PaymentException("null");

        ArmouredPayment ap = new ArmouredPayment(pay);
        return ap.encode();

    }
     */


    /**
     *  Decoder of SOX armoured messages.
     *  @return a good payment
     *  @throw PaymentException if payment could not be decoded
     *  NOT USED?
    public static AbstractPayment decodeSOXPayment(byte[] data)
        throws PaymentException
    {
        if (data == null)
            throw new PaymentException("decodeSOXPayment(null) ?");

        ArmouredPayment ap;
        try {
            ap = new ArmouredPayment(data);
        } catch (SOXPacketException ex) {
            throw new PaymentException("SOXPEx: " + ex);
        }

        AbstractPayment pay = ap.getPay();

        return pay;
    }
     */



///////////  Deposit  //////////////////////////////////////////


    private String _func;
    private void ENTER(String s) { this._func = s; }
    private void TRACE(String s) 
    { 
        System.err.println( "Wallet." + this._func + ": " + s );
    }


    /**
     *  Deposit the given payment.
     *
     *  @param pay for deposit
     *  @param account is the account to use
     *  @param desc description
     *  @param idempotentId is suggested id for deposit, ignored if null/empty
     *
     *  @throw AccountException if the target account does not exist, or
     *                          does not include the item as a subaccount
     */
    public Receipt doDeposit(AbstractPayment pay, AccountId accountid, 
                             byte[] desc, String idempotentId)
        throws DepositException, AccountException
    {
        ValueAccount val = getKnownValueAccount(accountid, pay.getItem());

        String choice = val.getAccount().getName();
        logmsg("ready to deposit payment using " + choice + "\n" + pay);

        if (desc == null)
            desc = new byte[0];

        ItemId item = pay.getItem();

        MailItem[] mails;
        try {
            mails = val.deposit(pay, new String(desc), idempotentId);

        } catch (SOXDepositException ex) {      // webfunds.sox.Errors
            throw new DepositException(ex.getNumber(), ex.getMessage());
        } catch (SOXLaterException ex) {
            throw new DepositException(ex.getNumber(), ex.getMessage());
        } catch (SOXSubAccountException ex) {   // negative
            ex.printStackTrace();
            throw new DepositException(ex.getNumber(), ex.getMessage());

        } catch (SOXArgsException ex) {
            ex.printStackTrace();
            throw new Panic("args failed: " + ex);
        } catch (SOXException ex) {
            ex.printStackTrace();
            throw new Panic("unknown exception: " + ex);
        }

        logmsg("received mails: " + mails.length);
        
        //
        //  By convention, if mails are returned, 1st one is
        //  the one for this deposit.
        //  Others are for the (sub?) account.
        //  (Actually, as implemented, there is only one mail.)
        //
        if (mails == null)
            throw new Panic(
                "Deposit partial (no mail returned) try an update...");

        AccountId ptt = pay.getTarget();
        AccountId pss = pay.getSource();
        long qty = pay.getQty();
        String s = checkReceipt(mails[0], pss, ptt, item, qty);
        if (s != null)
            /// hmmm this is really a SubAccount failure, should never happen
            throw new DepositException(SOXSubAccountException.NO_RECEIPT);

        internalUpdate(val, mails);

        Receipt rcpt = mailItemToReceipt( mails[0] );
        if( rcpt == null )
            /// hmmm this is really a SubAccount failure, should never happen
            throw new DepositException(SOXSubAccountException.NO_RECEIPT);

        return rcpt;
    }



    /**
     *  Check the receipt conforms to what was expected.
     *  @return an error string if not conformant, else null
     */
    public String checkReceipt(MailItem mail,
                               AccountId p_src, AccountId p_tgt,
                               ItemId p_item, long amount)
    {
        byte[] b = mail.getMessage();
        Receipt receipt;
        try {
            receipt = new Receipt(b);
        } catch (SOXPacketException ex) {
            return "could not recover receipt: " + ex ;
        }

        return checkReceipt(receipt, p_src, p_tgt, p_item, amount);
    }


    /**
     * Convert a MailItem to Receipt.
     *
     * @return Receipt or null
     */
    private Receipt mailItemToReceipt(MailItem mailItem)
    {
        try
        {
            return new Receipt( mailItem.getMessage() );
        }
        catch(SOXPacketException e1)
        {
            return null;
        }
    }

    /**
     *  Check the receipt conforms to what was expected.
     *  @return an error string if not conformant, else null
     */
    public String checkReceipt(Receipt receipt,
                               AccountId p_src, AccountId p_tgt,
                               ItemId p_item, long p_qty)
    {
        String statString = "";

        String s = "";

        AccountId tgt = receipt.getTarget();
        AccountId src = receipt.getSource();
        ItemId item = receipt.getItem();
        long qty = receipt.getQty();

        logmsg("checking: " +
               tgt + "==" + p_tgt + "   " +
               src + "==" + p_src + "   " +
               item + "==" + p_item + "   " +
               qty + "==" + p_qty);

        if (!tgt.equals(p_tgt))
            statString += "\nTarget " + tgt + " is not expected " + p_tgt;

        if (!src.equals(p_src))
            statString += "\nSource " + tgt + " is not expected " + p_src;

        if (!item.equals(p_item))
            statString += "\nItem " + item + " is not expected " + p_item;

        if (qty != p_qty)
            statString += "\nAmount " + qty + " is not expected " + qty;

        return (s.length() == 0) ? null : s ;
    }



///////////  Cancel  ////////////////////////


    /**
     *  Cancel a Payment (pid) within a SubAccount.
     *  And call the update.  For high-level user programs.
     *
     */
    public void cancel(AccountId accountid, ItemId item, String pid)
        throws CancelException, SOXLaterException, AccountException
    {

        ValueAccount val = getKnownValueAccount(accountid, item);

        internalCancel(val, pid);
    }

    /**
     *  Return the full state (machine) for a transaction.
     *
     *  Low Level Wallet Interface
     *
     *  @return a StateReceipt, or null if the pid is not found
     */
    public StateReceipt getTransactionState(AccountId acct,
                                ItemId item, String id)
    {
        StateReceipt sr;
        try {
            sr = receiptStore.getReceipt(acct, item, id);
        } catch (StoreException ex) {
            throw new webfunds.utils.Panic("get StateReceipt for " + id);
        }
        return sr ;
    }

    /**
     *  Cancel Payments (by pids) within a SubAccount
     *  before they hit the server.
     *
     */
    protected void internalCancel(ValueAccount sub, String[] pids)
        throws CancelException, SOXLaterException
    {
        AccountId acct = getAccountId(sub);
        ItemId item = sub.getItemId();
        logmsg("internalCancel("+acct+"/"+item+", pid["+pids.length+"])");
        Vector updates = new Vector();
        SOXLaterException soxlater = null;
        StoreException storex = null;
        int failures = 0;
        String errors = "";

        for (int i = 0; i < pids.length; i++)
        {
            String pid = pids[i];
            StateReceipt sr;
            try {
                sr = receiptStore.getReceipt(acct, item, pid);
            } catch (StoreException ex) {
                storex = ex ;
                errors += "\nStoreEx: " + ex;
                break ;
            }

            if ((sr != null) && sr.isComplete())
                continue ;

            MailItem[] mails;
            try {
                mails = quietCancel(sub, pid);
            } catch (SOXLaterException ex) {
                soxlater = ex ;
                errors += "\nLater: " + ex;
                break ;
            } catch (CancelException ex) {
                failures++;
                errors += "\npid "+pid+" failed to cancel (skipping): "+ex;
                continue ;
            }

            byte[] b = mails[0].getMessage();
            Receipt receipt;
            try {
                receipt = new Receipt(b);
            } catch (SOXPacketException ex) {
                failures++;
                errors += "\npid "+pid+"could not recover receipt: " + ex ;
                continue ;
            }

            String s = checkReceipt(receipt, acct, acct, item, 0);
            if (s != null)
            {
                // if we get this far, and we get a receipt, it must
                // be a good one.  what went wrong?
                throw new CancelException(CancelException.SERVER,
                                          "receipt is not as expected: " + s);
            }

            // hey, is this necessary?  It gets updated later anyway.
            sr.setCancelled(receipt);
            // put new sr!

            for (int j = 0; j < mails.length; j++)
                updates.addElement(mails[j]);
        }

        int num = updates.size();
        if (num > 0)
        {
            MailItem[] confirms = new MailItem[num];
            updates.copyInto(confirms);
        
            //
            //  Should this be here?  Perhaps the low level wallet
            //  should not try and force the update?  Still have
            //  to do something with the mails.
            //
            internalUpdate(sub, confirms);
            errors = "updated " + num + " mails.\n" + errors;
        }

        if (storex != null)
            throw new CancelException(CancelException.INTERNAL, errors);
        if (failures > 0)
            throw new CancelException(errors) ;
        if (soxlater != null)
            throw soxlater ;

        return ;
    }


    /**
     *  Cancel a Payment (pid) within a SubAccount.
     *
     *  @return an error string if errors detected, else empty (never null)
     */
    protected void internalCancel(ValueAccount sub, String pid)
        throws CancelException, SOXLaterException
    {

        AccountId acct = getAccountId(sub);
        ItemId item = sub.getItemId();
        logmsg("internalCancel(" + acct + "/" + item + ", " + pid + ")");

        StateReceipt sr;
        try {
            sr = receiptStore.getReceipt(acct, item, pid);
        } catch (StoreException ex) {
            throw new CancelException(CancelException.INTERNAL,ex.getMessage());
        }

        if ((sr != null) && sr.isSucceeded())
            throw new CancelException(CancelException.TOO_LATE);
        if ((sr != null) && sr.isCancelled())
            throw new CancelException(CancelException.ALREADY);

        MailItem[] mails = quietCancel(sub, pid);
        
        //
        //  Should this be here?  Perhaps the low level wallet
        //  should not try and force the update?  Still have
        //  to do something with the mails.
        //
        internalUpdate(sub, mails);

        return ;
    }


    /**
     *  Cancel many Payments (pid) within a SubAccount.
     *  Not yet used, more here as a placeholder.
     *
     *  Low Level Wallet Interface
     *
     *  @return an error string if errors detected, else empty (never null)
     */
    protected void quietCancel(ValueAccount sub, String[] pids)
    {
        throw new Panic("not implemented");
    }


    /**
     *  Cancel a Payment (pid) within a SubAccount.
     *  This can be used by other wallets, but be
     *  careful to complete the update and store by calling
     *  internalUpdate(SubAccount, MailItem[])
     *
     *  Low Level Wallet Interface
     *
     *  @return Mails received, first of which is the applicable one
     */
    protected MailItem[] quietCancel(ValueAccount sub, String pid)
        throws SOXLaterException, CancelException
    {
        //
        //  In theory, this doesn't change "state" as the important
        //  event, the writing and saving of the payment, is done
        //  elsewhere.
        //

        logmsg("cancelling " + pid);
        MailItem[] mails;
        try {
            mails = sub.cancel(pid);
        // } catch (SOXLaterException ex) {
        //     return try_again_later;
        // } catch (SOXSubAccountException e) {
        } catch (SOXException ex) {
            ex.printStackTrace(err());
            throw new CancelException(CancelException.INTERNAL,
                                      "SubAccount broken?) :\n    " + ex);
        }

        //
        //  By convention, if mails are returned, 1st one is
        //  the one for this deposit (cancel).
        //  (Actually, as implemented, will only be one.)
        //  But, if there is an error from subaccount, how to pass back?
        //  an empty array should be an exception!
        //
        if (mails == null)
            throw new CancelException(CancelException.SERVER,
                                      "Cancel failed?  (no mail returned)");

        AccountId acct = getAccountId(sub);
        String s = checkReceipt(mails[0], acct, acct, sub.getItemId(), 0);
        if (s != null)
        {
            // if we get this far, and we get a receipt, it must
            // be a good one.  what went wrong?
            throw new CancelException(CancelException.SERVER,
                                      "receipt is not as expected: " + s);
        }

        return mails ;
    }



///////////  Account / SubAccount manipulations ////////////////////////


    /**
     *  Create a new account in the Wallet and return it's AccountId.
     *
     *  @param a string name, may be null
     *  @return a valid AccountId, never null
     *  @excep AccountException for any errors
     */
    public AccountId createAccount(String name)
        throws AccountException
    {
        Account acc;
        try {
            acc = Account.getInstance();
        } catch(SOXAccountException e) {
            throw new AccountException(e.toString());
        }

        acc.setName(name);
        logmsg("createAccount: Created new account");
        AccountId accId = acc.getId();

        try {
            this.accountStore.addAccount(acc);
            this.getAccount(accId);
        } catch(StoreException e) {
            throw new AccountException(e.toString());
        }

        logmsg("createAccount: Added account to Store");

        logmsg("createAccount -> " + accId);
        return accId;
    }




    /**
     *  Cause the account to be stored and saved in its current state.
     */
    protected void storeAccount(Account account)
        throws AccountException
    {
        try {
            accountStore.addAccount(account);
        } catch (StoreException ex) {
            ex.printStackTrace();
            throw new AccountException(ex.toString());
        }
    }

    /**
     *  Rename the account.
     */
    public void renameAccount(AccountId accountid, String name)
        throws AccountException
    {
        Account account = getKnownAccount(accountid);

        account.setName(name);
        storeAccount(account);
    }

    /**
     *  Remove the account out of the account store.
     *  Only possible if it is empty of subaccounts.
     *
     *  @except AccountException if the account is unknown, or has subaccounts
     */
    public void removeAccount(AccountId accountid)
        throws AccountException
    {
        Account account = getKnownAccount(accountid);

        //
        // This is a bit grotty.
        // The account is spread across the Account, SubAccounts, Receipts.
        //
        SubAccount[] subs = account.getSubAccounts();
        if (subs.length > 0)
        {
            throw new AccountException("ac has subs");
        }

        try {
            accountStore.deleteAccount(account);
        } catch (StoreException ex) {
            throw new AccountException("ac not deleted: " + ex);
        }
    }

    /**
     *  The account and subAccount have to exist.
     *
     *  @except AccountException if the sub could not be frozen or ac not there
     */
    public void freezeSubAccount(AccountId accountid, ItemId item)
        throws AccountException
    {
        Account account = getKnownAccount(accountid);

        SubAccount sub = account.getSub(item);

        if (sub == null)
            throw new AccountException("sub not known");

        sub.freeze();

        storeAccount(account);
    }

    /**
     *  The account has to exist, the subaccount may not.
     *  Before calling this, freeze the subaccount.
     *
     *  @except AccountException if the sub not there or not frozen
     */
    public void removeSubAccount(AccountId accountid, ItemId item)
        throws AccountException
    {
        Account account = getKnownAccount(accountid);

        SubAccount sub = account.getSub(item);

        if (sub == null)
            throw new AccountException("sub not known");

        if (!sub.isFrozen())
            throw new AccountException("sub not frozen");

        // receipts are not lost by this mechanism, which makes
        // it silly - how to see the receipts?

        account.removeSubAccount(item);

        storeAccount(account);
    }


    /**
     *  Adds the Value subaccount to the Account.
     *
     */
    public void addValueAccount(AccountId accountid, ItemId item)
        throws AccountException
    {
        Account account = getKnownAccount(accountid);

        //
        // First, create the subaccount.
        // Then, add the account back into the store and hope it saves.
        //
        SubAccount sub = account.getSub(item);
        if (sub != null)
        {
            throw new AccountException("already? : " + sub);
        }
        sub = new ValueAccount(item);

        addSubAccount(account, sub);

    }

    /**
     *  Adds the subaccount to the Account.
     *
     */
    protected void addSubAccount(Account acct, SubAccount sub)
        throws AccountException
    {

        //
        //  Ouch!  This part should be SYNCronised, as it can
        //  trigger slowdowns and the user can call in here
        //  many times...
        //
        try {
            acct.newSub(sub);
        } catch (SOXSubAccountException ex) {
            ex.printStackTrace();
            throw new AccountException("EX?: " + ex);
        } catch (SOXAccountException ex) {
            ex.printStackTrace();
            throw new AccountException("EX?: " + ex);
        }

        storeAccount(acct);

        //
        // Quick check that it is there.
        //

        Account account = getAccountOrDie(acct.getId());
        ItemId item = sub.getItemId();
        ItemId[] items = account.getItemIds();

        logmsg("#items: " + items.length);
        boolean found = false;
        for (int i = 0; i < items.length; i++)
        {
            if (items[i].equals(item))
                found = true;
            logmsg("item: " + items[i]);
        }

        if (!found)
            throw new Panic("could not add sub to " + acct + ": " + sub);
    }



/////////////   U P D A T E   /////////////////////////////

    /**
     *  Make an AccountId for this sub account.
     *  Gets the account, then the id.
     *  The address bits are unset.
     */
    protected AccountId getAccountId(SubAccount sub)
    {
        Account ac = sub.getAccount();
        return ac.getId();
    }

    /**
     *  Get and sign for the mail for the item's server for this account.
     *  Will drag in all items that are handled by this account at
     *  that SOX Server - part of the account request structure.
     *
     *  The account and sub account must exist.
     *
     *  @throw AccountException if something doesn't exist
     */
    public void update(AccountId accountid, ItemId item)
        throws AccountException
    {
        logmsg("AccountId update");

        SubAccount sub = getKnownSubAccount(accountid, item);

        //
        //  Do the sub.update, which sends all cached hashes,
        //  and returns more receipts.
        //
        try
        {
            manyUpdates(sub, null);       // sub is surrogate for account

        } catch (SOXException e) {
            e.printStackTrace();
            logmsg("update failed: " + e);
        }

    }



    /**
     *  Handle these mails that came via another request
     *  or another subaccount.
     *  The subaccount must identify the account and the SOXServer.
     *  All mail items must come from the same SOXServer,
     *  (but are not necessarily the same subaccount, etc).
     *  This points to a flaw in the sox object model.
     */
    protected void internalUpdate(SubAccount sub, MailItem[] mails)
    {
        try {
            manyUpdates(sub, mails);
        } catch (SOXLaterException ex) {
            logmsg("try mail later!");
        } catch (SOXException ex) {
            logmsg("SOXEx: " + ex);
        }
    }


    protected void internalUpdate(SubAccount sub, MailItem mail)
    {
        MailItem[] mm = new MailItem[1];
        mm[0] = mail;
        internalUpdate(sub, mm);
    }


    /**
     *  Get and sign for the mail from the issuer for this account,
     *  and handle these mails also.
     *  If any new mail is returned in the process, deal with that
     *  and sign for it as well, cyclically.
     */
    protected void manyUpdates(SubAccount sub, MailItem[] mails)
        throws SOXLaterException, SOXException
    {
        MailId[] confirms = null;

        logmsg("manyUpdates(" + ((mails == null)? 0: mails.length) + ")");
        if (mails != null && (mails.length > 0))
            confirms = tryHandle(sub, mails);

        //
        //  Spin around getting and signing for mail.
        //  Would often be two cycles, might be 3 if
        //  something arrived during the process.
        //  Too many cycles would probably indicate a bug
        //  somewhere.  Although, if someone does a whole
        //  bunch of transactions, this can cause a steady
        //  series of cycles.  Better to back off in that
        //  case.  (Like if WebFunds does a hundred cancels...)
        //
        for (int i = 0; i < 10; i++)
        {
            // mails = tryUpdate(sub, confirms);
            mails = sub.update(confirms);

            if (mails == null || (mails.length == 0))
                return ;
            logmsg("for(;" + i + "<4; ++) on " + mails.length + " mails");

            //
            //  Did I get some more mail in that update?
            //  Sign for it now ...  Actually, this is necessary
            //  as there is nowhere else to save the confirms.
            //  Can't save it in the sub account as that is only
            //  a copy...
            //
            confirms = tryHandle(sub, mails);
            if (confirms == null || (confirms.length == 0))
                return ;
        }

        logmsg("too many: mail appears to be spinning");
        return ;
    }



    /**
     * Do an update - one single cycle of request with sigs.
     */
    protected MailId[] tryHandle(SubAccount sub, MailItem[] mails)
    {
        Vector newConfirms = new Vector();

        //
        //  Have received some more mail,
        //  save them and ready them for confirm
        //
        logmsg("tryHandle() received " + mails.length + " mail items");
        for (int i = 0; i < mails.length; i++)
        {
            MailItem mail = mails[i];
            if (handleMail(mail, sub))      // handle mail, and
            {
                MailId id = mail.getMailId();  // confirm this number
                newConfirms.addElement(id);  // confirm
                logmsg("uccessfully saved " + id);
            }
        }

        MailId[] confirms = new MailId[newConfirms.size()];
        newConfirms.copyInto(confirms);
        logmsg("have " + confirms.length + " MailIds ready to confirm for " +
               sub.fp());
        return confirms ;
    }



    /**
     * Switchboard for various mail types.
     * Currently, we only handle Receipts here.
     * To handle other types, override this method.
     *
     * @return true if it is saved *and* can now be confirmed.
     *         should really be ternary:  {don't know, failed to save, ok}
     *         so we can extend, and call super() on this method.
     *         throw a save-fail exception?
     */
    protected boolean handleMail(MailItem mail, SubAccount sub)
    {
        boolean ok = false;

        if (mail.isReceipt())
            ok = handleReceiptData(mail.getMessage(), getAccountId(sub));
        else
            logmsg("Unknown mail: " + mail.getType() + " ... ignoring:\n\n" +
                   Hex.data2hex(mail.getMail()) + "\n\n");

        return ok ;
    }


    /**
     *  For some reason, Receipts are stored on an account basis.
     */
    protected boolean handleReceiptData(byte[] recdata, AccountId acct)
    {
        Receipt rec = null;
        try {
            rec = new Receipt(recdata);
        } catch (SOXPacketException ex) {
            // SEPs:  ex.printStackTrace(err());
            logmsg("not a receipt: " + ex);
            logmsg(Hex.data2hex(recdata));
            return false ;
        }
        logmsg("handleReceiptData(): " + rec);
        return handleReceipt(rec, acct);
    }


    protected boolean handleReceipt(Receipt rec, AccountId acct)
    {
        try {
            receiptStore.addReceipt(rec, acct);
        } catch (StoreException ex) {
            ex.printStackTrace();
            logmsg("losing receipts for " + acct.fp() + ": " + ex);
            return false ;   // must not confirm !!!!
        }

        return true ;        // got it stored, please confirm
    }

    protected void confirm(Vector v, byte[] b)
    {
        v.addElement(MailId.newInstance(b));

    }

    protected void confirm(Vector v, MailItem mi)
    {
        v.addElement(mi.getMailId());
    }



////////////////  Transactions   ///////////////////////////////

    /**
     *
     *  @return a list of Receipt within this subaccount
     */
    public Receipt[] getReceipts(AccountId acct, ItemId item)
        throws AccountException
    {
        Receipt[] receipts;

        try
        {
            receipts = receiptStore.getReceipts(acct, item);
        }
        catch (StoreException ex)
        {
            throw new AccountException("no pendings: " + ex);
        }

        return receipts;
    }

    /**
     *
     *  @return a list of Pendings within this subaccount
     */
    public PendingReceipt[] getPending(AccountId acct, ItemId item)
        throws AccountException
    {
        PendingReceipt[] pendings;

        try
        {
            pendings = receiptStore.getPendingReceipts(acct, item);
        }
        catch (StoreException ex)
        {
            throw new AccountException("no pendings: " + ex);
        }

        return pendings;
    }


    public String toString()
    {
        return shortname;
    }
}