chiark / gitweb /
thread fixes: wip coroutine-style explicit multithread management
authorIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 2 Apr 2011 15:40:28 +0000 (16:40 +0100)
committerIan Jackson <ijackson@chiark.greenend.org.uk>
Sat, 2 Apr 2011 15:44:53 +0000 (16:44 +0100)
src/net/chiark/yarrg/MarketUploader.java

index 4e04ee4..5dc7e6c 100644 (file)
@@ -72,25 +72,31 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
   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();
-       }
+    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
+   *
+   * Useable on any thread.
+   *
+   */
   private int parseQty(String str) {
     if (str.equals(">1000")) {
       return 1001;
@@ -120,15 +126,6 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
     strm.write(data);
     strm.close();
   }
-
-  private void progressNote(ProgressMonitor pm, String s) {
-    String arb = null;
-    if (arbitrageResult != null)
-      arb = arbitrageResult.getText();
-    if (arb != null && arb.length() != 0)
-      s = "<html>" + arb + "<br>" + s;
-    pm.setNote(s);
-  }
        
   /**
    *   An abstract market offer, entailing a commodity being bought or sold by
@@ -267,8 +264,24 @@ 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();
+      if (arb != null && arb.length() != 0)
+       s = "<html>" + arb + "<br>" + s;
+      pm.setNote(s);
+    }}.exec();
+  }
        
-  /**
+  /*
+   * ENTRY POINT AND STARTUP
+   *
+   * Main thread and/or event thread
+   */
+  /*
    *   Entry point.  Read our preferences.
    */
   public MarketUploader() {
@@ -347,6 +360,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *   a Window named "Puzzle Pirates" though.
    */
   private void createGUI() {
+    // on event thread
     frame = new JFrame("Jarrg Uploader");
     frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
     GridLayout layout = new GridLayout(2,1);
@@ -357,25 +371,24 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
     findMarket.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          findMarket.setEnabled(false);
+         resultSummary.setText("");
+         arbitrageResult.setText("");
          new Thread() {
            public void run() {
              startTime = new Date().getTime();
-             resultSummary.setText("");
-             arbitrageResult.setText("");
              unknownPCTBcommods = 0;
              try {
                runUpload();
              } catch(Exception e) {
                error(e.toString());
                e.printStackTrace();
-               resultSummary.setText("failed");
-             } finally {
+             }
+             EventQueue.invokeAndWait(new Runnable() { public void run() { 
                if(sidePanel != null) {
-                 // remove it if it's still attached
                  sidePanel.removePropertyChangeListener(changeListener);
                }
-             }
-             findMarket.setEnabled(true);
+               findMarket.setEnabled(true);
+             }});
            }
          }.start();
        }
@@ -394,11 +407,79 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
 
     frame.pack();
   }
+
+  /*
+   * ERROR REPORTING AND GENERAL UTILITIES
+   *
+   * Synchronous modal dialogues
+   * 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() {
+      if (EventQueue.isDispatchThread()) {
+       r.run();
+      } else {
+       invokeAndWait(r);
+      }
+    };
+  };
+
+  private void error(String msg) {
+    new UI() { public booolean body() {
+      resultSummary.setText("failed");
+      JOptionPane.showMessageDialog(frame,msg,"Error",
+                                   JOptionPane.ERROR_MESSAGE);
+      return true;
+    }}.exec();
+  }
        
-  /**
-   *   Finds the island name from the /who tab, sets global islandName variable
+  private void error_html(String msg, String html) {
+    Pattern body = Pattern.compile("<body>(.*)</body>",
+                                  Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
+    Matcher m = body.matcher(html);
+    if (m.find()) {
+      html = m.group(1);
+      Pattern fixup = Pattern.compile("<(\\w+) */>");;
+      m = fixup.matcher(html);
+      html = m.replaceAll("<$1>");
+      m = Pattern.compile("[\\r\\n]+").matcher(html);
+      html = m.replaceAll(" ");
+    }
+    String whole_msg = "<html><h1>Error</h1>"+msg
+      +"<h1>PCTB Server said:</h1><blockquote>"+html+"</blockquote>";
+    if (dtxt!=null) dtxt.println("###" + whole_msg + "###");
+  
+    error(whole_msg);
+  }
+       
+  /*
+   * GUI MANIPULATION CALLBACKS
    */
+
+  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();
+      }
+    }
+  };
+
   private void getIsland() {
+    // runs on event thread
 
     // If the league tracker is there, we can skip the faff
     // and ask for its tooltip, since we're on a boat
@@ -450,6 +531,7 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
    *      oceanName variable
    */
   private void getOcean() {
+               // runs on event thread
     oceanName = null;
     AccessibleContext topwindow = window.getAccessibleContext();
     oceanName = topwindow.getAccessibleName()
@@ -458,35 +540,6 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
 
 
   /**
-   *   Shows a dialog with the error <code>msg</code>.
-   *
-   *   @param msg a String describing the error that occured.
-   */
-  private void error(String msg) {
-    JOptionPane.showMessageDialog(frame,msg,"Error",JOptionPane.ERROR_MESSAGE);
-  }
-       
-  private void error_html(String msg, String html) {
-    Pattern body = Pattern.compile("<body>(.*)</body>",
-                                  Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
-    Matcher m = body.matcher(html);
-    if (m.find()) {
-      html = m.group(1);
-      Pattern fixup = Pattern.compile("<(\\w+) */>");;
-      m = fixup.matcher(html);
-      html = m.replaceAll("<$1>");
-      m = Pattern.compile("[\\r\\n]+").matcher(html);
-      html = m.replaceAll(" ");
-    }
-    String whole_msg = "<html><h1>Error</h1>"+msg
-      +"<h1>PCTB Server said:</h1><blockquote>"+html+"</blockquote>";
-    if (dtxt!=null) dtxt.println("###" + whole_msg + "###");
-
-    JOptionPane.showMessageDialog(frame,whole_msg,"Error",
-                                 JOptionPane.ERROR_MESSAGE);
-  }
-       
-  /**
    *   Run the data collection process, and upload the results. This
    *   is the method that calls most of the other worker methods for
    *   the process. If an error occurs, the method will call the
@@ -508,41 +561,46 @@ implements Runnable, TopLevelWindowListener, GUIInitializedListener {
   };
 
   private void runUpload() throws Exception {
-    progresslog("starting");
-
-    ProgressMonitor pm = new ProgressMonitor
-      (frame,"Processing Market Data","Getting table data",0,100);
-    pm.setMillisToDecideToPopup(0);
-    pm.setMillisToPopup(0);
     boolean doneyarrg = false, donepctb = false;
     YarrgTimestampFetcher yarrgts_thread = null;
 
+    progresslog("starting");
+
     if (uploadToYarrg) {
       progresslog("(async) yarrg timestamp...");
       yarrgts_thread = new YarrgTimestampFetcher();
       yarrgts_thread.start();
     }
 
-    AccessibleTable accesstable = findMarketTable();
-    if(accesstable == null) {
-      error("Market table not found!"+
-           " Please open the Buy/Sell Commodities interface.");
-      return;
-    }
-    if(accesstable.getAccessibleRowCount() == 0) {
-      error("No data found, please wait for the table to have data first!");
-      return;
-    }
-    if(!isDisplayAll()) {
-      error("Please select \"All\" from the Display: popup menu.");
-      return;
-    }
+    AccessibleTable accesstable = null;
+    new UI() { public boolean body() {
+      ProgressMonitor pm = new ProgressMonitor
+       (frame,"Processing Market Data","Getting table data",0,100);
+      pm.setMillisToDecideToPopup(0);
+      pm.setMillisToPopup(0);
+
+      accesstable = findMarketTable();
+      if(accesstable == null) {
+       error("Market table not found!"+
+             " Please open the Buy/Sell Commodities interface.");
+       return;
+      }
+      if(accesstable.getAccessibleRowCount() == 0) {
+       error("No data found, please wait for the table to have data first!");
+       return;
+      }
+      if(!isDisplayAll()) {
+       error("Please select \"All\" from the Display: popup menu.");
+       return;
+      }
 
-    progresslog("(async) getisland...");
-    getIsland();
-    progresslog("getocean...");
-    getOcean();
-    progresslog("getocean done");
+      progresslog("(async) getisland...");
+      getIsland();
+      progresslog("getocean...");
+      getOcean();
+      progresslog("getocean done");
+    }}.exec();
+    if (accesstable == null) return;
 
     if (latch != null) {
       latch.await(2, java.util.concurrent.TimeUnit.SECONDS);
@@ -564,34 +622,39 @@ 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" };
-    ArrayList<ArrayList<String>> headers =
-      getData(accesstable.getAccessibleColumnHeader());
-    if (headers.size() != 1) {
-      error("Table headings not one row! " + headers.toString());
-      return;
-    }
-    if (headers.get(0).size() < 6 ||
-       headers.get(0).size() > 7) {
-      error("Table headings not six or seven columns! " + headers.toString());
-      return;
-    }
-    for (int col=0; col<headings_expected.length; col++) {
-      String expd = headings_expected[col];
-      String got = headers.get(0).get(col);
-      if (expd.compareTo(got) != 0) {
-       error("Table heading for column "+col
-             +" is not \""+expd+"\" but \""+got+"\".\n\n"
-             +"Please do not reorder the table when using this tool.");
+
+    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;
       }
-    }
+      if (headers.get(0).size() < 6 ||
+         headers.get(0).size() > 7) {
+       error("Table headings not six or seven columns! " + headers.toString());
+       return;
+      }
+      for (int col=0; col<headings_expected.length; col++) {
+       String expd = headings_expected[col];
+       String got = headers.get(0).get(col);
+       if (expd.compareTo(got) != 0) {
+         error("Table heading for column "+col
+               +" is not \""+expd+"\" but \""+got+"\".\n\n"
+               +"Please do not reorder the table when using this tool.");
+         return;
+       }
+      }
 
-    progresslog("table read...");
+      progresslog("table read...");
 
-    ArrayList<ArrayList<String>> data = getData(accesstable);
+      ArrayList<ArrayList<String>> data = getData(accesstable);
+    }}.exec();
+    if (!data) return;
 
     if (showArbitrage) {
       progresslog("arbitrage...");