chiark / gitweb /
thread fixes: coroutine-style explicit multithread management, compiles
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 2 Apr 2011 17:20:06 +0000 (18:20 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 2 Apr 2011 17:20:06 +0000 (18:20 +0100)
src/net/chiark/yarrg/MarketUploader.java

index 5dc7e6c..dda1538 100644 (file)
@@ -43,6 +43,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
   private JLabel arbitrageResult = null;
   private int unknownPCTBcommods = 0;
   private long startTime = 0;
+  private ProgressMonitor progmon = null;
 
   private final static String PCTB_LIVE_HOST_URL = "http://pctb.crabdance.com/";
   private final static String PCTB_TEST_HOST_URL = "http://pctb.ilk.org/";
@@ -71,26 +72,6 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
   private HashMap<String,Integer> commodMap;
   public PrintStream dtxt = null;
 
-  private PropertyChangeListener changeListener = new PropertyChangeListener() {
-    public void propertyChange(PropertyChangeEvent e) {
-      if(e.getNewValue() != null && 
-        e.getPropertyName().equals
-        (AccessibleContext.ACCESSIBLE_CHILD_PROPERTY)) {
-       Accessible islandInfo = 
-         descendNodes(window,new int[] {0,1,0,0,2,2,0,0,0,0,1,2});;
-       String text = 
-         islandInfo.getAccessibleContext().getAccessibleText()
-         .getAtIndex(AccessibleText.SENTENCE,0);
-       int index = text.indexOf(":");
-       String name = text.substring(0,index);
-       islandName = name;
-       // if (dtxt!=null) dtxt.println(islandName);
-       sidePanel.removePropertyChangeListener(this);
-       latch.countDown();
-      }
-    }
-  };
-
   /*
    * UTILITY METHODS AND SUBCLASSES
    *
@@ -265,14 +246,14 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
     }
   }
 
-  private void progressNote(ProgressMonitor pm, String s) {
-    String arb = null;
-    new UI() { public void body() {
-      if (arbitrageResult != null)
-       arb = arbitrageResult.getText();
+  private void progressNote(final String s_in) throws Exception {
+    new UIX() { public void body() {
+      String arb = null;
+      arb = arbitrageResult.getText();
+      String s = s_in;
       if (arb != null && arb.length() != 0)
        s = "<html>" + arb + "<br>" + s;
-      pm.setNote(s);
+      progmon.setNote(s);
     }}.exec();
   }
        
@@ -360,7 +341,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   a Window named "Puzzle Pirates" though.
    */
   private void createGUI() {
-    // on event thread
+    on_ui_thread();
     frame = new JFrame("Jarrg Uploader");
     frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
     GridLayout layout = new GridLayout(2,1);
@@ -370,6 +351,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
     findMarket = new JButton("Upload Market Data");
     findMarket.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
+         on_ui_thread();
          findMarket.setEnabled(false);
          resultSummary.setText("");
          arbitrageResult.setText("");
@@ -383,12 +365,21 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
                error(e.toString());
                e.printStackTrace();
              }
-             EventQueue.invokeAndWait(new Runnable() { public void run() { 
-               if(sidePanel != null) {
-                 sidePanel.removePropertyChangeListener(changeListener);
-               }
-               findMarket.setEnabled(true);
-             }});
+             try {
+               new UIX() { public void body() { 
+                 if(sidePanel != null) {
+                   sidePanel.removePropertyChangeListener(changeListener);
+                 }
+                 if (progmon != null) {
+                   progmon.close();
+                   progmon = null;
+                 }
+                 findMarket.setEnabled(true);
+               }}.exec();
+             } catch (Exception e) {
+               System.err.println("exception tidying on UI thread:");
+               e.printStackTrace();
+             }
            }
          }.start();
        }
@@ -415,28 +406,41 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    * error and error_html may be called from any thread
    */ 
 
-  private Class UI implements Runnable {
-    public virtual void body();
-    public void run() { body(); };
-    public void exec() {
+  private abstract class UIXR<ReturnType> implements Runnable {
+    public abstract ReturnType bodyr();
+    public ReturnType return_value;
+    public void run() { return_value = bodyr(); };
+    public ReturnType exec() throws Exception {
       if (EventQueue.isDispatchThread()) {
-       r.run();
+       this.run();
       } else {
-       invokeAndWait(r);
+       EventQueue.invokeAndWait(this);
       }
+      return return_value;
     };
   };
+  private abstract class UIX extends UIXR<Object> implements Runnable {
+    public abstract void body();
+    public Object bodyr() { body(); return null; }
+  };
 
-  private void error(String msg) {
-    new UI() { public booolean body() {
-      resultSummary.setText("failed");
-      JOptionPane.showMessageDialog(frame,msg,"Error",
-                                   JOptionPane.ERROR_MESSAGE);
-      return true;
-    }}.exec();
+  private void on_ui_thread() { assert(EventQueue.isDispatchThread()); }
+  private void on_our_thread() { assert(!EventQueue.isDispatchThread()); }
+
+  private void error(final String msg) {
+    try {
+      new UIX() { public void body() {
+       resultSummary.setText("failed");
+       JOptionPane.showMessageDialog(frame,msg,"Error",
+                                     JOptionPane.ERROR_MESSAGE);
+      }}.exec();
+    } catch (Exception e) {
+      System.err.println("exception reporting to UI thread:");
+      e.printStackTrace();
+    }
   }
        
-  private void error_html(String msg, String html) {
+  private void error_html(final String msg, String html) {
     Pattern body = Pattern.compile("<body>(.*)</body>",
                                   Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
     Matcher m = body.matcher(html);
@@ -454,6 +458,17 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
   
     error(whole_msg);
   }
+
+  private void setProgress(final int nv) throws Exception {
+    new UIX() { public void body() {
+      progmon.setProgress(nv);
+    }}.exec();
+  }
+  private boolean isCanceled() throws Exception {
+    return new UIXR<Boolean>() { public Boolean bodyr() {
+      return new Boolean(progmon.isCanceled());
+    }}.exec().booleanValue();
+  }
        
   /*
    * GUI MANIPULATION CALLBACKS
@@ -461,6 +476,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
 
   private PropertyChangeListener changeListener = new PropertyChangeListener() {
     public void propertyChange(PropertyChangeEvent e) {
+      on_ui_thread();
       if(e.getNewValue() != null && 
         e.getPropertyName().equals
         (AccessibleContext.ACCESSIBLE_CHILD_PROPERTY)) {
@@ -479,7 +495,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
   };
 
   private void getIsland() {
-    // runs on event thread
+    on_ui_thread();
 
     // If the league tracker is there, we can skip the faff
     // and ask for its tooltip, since we're on a boat
@@ -531,7 +547,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *      oceanName variable
    */
   private void getOcean() {
-               // runs on event thread
+    on_ui_thread();
     oceanName = null;
     AccessibleContext topwindow = window.getAccessibleContext();
     oceanName = topwindow.getAccessibleName()
@@ -561,6 +577,8 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
   };
 
   private void runUpload() throws Exception {
+    on_our_thread();
+
     boolean doneyarrg = false, donepctb = false;
     YarrgTimestampFetcher yarrgts_thread = null;
 
@@ -572,26 +590,26 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
       yarrgts_thread.start();
     }
 
-    AccessibleTable accesstable = null;
-    new UI() { public boolean body() {
-      ProgressMonitor pm = new ProgressMonitor
+    final AccessibleTable accesstable = 
+    new UIXR<AccessibleTable>() { public AccessibleTable bodyr() {
+      progmon = new ProgressMonitor
        (frame,"Processing Market Data","Getting table data",0,100);
-      pm.setMillisToDecideToPopup(0);
-      pm.setMillisToPopup(0);
+      progmon.setMillisToDecideToPopup(0);
+      progmon.setMillisToPopup(0);
 
-      accesstable = findMarketTable();
-      if(accesstable == null) {
+      AccessibleTable at = findMarketTable();
+      if(at == null) {
        error("Market table not found!"+
              " Please open the Buy/Sell Commodities interface.");
-       return;
+       return null;
       }
-      if(accesstable.getAccessibleRowCount() == 0) {
+      if(at.getAccessibleRowCount() == 0) {
        error("No data found, please wait for the table to have data first!");
-       return;
+       return null;
       }
       if(!isDisplayAll()) {
        error("Please select \"All\" from the Display: popup menu.");
-       return;
+       return null;
       }
 
       progresslog("(async) getisland...");
@@ -599,6 +617,8 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
       progresslog("getocean...");
       getOcean();
       progresslog("getocean done");
+
+      return at;
     }}.exec();
     if (accesstable == null) return;
 
@@ -622,22 +642,23 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
 
     progresslog("table check...");
 
-    ArrayList<ArrayList<String>> data = null;
-    String headings_expected[] = new String[]
-      { "Commodity", "Trading outlet", "Buy price",
-       "Will buy", "Sell price", "Will sell" };
+    final ArrayList<ArrayList<String>> data =
+    new UIXR<ArrayList<ArrayList<String>>>
+          () { public ArrayList<ArrayList<String>> bodyr() {
+      String headings_expected[] = new String[]
+       { "Commodity", "Trading outlet", "Buy price",
+         "Will buy", "Sell price", "Will sell" };
 
-    new UI() { public void body() {
       ArrayList<ArrayList<String>> headers =
        getData(accesstable.getAccessibleColumnHeader());
       if (headers.size() != 1) {
        error("Table headings not one row! " + headers.toString());
-       return;
+       return null;
       }
       if (headers.get(0).size() < 6 ||
          headers.get(0).size() > 7) {
        error("Table headings not six or seven columns! " + headers.toString());
-       return;
+       return null;
       }
       for (int col=0; col<headings_expected.length; col++) {
        String expd = headings_expected[col];
@@ -646,15 +667,15 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
          error("Table heading for column "+col
                +" is not \""+expd+"\" but \""+got+"\".\n\n"
                +"Please do not reorder the table when using this tool.");
-         return;
+         return null;
        }
       }
 
       progresslog("table read...");
 
-      ArrayList<ArrayList<String>> data = getData(accesstable);
+      return getData(accesstable);
     }}.exec();
-    if (!data) return;
+    if (data == null) return;
 
     if (showArbitrage) {
       progresslog("arbitrage...");
@@ -664,8 +685,8 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
 
     if (uploadToYarrg && yarrgts != null) {
       progresslog("yarrg prepare...");
-      progressNote(pm, "Yarrg: Preparing data");
-      pm.setProgress(10);
+      progressNote("Yarrg: Preparing data");
+      setProgress(10);
 
       StringBuilder yarrgsb = new StringBuilder();
       String yarrgdata; // string containing what we'll feed to yarrg
@@ -684,7 +705,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
 
       yarrgdata = yarrgsb.toString();
 
-      progressNote(pm, "Yarrg: Uploading");
+      progressNote("Yarrg: Uploading");
       progresslog("yarrg upload...");
 
       doneyarrg = runYarrg(yarrgts, oceanName, islandName, yarrgdata);
@@ -693,17 +714,17 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
 
     if (uploadToPCTB) {
       progresslog("pctb prepare...");
-      progressNote(pm, "PCTB: Getting stall names");
-      pm.setProgress(20);
-      if(pm.isCanceled()) {
+      progressNote("PCTB: Getting stall names");
+      setProgress(20);
+      if(isCanceled()) {
        return;
       }
       TreeSet<Offer> buys = new TreeSet<Offer>();
       TreeSet<Offer> sells = new TreeSet<Offer>();
       LinkedHashMap<String,Integer> stallMap = getStallMap(data);
-      pm.setProgress(40);
-      progressNote(pm, "PCTB: Sorting offers");
-      if(pm.isCanceled()) {
+      setProgress(40);
+      progressNote("PCTB: Sorting offers");
+      if(isCanceled()) {
        return;
       }
       // get commod map
@@ -719,9 +740,9 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
       // if (dtxt!=null) dtxt.println("\n\n\n"+buys);
 
       ByteArrayOutputStream outStream = new ByteArrayOutputStream();
-      pm.setProgress(60);
-      progressNote(pm, "PCTB: Sending data");
-      if(pm.isCanceled()) {
+      setProgress(60);
+      progressNote("PCTB: Sending data");
+      if(isCanceled()) {
        return;
       }
       GZIPOutputStream out = new GZIPOutputStream(outStream);
@@ -739,27 +760,33 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
       InputStream in = sendInitialData(new ByteArrayInputStream(ba));
       progresslog("pctb sent.");
       if (in == null) return;
-      pm.setProgress(80);
-      if(pm.isCanceled()) {
+      setProgress(80);
+      if(isCanceled()) {
        return;
       }
-      progressNote(pm, "PCTB: Waiting ...");
+      progressNote("PCTB: Waiting ...");
       progresslog("pctb finish...");
       donepctb = finishUpload(in);
       progresslog("pctb done.");
     }
-    pm.setProgress(100);
+    setProgress(99);
 
+    String summary;
     if ((uploadToPCTB && !donepctb) ||
        (uploadToYarrg && !doneyarrg)) {
-      resultSummary.setText("trouble");
+      summary= "trouble";
     } else if (unknownPCTBcommods != 0) {
-      resultSummary.setText("PCTB lacks "+unknownPCTBcommods+" commod");
+      summary= "PCTB lacks "+unknownPCTBcommods+" commod(s)";
     } else if (donepctb || doneyarrg) {
-      resultSummary.setText("Done " + islandName);
+      summary= "Done " + islandName;
     } else {
-      resultSummary.setText("uploaded nowhere!");
+      summary= "uploaded nowhere!";
     }
+    final String summary_final = summary;
+    new UIX() { public void body() {
+      resultSummary.setText(summary_final);
+    }}.exec();
+
     progresslog("done.");
   }
        
@@ -771,6 +798,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   @return an array of record arrays, each representing a row of the table
    */
   private ArrayList<ArrayList<String>> getData(AccessibleTable table) {
+    on_ui_thread();
     ArrayList<ArrayList<String>> data = new ArrayList<ArrayList<String>>();
     for (int i = 0; i < table.getAccessibleRowCount(); i++) {
       ArrayList<String> row = new ArrayList<String>();
@@ -788,6 +816,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   otherwise <code>null</code>
    */
   public AccessibleTable findMarketTable() {
+    on_ui_thread();
     Accessible node1 = window;
     Accessible node = descendNodes(node1,new int[] {0,1,0,0,0,0,1,0,0,1,0,0}); 
       // commod market
@@ -815,6 +844,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   invalid.
    */
   private Accessible descendNodes(Accessible parent, int[] path) {
+    on_ui_thread();
     for(int i=0;i<path.length;i++) {
       if (null == (parent = descend(parent, path[i]))) return null;
     }
@@ -831,6 +861,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   or <code>null</code> if the child is not found.
    */
   private Accessible descend(Accessible parent, int childNum) {
+    on_ui_thread();
     if (parent == null) return null;
     int children = parent.getAccessibleContext().getAccessibleChildrenCount();
     if (childNum >= children) {
@@ -853,6 +884,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   @return the child or <code>null</code> if the child is not found.
    */
   private Accessible descendByClass(Accessible parent, String classname) {
+    on_ui_thread();
     if (parent == null) return null;
     AccessibleContext ac = parent.getAccessibleContext();
     int children = ac.getAccessibleChildrenCount();
@@ -879,6 +911,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   otherwise <code>false</code>
    */
   private boolean isDisplayAll() {
+    on_ui_thread();
     Accessible button = descendNodes(window,new int[] {0,1,0,0,0,0,1,0,0,0,1});
     if(!(button instanceof JButton)) {
       button = descendNodes(window,new int[] {0,1,0,0,0,0,1,0,1,0,0,0,1});
@@ -897,6 +930,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   the commodity id.
    */
   private HashMap<String,Integer> getCommodMap() {
+    on_our_thread();
     if(commodMap != null) {
       return commodMap;
     }
@@ -1144,6 +1178,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   @param file an InputStream open to the gzipped data we want to send
    */
   private InputStream sendInitialData(InputStream file) throws IOException {
+    on_our_thread();
     ClientHttpRequest http =
       new ClientHttpRequest(PCTB_HOST_URL + "upload.php");
     http.setParameter("marketdata","marketdata.gz",file,"application/gzip");
@@ -1177,6 +1212,8 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   @param in stream of data from the server to read
    */
   private boolean finishUpload(InputStream in) throws IOException {
+    on_our_thread();
+
     String html = readstreamstring(in);
     debug_write_stringdata("pctb-initial.html", html);
     Matcher m;
@@ -1238,6 +1275,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
 
   private InputStream post_for_yarrg(ClientHttpRequest http)
   throws IOException {
+    on_our_thread();
     if (!http.post()) {
       String err = readstreamstring(http.resultstream());
       error("<html><h1>Error reported by YARRG server</h1>\n" + err);
@@ -1333,7 +1371,8 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
   }
 
   private @SuppressWarnings("unchecked")
-    void calculateArbitrage(ArrayList<ArrayList<String>> data) {
+  void calculateArbitrage(ArrayList<ArrayList<String>> data) 
+  throws InterruptedException {
     int arbitrage = 0;
     ArrayList<SortedSet<int[]>> arb_bs = null;
     String lastcommod = null;
@@ -1364,12 +1403,16 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
       }
     }
     arbitrage += calculateArbitrageCommodity(arb_bs);
+    String arb;
     if (arbitrage != 0) {
-      arbitrageResult.setText("<html><strong>arbitrage: "+arbitrage
-                             +" poe</strong>");
+      arb = "<html><strong>arbitrage: "+arbitrage+" poe</strong>";
     } else {
-      arbitrageResult.setText("no arbitrage");
+      arb = "no arbitrage";
     }
+    final String arb_final = arb;
+    EventQueue.invokeLater(new Runnable() { public void run() {
+      arbitrageResult.setText(arb_final);
+    }});
   }
     
 }